diff --git a/funks.js b/funks.js index 9878aef6..5ca138c5 100644 --- a/funks.js +++ b/funks.js @@ -795,6 +795,10 @@ module.exports.parseAssociations = function (dataModel) { // set extra association fields assoc["targetKey"] = association.targetKey; assoc["targetKey_cp"] = capitalizeString(association.targetKey); + if (association.sourceKey) { + assoc["sourceKey"] = association.sourceKey; + assoc["sourceKey_cp"] = capitalizeString(association.sourceKey); + } assoc["keysIn_lc"] = uncapitalizeString(association.keysIn); assoc["holdsForeignKey"] = false; assoc["assocThroughArray"] = false; @@ -852,19 +856,22 @@ module.exports.parseAssociations = function (dataModel) { // schema attrtibutes associations_info.schema_attributes["one"][name] = schema_attributes; - // holds foreignKey ? - if (association.keysIn === dataModel.model) { + if (association.sourceKey) { + assoc["assocThroughArray"] = true; + } else if (association.keysIn === dataModel.model) { assoc["holdsForeignKey"] = true; } associations_info["to_one"].push(assoc); break; case "many_to_many": assoc["assocThroughArray"] = true; - assoc["sourceKey"] = association.sourceKey; case "one_to_many": associations_info.schema_attributes["many"][name] = schema_attributes; associations_info["to_many"].push(assoc); + if (association.sourceKey) { + assoc["assocThroughArray"] = true; + } break; default: break; diff --git a/test/integration_test_misc/integration_test_helpers.js b/test/integration_test_misc/integration_test_helpers.js index 22122f1a..26eaf1e2 100644 --- a/test/integration_test_misc/integration_test_helpers.js +++ b/test/integration_test_misc/integration_test_helpers.js @@ -175,3 +175,16 @@ module.exports.count_all_records = async function (count_func) { let res = await module.exports.request_graph_ql_post(`{ ${count_func} }`); return JSON.parse(res.body.toString("utf8")).data[count_func]; }; + +/** + * count_all_records - Count all records using given GraphQL query + * + * @param {count_func} {string} GraphQL count function name for the given table, e.g. 'countIndividuals' + * @return {integer} Number of the records in a given table + */ +module.exports.count_all_records_instance2 = async function (count_func) { + let res = await module.exports.request_graph_ql_post_instance2( + `{ ${count_func} }` + ); + return JSON.parse(res.body.toString("utf8")).data[count_func]; +}; diff --git a/test/integration_test_misc/integration_test_models_instance1/bank.json b/test/integration_test_misc/integration_test_models_instance1/bank.json new file mode 100644 index 00000000..c140d3cf --- /dev/null +++ b/test/integration_test_misc/integration_test_models_instance1/bank.json @@ -0,0 +1,38 @@ +{ + "model": "bank", + "storageType": "cassandra", + "attributes": { + "bank_id": "String", + "foundation_year": "Int", + "district_id": "String", + "founder_id": "String" + }, + "associations": { + "district": { + "type": "many_to_one", + "implementation": "foreignkeys", + "target": "district", + "targetKey": "bank_ids", + "sourceKey": "district_id", + "keysIn": "bank", + "targetStorageType": "cassandra", + "deletion":"update" + }, + "unique_founder": { + "type": "one_to_one", + "implementation": "foreignkeys", + "target": "founder", + "targetKey": "bank_id", + "sourceKey": "founder_id", + "keysIn": "bank", + "targetStorageType": "cassandra", + "deletion":"update" + } + }, + "internalId": "bank_id", + "id": { + "name": "bank_id", + "type": "String" + }, + "useDataLoader": true +} \ No newline at end of file diff --git a/test/integration_test_misc/integration_test_models_instance1/dist_bank.json b/test/integration_test_misc/integration_test_models_instance1/dist_bank.json new file mode 100644 index 00000000..cbcaab26 --- /dev/null +++ b/test/integration_test_misc/integration_test_models_instance1/dist_bank.json @@ -0,0 +1,39 @@ +{ + "model": "dist_bank", + "storageType": "distributed-data-model", + "registry": ["dist_bank_instance1"], + "attributes": { + "bank_id": "String", + "foundation_year": "Int", + "district_id": "String", + "founder_id": "String" + }, + "associations": { + "dist_district": { + "type": "many_to_one", + "implementation": "foreignkeys", + "target": "dist_district", + "targetKey": "bank_ids", + "sourceKey": "district_id", + "keysIn": "dist_bank", + "targetStorageType": "distributed-data-model", + "deletion":"update" + }, + "dist_unique_founder": { + "type": "one_to_one", + "implementation": "foreignkeys", + "target": "dist_founder", + "targetKey": "bank_id", + "sourceKey": "founder_id", + "keysIn": "dist_bank", + "targetStorageType": "distributed-data-model", + "deletion":"update" + } + }, + "internalId": "bank_id", + "id": { + "name": "bank_id", + "type": "String" + }, + "useDataLoader": true +} \ No newline at end of file diff --git a/test/integration_test_misc/integration_test_models_instance1/dist_bank_instance1.json b/test/integration_test_misc/integration_test_models_instance1/dist_bank_instance1.json new file mode 100644 index 00000000..0c8f8943 --- /dev/null +++ b/test/integration_test_misc/integration_test_models_instance1/dist_bank_instance1.json @@ -0,0 +1,40 @@ +{ + "model": "dist_bank", + "storageType": "cassandra-adapter", + "adapterName": "dist_bank_instance1", + "regex": "instance1", + "attributes": { + "bank_id": "String", + "foundation_year": "Int", + "district_id": "String", + "founder_id": "String" + }, + "associations": { + "dist_district": { + "type": "many_to_one", + "implementation": "foreignkeys", + "target": "dist_district", + "targetKey": "bank_ids", + "sourceKey": "district_id", + "keysIn": "dist_bank", + "targetStorageType": "distributed-data-model", + "deletion":"update" + }, + "dist_unique_founder": { + "type": "one_to_one", + "implementation": "foreignkeys", + "target": "dist_founder", + "targetKey": "bank_id", + "sourceKey": "founder_id", + "keysIn": "dist_bank", + "targetStorageType": "distributed-data-model", + "deletion":"update" + } + }, + "internalId": "bank_id", + "id": { + "name": "bank_id", + "type": "String" + }, + "useDataLoader": true +} \ No newline at end of file diff --git a/test/integration_test_misc/integration_test_models_instance1/dist_district.json b/test/integration_test_misc/integration_test_models_instance1/dist_district.json new file mode 100644 index 00000000..ea07d1b3 --- /dev/null +++ b/test/integration_test_misc/integration_test_models_instance1/dist_district.json @@ -0,0 +1,28 @@ +{ + "model": "dist_district", + "storageType": "distributed-data-model", + "registry": ["dist_district_instance1"], + "attributes": { + "district_id": "String", + "district_name": "String", + "bank_ids": "[String]" + }, + "associations": { + "dist_banks": { + "type": "one_to_many", + "implementation": "foreignkeys", + "target": "dist_bank", + "targetKey": "district_id", + "sourceKey": "bank_ids", + "keysIn": "dist_district", + "targetStorageType": "distributed-data-model", + "deletion":"update" + } + }, + "internalId": "district_id", + "id": { + "name": "district_id", + "type": "String" + }, + "useDataLoader": false +} \ No newline at end of file diff --git a/test/integration_test_misc/integration_test_models_instance1/dist_district_instance1.json b/test/integration_test_misc/integration_test_models_instance1/dist_district_instance1.json new file mode 100644 index 00000000..c2cfd055 --- /dev/null +++ b/test/integration_test_misc/integration_test_models_instance1/dist_district_instance1.json @@ -0,0 +1,29 @@ +{ + "model": "dist_district", + "storageType": "cassandra-adapter", + "adapterName": "dist_district_instance1", + "regex": "instance1", + "attributes": { + "district_id": "String", + "district_name": "String", + "bank_ids": "[String]" + }, + "associations": { + "dist_banks": { + "type": "one_to_many", + "implementation": "foreignkeys", + "target": "dist_bank", + "targetKey": "district_id", + "sourceKey": "bank_ids", + "keysIn": "dist_district", + "targetStorageType": "distributed-data-model", + "deletion":"update" + } + }, + "internalId": "district_id", + "id": { + "name": "district_id", + "type": "String" + }, + "useDataLoader": false +} \ No newline at end of file diff --git a/test/integration_test_misc/integration_test_models_instance1/dist_field.json b/test/integration_test_misc/integration_test_models_instance1/dist_field.json new file mode 100644 index 00000000..dc1e591c --- /dev/null +++ b/test/integration_test_misc/integration_test_models_instance1/dist_field.json @@ -0,0 +1,29 @@ +{ + "model": "dist_field", + "storageType": "distributed-data-model", + "registry": ["dist_field_instance1"], + "attributes": { + "field_id": "String", + "field_name": "String", + "owner": "String", + "plant_ids": "[String]" + }, + "associations": { + "dist_plants": { + "type": "one_to_many", + "implementation": "foreignkeys", + "target": "dist_plant", + "targetKey": "field_id", + "sourceKey": "plant_ids", + "keysIn": "dist_field", + "targetStorageType": "distributed-data-model", + "deletion":"update" + } + }, + "internalId": "field_id", + "id": { + "name": "field_id", + "type": "String" + }, + "useDataLoader": false +} \ No newline at end of file diff --git a/test/integration_test_misc/integration_test_models_instance1/dist_field_instance1.json b/test/integration_test_misc/integration_test_models_instance1/dist_field_instance1.json new file mode 100644 index 00000000..f4b5deeb --- /dev/null +++ b/test/integration_test_misc/integration_test_models_instance1/dist_field_instance1.json @@ -0,0 +1,30 @@ +{ + "model": "dist_field", + "storageType": "mongodb-adapter", + "adapterName": "dist_field_instance1", + "regex": "instance1", + "attributes": { + "field_id": "String", + "field_name": "String", + "owner": "String", + "plant_ids": "[String]" + }, + "associations": { + "dist_plants": { + "type": "one_to_many", + "implementation": "foreignkeys", + "target": "dist_plant", + "targetKey": "field_id", + "sourceKey": "plant_ids", + "keysIn": "dist_field", + "targetStorageType": "distributed-data-model", + "deletion":"update" + } + }, + "internalId": "field_id", + "id": { + "name": "field_id", + "type": "String" + }, + "useDataLoader": false +} \ No newline at end of file diff --git a/test/integration_test_misc/integration_test_models_instance1/dist_founder.json b/test/integration_test_misc/integration_test_models_instance1/dist_founder.json new file mode 100644 index 00000000..e7ce2d99 --- /dev/null +++ b/test/integration_test_misc/integration_test_models_instance1/dist_founder.json @@ -0,0 +1,27 @@ +{ + "model": "dist_founder", + "storageType": "distributed-data-model", + "registry": ["dist_founder_instance1"], + "attributes": { + "founder_id": "String", + "bank_id": "String", + "name": "String" + }, + "associations": { + "dist_unique_bank": { + "type": "one_to_one", + "implementation": "foreignkeys", + "target": "dist_bank", + "targetKey": "founder_id", + "sourceKey": "bank_id", + "keysIn": "dist_founder", + "targetStorageType": "distributed-data-model", + "deletion":"update" + } + }, + "internalId": "founder_id", + "id": { + "name": "founder_id", + "type": "String" + } +} \ No newline at end of file diff --git a/test/integration_test_misc/integration_test_models_instance1/dist_founder_instance1.json b/test/integration_test_misc/integration_test_models_instance1/dist_founder_instance1.json new file mode 100644 index 00000000..3a8f49f6 --- /dev/null +++ b/test/integration_test_misc/integration_test_models_instance1/dist_founder_instance1.json @@ -0,0 +1,28 @@ +{ + "model": "dist_founder", + "storageType": "cassandra-adapter", + "adapterName": "dist_founder_instance1", + "regex": "instance1", + "attributes": { + "founder_id": "String", + "bank_id": "String", + "name": "String" + }, + "associations": { + "dist_unique_bank": { + "type": "one_to_one", + "implementation": "foreignkeys", + "target": "dist_bank", + "targetKey": "founder_id", + "sourceKey": "bank_id", + "keysIn": "dist_founder", + "targetStorageType": "distributed-data-model", + "deletion":"update" + } + }, + "internalId": "founder_id", + "id": { + "name": "founder_id", + "type": "String" + } +} \ No newline at end of file diff --git a/test/integration_test_misc/integration_test_models_instance1/dist_plant.json b/test/integration_test_misc/integration_test_models_instance1/dist_plant.json new file mode 100644 index 00000000..d606fe2c --- /dev/null +++ b/test/integration_test_misc/integration_test_models_instance1/dist_plant.json @@ -0,0 +1,42 @@ +{ + "model": "dist_plant", + "storageType": "distributed-data-model", + "registry": ["dist_plant_instance1"], + "attributes": { + "plant_id": "String", + "category": "String", + "plant_name": "String", + "age": "Int", + "weight": "Float", + "field_id": "String", + "spot_id": "String" + }, + "associations": { + "dist_field": { + "type": "many_to_one", + "implementation": "foreignkeys", + "target": "dist_field", + "targetKey": "plant_ids", + "sourceKey": "field_id", + "keysIn": "dist_plant", + "targetStorageType": "distributed-data-model", + "deletion":"update" + }, + "dist_unique_spot": { + "type": "one_to_one", + "implementation": "foreignkeys", + "target": "dist_spot", + "targetKey": "plant_id", + "sourceKey": "spot_id", + "keysIn": "dist_plant", + "targetStorageType": "distributed-data-model", + "deletion":"update" + } + }, + "internalId": "plant_id", + "id": { + "name": "plant_id", + "type": "String" + }, + "useDataLoader": true +} \ No newline at end of file diff --git a/test/integration_test_misc/integration_test_models_instance1/dist_plant_instance1.json b/test/integration_test_misc/integration_test_models_instance1/dist_plant_instance1.json new file mode 100644 index 00000000..7c44b947 --- /dev/null +++ b/test/integration_test_misc/integration_test_models_instance1/dist_plant_instance1.json @@ -0,0 +1,43 @@ +{ + "model": "dist_plant", + "storageType": "mongodb-adapter", + "adapterName": "dist_plant_instance1", + "regex": "instance1", + "attributes": { + "plant_id": "String", + "category": "String", + "plant_name": "String", + "age": "Int", + "weight": "Float", + "field_id": "String", + "spot_id": "String" + }, + "associations": { + "dist_field": { + "type": "many_to_one", + "implementation": "foreignkeys", + "target": "dist_field", + "targetKey": "plant_ids", + "sourceKey": "field_id", + "keysIn": "dist_plant", + "targetStorageType": "distributed-data-model", + "deletion":"update" + }, + "dist_unique_spot": { + "type": "one_to_one", + "implementation": "foreignkeys", + "target": "dist_spot", + "targetKey": "plant_id", + "sourceKey": "spot_id", + "keysIn": "dist_plant", + "targetStorageType": "distributed-data-model", + "deletion":"update" + } + }, + "internalId": "plant_id", + "id": { + "name": "plant_id", + "type": "String" + }, + "useDataLoader": true +} \ No newline at end of file diff --git a/test/integration_test_misc/integration_test_models_instance1/dist_spot.json b/test/integration_test_misc/integration_test_models_instance1/dist_spot.json new file mode 100644 index 00000000..e8fb41b7 --- /dev/null +++ b/test/integration_test_misc/integration_test_models_instance1/dist_spot.json @@ -0,0 +1,27 @@ +{ + "model": "dist_spot", + "storageType": "distributed-data-model", + "registry": ["dist_spot_instance1"], + "attributes": { + "spot_id": "String", + "plant_id": "String", + "location": "String" + }, + "associations": { + "dist_unique_plant": { + "type": "one_to_one", + "implementation": "foreignkeys", + "target": "dist_plant", + "targetKey": "spot_id", + "sourceKey": "plant_id", + "keysIn": "dist_spot", + "targetStorageType": "distributed-data-model", + "deletion":"update" + } + }, + "internalId": "spot_id", + "id": { + "name": "spot_id", + "type": "String" + } +} \ No newline at end of file diff --git a/test/integration_test_misc/integration_test_models_instance1/dist_spot_instance1.json b/test/integration_test_misc/integration_test_models_instance1/dist_spot_instance1.json new file mode 100644 index 00000000..40ec7fe9 --- /dev/null +++ b/test/integration_test_misc/integration_test_models_instance1/dist_spot_instance1.json @@ -0,0 +1,28 @@ +{ + "model": "dist_spot", + "storageType": "mongodb-adapter", + "adapterName": "dist_spot_instance1", + "regex": "instance1", + "attributes": { + "spot_id": "String", + "plant_id": "String", + "location": "String" + }, + "associations": { + "dist_unique_plant": { + "type": "one_to_one", + "implementation": "foreignkeys", + "target": "dist_plant", + "targetKey": "spot_id", + "sourceKey": "plant_id", + "keysIn": "dist_spot", + "targetStorageType": "distributed-data-model", + "deletion":"update" + } + }, + "internalId": "spot_id", + "id": { + "name": "spot_id", + "type": "String" + } +} \ No newline at end of file diff --git a/test/integration_test_misc/integration_test_models_instance1/district.json b/test/integration_test_misc/integration_test_models_instance1/district.json new file mode 100644 index 00000000..8cf0a706 --- /dev/null +++ b/test/integration_test_misc/integration_test_models_instance1/district.json @@ -0,0 +1,27 @@ +{ + "model": "district", + "storageType": "cassandra", + "attributes": { + "district_id": "String", + "district_name": "String", + "bank_ids": "[String]" + }, + "associations": { + "banks": { + "type": "one_to_many", + "implementation": "foreignkeys", + "target": "bank", + "targetKey": "district_id", + "sourceKey": "bank_ids", + "keysIn": "district", + "targetStorageType": "cassandra", + "deletion":"update" + } + }, + "internalId": "district_id", + "id": { + "name": "district_id", + "type": "String" + }, + "useDataLoader": true +} \ No newline at end of file diff --git a/test/integration_test_misc/integration_test_models_instance1/farm.json b/test/integration_test_misc/integration_test_models_instance1/farm.json index caa5a6eb..d33b3fed 100644 --- a/test/integration_test_misc/integration_test_models_instance1/farm.json +++ b/test/integration_test_misc/integration_test_models_instance1/farm.json @@ -14,7 +14,6 @@ "targetKey": "farm_id", "keysIn": "animal", "targetStorageType": "mongodb", - "label": "animal_id", "deletion":"update" } }, diff --git a/test/integration_test_misc/integration_test_models_instance1/field.json b/test/integration_test_misc/integration_test_models_instance1/field.json new file mode 100644 index 00000000..b40a97f3 --- /dev/null +++ b/test/integration_test_misc/integration_test_models_instance1/field.json @@ -0,0 +1,28 @@ +{ + "model": "field", + "storageType": "mongodb", + "attributes": { + "field_id": "String", + "field_name": "String", + "owner": "String", + "plant_ids": "[String]" + }, + "associations": { + "plants": { + "type": "one_to_many", + "implementation": "foreignkeys", + "target": "plant", + "targetKey": "field_id", + "sourceKey": "plant_ids", + "keysIn": "field", + "targetStorageType": "mongodb", + "deletion":"update" + } + }, + "internalId": "field_id", + "id": { + "name": "field_id", + "type": "String" + }, + "useDataLoader": false +} \ No newline at end of file diff --git a/test/integration_test_misc/integration_test_models_instance1/founder.json b/test/integration_test_misc/integration_test_models_instance1/founder.json new file mode 100644 index 00000000..e084455e --- /dev/null +++ b/test/integration_test_misc/integration_test_models_instance1/founder.json @@ -0,0 +1,26 @@ +{ + "model": "founder", + "storageType": "cassandra", + "attributes": { + "founder_id": "String", + "bank_id": "String", + "name": "String" + }, + "associations": { + "unique_bank": { + "type": "one_to_one", + "implementation": "foreignkeys", + "target": "bank", + "targetKey": "founder_id", + "sourceKey": "bank_id", + "keysIn": "founder", + "targetStorageType": "cassandra", + "deletion":"update" + } + }, + "internalId": "founder_id", + "id": { + "name": "founder_id", + "type": "String" + } +} \ No newline at end of file diff --git a/test/integration_test_misc/integration_test_models_instance1/plant.json b/test/integration_test_misc/integration_test_models_instance1/plant.json new file mode 100644 index 00000000..e470a94e --- /dev/null +++ b/test/integration_test_misc/integration_test_models_instance1/plant.json @@ -0,0 +1,41 @@ +{ + "model": "plant", + "storageType": "mongodb", + "attributes": { + "plant_id": "String", + "category": "String", + "plant_name": "String", + "age": "Int", + "weight": "Float", + "field_id": "String", + "spot_id": "String" + }, + "associations": { + "field": { + "type": "many_to_one", + "implementation": "foreignkeys", + "target": "field", + "targetKey": "plant_ids", + "sourceKey": "field_id", + "keysIn": "plant", + "targetStorageType": "mongodb", + "deletion":"update" + }, + "unique_spot": { + "type": "one_to_one", + "implementation": "foreignkeys", + "target": "spot", + "targetKey": "plant_id", + "sourceKey": "spot_id", + "keysIn": "plant", + "targetStorageType": "mongodb", + "deletion":"update" + } + }, + "internalId": "plant_id", + "id": { + "name": "plant_id", + "type": "String" + }, + "useDataLoader": true +} \ No newline at end of file diff --git a/test/integration_test_misc/integration_test_models_instance1/spot.json b/test/integration_test_misc/integration_test_models_instance1/spot.json new file mode 100644 index 00000000..583498b6 --- /dev/null +++ b/test/integration_test_misc/integration_test_models_instance1/spot.json @@ -0,0 +1,26 @@ +{ + "model": "spot", + "storageType": "mongodb", + "attributes": { + "spot_id": "String", + "plant_id": "String", + "location": "String" + }, + "associations": { + "unique_plant": { + "type": "one_to_one", + "implementation": "foreignkeys", + "target": "plant", + "targetKey": "spot_id", + "sourceKey": "plant_id", + "keysIn": "spot", + "targetStorageType": "mongodb", + "deletion":"update" + } + }, + "internalId": "spot_id", + "id": { + "name": "spot_id", + "type": "String" + } +} \ No newline at end of file diff --git a/test/integration_test_misc/integration_test_models_instance2/area.json b/test/integration_test_misc/integration_test_models_instance2/area.json new file mode 100644 index 00000000..c882734c --- /dev/null +++ b/test/integration_test_misc/integration_test_models_instance2/area.json @@ -0,0 +1,27 @@ +{ + "model": "area", + "storageType": "sql", + "attributes": { + "area_id": "String", + "area_name": "String", + "hospital_ids": "[String]" + }, + "associations": { + "hospitals": { + "type": "one_to_many", + "implementation": "foreignkeys", + "target": "hospital", + "targetKey": "area_id", + "sourceKey": "hospital_ids", + "keysIn": "area", + "targetStorageType": "sql", + "deletion":"update" + } + }, + "internalId": "area_id", + "id": { + "name": "area_id", + "type": "String" + }, + "useDataLoader": true +} \ No newline at end of file diff --git a/test/integration_test_misc/integration_test_models_instance2/dist_area.json b/test/integration_test_misc/integration_test_models_instance2/dist_area.json new file mode 100644 index 00000000..4f162d25 --- /dev/null +++ b/test/integration_test_misc/integration_test_models_instance2/dist_area.json @@ -0,0 +1,28 @@ +{ + "model": "dist_area", + "storageType": "distributed-data-model", + "registry": ["dist_area_instance1"], + "attributes": { + "area_id": "String", + "area_name": "String", + "hospital_ids": "[String]" + }, + "associations": { + "dist_hospitals": { + "type": "one_to_many", + "implementation": "foreignkeys", + "target": "dist_hospital", + "targetKey": "area_id", + "sourceKey": "hospital_ids", + "keysIn": "dist_area", + "targetStorageType": "distributed-data-model", + "deletion":"update" + } + }, + "internalId": "area_id", + "id": { + "name": "area_id", + "type": "String" + }, + "useDataLoader": true +} \ No newline at end of file diff --git a/test/integration_test_misc/integration_test_models_instance2/dist_area_instance1.json b/test/integration_test_misc/integration_test_models_instance2/dist_area_instance1.json new file mode 100644 index 00000000..924b0279 --- /dev/null +++ b/test/integration_test_misc/integration_test_models_instance2/dist_area_instance1.json @@ -0,0 +1,29 @@ +{ + "model": "dist_area", + "storageType": "sql-adapter", + "adapterName": "dist_area_instance1", + "regex": "instance1", + "attributes": { + "area_id": "String", + "area_name": "String", + "hospital_ids": "[String]" + }, + "associations": { + "dist_hospitals": { + "type": "one_to_many", + "implementation": "foreignkeys", + "target": "dist_hospital", + "targetKey": "area_id", + "sourceKey": "hospital_ids", + "keysIn": "dist_area", + "targetStorageType": "distributed-data-model", + "deletion":"update" + } + }, + "internalId": "area_id", + "id": { + "name": "area_id", + "type": "String" + }, + "useDataLoader": true +} \ No newline at end of file diff --git a/test/integration_test_misc/integration_test_models_instance2/dist_hospital.json b/test/integration_test_misc/integration_test_models_instance2/dist_hospital.json new file mode 100644 index 00000000..86697ead --- /dev/null +++ b/test/integration_test_misc/integration_test_models_instance2/dist_hospital.json @@ -0,0 +1,39 @@ +{ + "model": "dist_hospital", + "storageType": "distributed-data-model", + "registry": ["dist_hospital_instance1"], + "attributes": { + "hospital_id": "String", + "construction_year": "Int", + "area_id": "String", + "leader_id": "String" + }, + "associations": { + "dist_area": { + "type": "many_to_one", + "implementation": "foreignkeys", + "target": "dist_area", + "targetKey": "hospital_ids", + "sourceKey": "area_id", + "keysIn": "dist_hospital", + "targetStorageType": "distributed-data-model", + "deletion":"update" + }, + "dist_unique_leader": { + "type": "one_to_one", + "implementation": "foreignkeys", + "target": "dist_leader", + "targetKey": "hospital_id", + "sourceKey": "leader_id", + "keysIn": "dist_hospital", + "targetStorageType": "distributed-data-model", + "deletion":"update" + } + }, + "internalId": "hospital_id", + "id": { + "name": "hospital_id", + "type": "String" + }, + "useDataLoader": true +} \ No newline at end of file diff --git a/test/integration_test_misc/integration_test_models_instance2/dist_hospital_instance1.json b/test/integration_test_misc/integration_test_models_instance2/dist_hospital_instance1.json new file mode 100644 index 00000000..31d8c0d4 --- /dev/null +++ b/test/integration_test_misc/integration_test_models_instance2/dist_hospital_instance1.json @@ -0,0 +1,40 @@ +{ + "model": "dist_hospital", + "storageType": "sql-adapter", + "adapterName": "dist_hospital_instance1", + "regex": "instance1", + "attributes": { + "hospital_id": "String", + "construction_year": "Int", + "area_id": "String", + "leader_id": "String" + }, + "associations": { + "dist_area": { + "type": "many_to_one", + "implementation": "foreignkeys", + "target": "dist_area", + "targetKey": "hospital_ids", + "sourceKey": "area_id", + "keysIn": "dist_hospital", + "targetStorageType": "distributed-data-model", + "deletion":"update" + }, + "dist_unique_leader": { + "type": "one_to_one", + "implementation": "foreignkeys", + "target": "dist_leader", + "targetKey": "hospital_id", + "sourceKey": "leader_id", + "keysIn": "dist_hospital", + "targetStorageType": "distributed-data-model", + "deletion":"update" + } + }, + "internalId": "hospital_id", + "id": { + "name": "hospital_id", + "type": "String" + }, + "useDataLoader": true +} \ No newline at end of file diff --git a/test/integration_test_misc/integration_test_models_instance2/dist_house.json b/test/integration_test_misc/integration_test_models_instance2/dist_house.json new file mode 100644 index 00000000..38562f9e --- /dev/null +++ b/test/integration_test_misc/integration_test_models_instance2/dist_house.json @@ -0,0 +1,39 @@ +{ + "model": "dist_house", + "storageType": "distributed-data-model", + "registry": ["dist_house_instance1"], + "attributes": { + "house_id": "String", + "construction_year": "Int", + "street_id": "String", + "owner_id": "String" + }, + "associations": { + "dist_street": { + "type": "many_to_one", + "implementation": "foreignkeys", + "target": "dist_street", + "targetKey": "house_ids", + "sourceKey": "street_id", + "keysIn": "dist_house", + "targetStorageType": "distributed-data-model", + "deletion":"update" + }, + "dist_unique_owner": { + "type": "one_to_one", + "implementation": "foreignkeys", + "target": "dist_owner", + "targetKey": "house_id", + "sourceKey": "owner_id", + "keysIn": "dist_house", + "targetStorageType": "distributed-data-model", + "deletion":"update" + } + }, + "internalId": "house_id", + "id": { + "name": "house_id", + "type": "String" + }, + "useDataLoader": true +} \ No newline at end of file diff --git a/test/integration_test_misc/integration_test_models_instance2/dist_house_instance1.json b/test/integration_test_misc/integration_test_models_instance2/dist_house_instance1.json new file mode 100644 index 00000000..fc4cbe90 --- /dev/null +++ b/test/integration_test_misc/integration_test_models_instance2/dist_house_instance1.json @@ -0,0 +1,40 @@ +{ + "model": "dist_house", + "storageType": "neo4j-adapter", + "adapterName": "dist_house_instance1", + "regex": "instance1", + "attributes": { + "house_id": "String", + "construction_year": "Int", + "street_id": "String", + "owner_id": "String" + }, + "associations": { + "dist_street": { + "type": "many_to_one", + "implementation": "foreignkeys", + "target": "dist_street", + "targetKey": "house_ids", + "sourceKey": "street_id", + "keysIn": "dist_house", + "targetStorageType": "distributed-data-model", + "deletion":"update" + }, + "dist_unique_owner": { + "type": "one_to_one", + "implementation": "foreignkeys", + "target": "dist_owner", + "targetKey": "house_id", + "sourceKey": "owner_id", + "keysIn": "dist_house", + "targetStorageType": "distributed-data-model", + "deletion":"update" + } + }, + "internalId": "house_id", + "id": { + "name": "house_id", + "type": "String" + }, + "useDataLoader": true +} \ No newline at end of file diff --git a/test/integration_test_misc/integration_test_models_instance2/dist_leader.json b/test/integration_test_misc/integration_test_models_instance2/dist_leader.json new file mode 100644 index 00000000..ee10607a --- /dev/null +++ b/test/integration_test_misc/integration_test_models_instance2/dist_leader.json @@ -0,0 +1,27 @@ +{ + "model": "dist_leader", + "storageType": "distributed-data-model", + "registry": ["dist_leader_instance1"], + "attributes": { + "leader_id": "String", + "hospital_id": "String", + "name": "String" + }, + "associations": { + "dist_unique_hospital": { + "type": "one_to_one", + "implementation": "foreignkeys", + "target": "dist_hospital", + "targetKey": "leader_id", + "sourceKey": "hospital_id", + "keysIn": "dist_leader", + "targetStorageType": "distributed-data-model", + "deletion":"update" + } + }, + "internalId": "leader_id", + "id": { + "name": "leader_id", + "type": "String" + } +} \ No newline at end of file diff --git a/test/integration_test_misc/integration_test_models_instance2/dist_leader_instance1.json b/test/integration_test_misc/integration_test_models_instance2/dist_leader_instance1.json new file mode 100644 index 00000000..bebf3e70 --- /dev/null +++ b/test/integration_test_misc/integration_test_models_instance2/dist_leader_instance1.json @@ -0,0 +1,28 @@ +{ + "model": "dist_leader", + "storageType": "sql-adapter", + "adapterName": "dist_leader_instance1", + "regex": "instance1", + "attributes": { + "leader_id": "String", + "hospital_id": "String", + "name": "String" + }, + "associations": { + "dist_unique_hospital": { + "type": "one_to_one", + "implementation": "foreignkeys", + "target": "dist_hospital", + "targetKey": "leader_id", + "sourceKey": "hospital_id", + "keysIn": "dist_leader", + "targetStorageType": "distributed-data-model", + "deletion":"update" + } + }, + "internalId": "leader_id", + "id": { + "name": "leader_id", + "type": "String" + } +} \ No newline at end of file diff --git a/test/integration_test_misc/integration_test_models_instance2/dist_owner.json b/test/integration_test_misc/integration_test_models_instance2/dist_owner.json new file mode 100644 index 00000000..eb1c3bc1 --- /dev/null +++ b/test/integration_test_misc/integration_test_models_instance2/dist_owner.json @@ -0,0 +1,27 @@ +{ + "model": "dist_owner", + "storageType": "distributed-data-model", + "registry": ["dist_owner_instance1"], + "attributes": { + "owner_id": "String", + "house_id": "String", + "name": "String" + }, + "associations": { + "dist_unique_house": { + "type": "one_to_one", + "implementation": "foreignkeys", + "target": "dist_house", + "targetKey": "owner_id", + "sourceKey": "house_id", + "keysIn": "dist_owner", + "targetStorageType": "distributed-data-model", + "deletion":"update" + } + }, + "internalId": "owner_id", + "id": { + "name": "owner_id", + "type": "String" + } +} \ No newline at end of file diff --git a/test/integration_test_misc/integration_test_models_instance2/dist_owner_instance1.json b/test/integration_test_misc/integration_test_models_instance2/dist_owner_instance1.json new file mode 100644 index 00000000..6c5c152a --- /dev/null +++ b/test/integration_test_misc/integration_test_models_instance2/dist_owner_instance1.json @@ -0,0 +1,28 @@ +{ + "model": "dist_owner", + "storageType": "neo4j-adapter", + "adapterName": "dist_owner_instance1", + "regex": "instance1", + "attributes": { + "owner_id": "String", + "house_id": "String", + "name": "String" + }, + "associations": { + "dist_unique_house": { + "type": "one_to_one", + "implementation": "foreignkeys", + "target": "dist_house", + "targetKey": "owner_id", + "sourceKey": "house_id", + "keysIn": "dist_owner", + "targetStorageType": "distributed-data-model", + "deletion":"update" + } + }, + "internalId": "owner_id", + "id": { + "name": "owner_id", + "type": "String" + } +} \ No newline at end of file diff --git a/test/integration_test_misc/integration_test_models_instance2/dist_street.json b/test/integration_test_misc/integration_test_models_instance2/dist_street.json new file mode 100644 index 00000000..2d11c59c --- /dev/null +++ b/test/integration_test_misc/integration_test_models_instance2/dist_street.json @@ -0,0 +1,28 @@ +{ + "model": "dist_street", + "storageType": "distributed-data-model", + "registry": ["dist_street_instance1"], + "attributes": { + "street_id": "String", + "street_name": "String", + "house_ids": "[String]" + }, + "associations": { + "dist_houses": { + "type": "one_to_many", + "implementation": "foreignkeys", + "target": "dist_house", + "targetKey": "street_id", + "sourceKey": "house_ids", + "keysIn": "dist_street", + "targetStorageType": "distributed-data-model", + "deletion":"update" + } + }, + "internalId": "street_id", + "id": { + "name": "street_id", + "type": "String" + }, + "useDataLoader": false +} \ No newline at end of file diff --git a/test/integration_test_misc/integration_test_models_instance2/dist_street_instance1.json b/test/integration_test_misc/integration_test_models_instance2/dist_street_instance1.json new file mode 100644 index 00000000..1337e709 --- /dev/null +++ b/test/integration_test_misc/integration_test_models_instance2/dist_street_instance1.json @@ -0,0 +1,29 @@ +{ + "model": "dist_street", + "storageType": "neo4j-adapter", + "adapterName": "dist_street_instance1", + "regex": "instance1", + "attributes": { + "street_id": "String", + "street_name": "String", + "house_ids": "[String]" + }, + "associations": { + "dist_houses": { + "type": "one_to_many", + "implementation": "foreignkeys", + "target": "dist_house", + "targetKey": "street_id", + "sourceKey": "house_ids", + "keysIn": "dist_street", + "targetStorageType": "distributed-data-model", + "deletion":"update" + } + }, + "internalId": "street_id", + "id": { + "name": "street_id", + "type": "String" + }, + "useDataLoader": false +} \ No newline at end of file diff --git a/test/integration_test_misc/integration_test_models_instance2/hospital.json b/test/integration_test_misc/integration_test_models_instance2/hospital.json new file mode 100644 index 00000000..aef79afd --- /dev/null +++ b/test/integration_test_misc/integration_test_models_instance2/hospital.json @@ -0,0 +1,38 @@ +{ + "model": "hospital", + "storageType": "sql", + "attributes": { + "hospital_id": "String", + "construction_year": "Int", + "area_id": "String", + "leader_id": "String" + }, + "associations": { + "area": { + "type": "many_to_one", + "implementation": "foreignkeys", + "target": "area", + "targetKey": "hospital_ids", + "sourceKey": "area_id", + "keysIn": "hospital", + "targetStorageType": "sql", + "deletion":"update" + }, + "unique_leader": { + "type": "one_to_one", + "implementation": "foreignkeys", + "target": "leader", + "targetKey": "hospital_id", + "sourceKey": "leader_id", + "keysIn": "hospital", + "targetStorageType": "sql", + "deletion":"update" + } + }, + "internalId": "hospital_id", + "id": { + "name": "hospital_id", + "type": "String" + }, + "useDataLoader": true +} \ No newline at end of file diff --git a/test/integration_test_misc/integration_test_models_instance2/house.json b/test/integration_test_misc/integration_test_models_instance2/house.json new file mode 100644 index 00000000..53eada02 --- /dev/null +++ b/test/integration_test_misc/integration_test_models_instance2/house.json @@ -0,0 +1,38 @@ +{ + "model": "house", + "storageType": "neo4j", + "attributes": { + "house_id": "String", + "construction_year": "Int", + "street_id": "String", + "owner_id": "String" + }, + "associations": { + "street": { + "type": "many_to_one", + "implementation": "foreignkeys", + "target": "street", + "targetKey": "house_ids", + "sourceKey": "street_id", + "keysIn": "house", + "targetStorageType": "neo4j", + "deletion":"update" + }, + "unique_owner": { + "type": "one_to_one", + "implementation": "foreignkeys", + "target": "owner", + "targetKey": "house_id", + "sourceKey": "owner_id", + "keysIn": "house", + "targetStorageType": "neo4j", + "deletion":"update" + } + }, + "internalId": "house_id", + "id": { + "name": "house_id", + "type": "String" + }, + "useDataLoader": true +} \ No newline at end of file diff --git a/test/integration_test_misc/integration_test_models_instance2/leader.json b/test/integration_test_misc/integration_test_models_instance2/leader.json new file mode 100644 index 00000000..8f27a498 --- /dev/null +++ b/test/integration_test_misc/integration_test_models_instance2/leader.json @@ -0,0 +1,26 @@ +{ + "model": "leader", + "storageType": "sql", + "attributes": { + "leader_id": "String", + "hospital_id": "String", + "name": "String" + }, + "associations": { + "unique_hospital": { + "type": "one_to_one", + "implementation": "foreignkeys", + "target": "hospital", + "targetKey": "leader_id", + "sourceKey": "hospital_id", + "keysIn": "leader", + "targetStorageType": "sql", + "deletion":"update" + } + }, + "internalId": "leader_id", + "id": { + "name": "leader_id", + "type": "String" + } +} \ No newline at end of file diff --git a/test/integration_test_misc/integration_test_models_instance2/owner.json b/test/integration_test_misc/integration_test_models_instance2/owner.json new file mode 100644 index 00000000..cc941905 --- /dev/null +++ b/test/integration_test_misc/integration_test_models_instance2/owner.json @@ -0,0 +1,26 @@ +{ + "model": "owner", + "storageType": "neo4j", + "attributes": { + "owner_id": "String", + "house_id": "String", + "name": "String" + }, + "associations": { + "unique_house": { + "type": "one_to_one", + "implementation": "foreignkeys", + "target": "house", + "targetKey": "owner_id", + "sourceKey": "house_id", + "keysIn": "owner", + "targetStorageType": "neo4j", + "deletion":"update" + } + }, + "internalId": "owner_id", + "id": { + "name": "owner_id", + "type": "String" + } +} \ No newline at end of file diff --git a/test/integration_test_misc/integration_test_models_instance2/street.json b/test/integration_test_misc/integration_test_models_instance2/street.json new file mode 100644 index 00000000..473a9258 --- /dev/null +++ b/test/integration_test_misc/integration_test_models_instance2/street.json @@ -0,0 +1,27 @@ +{ + "model": "street", + "storageType": "neo4j", + "attributes": { + "street_id": "String", + "street_name": "String", + "house_ids": "[String]" + }, + "associations": { + "houses": { + "type": "one_to_many", + "implementation": "foreignkeys", + "target": "house", + "targetKey": "street_id", + "sourceKey": "house_ids", + "keysIn": "street", + "targetStorageType": "neo4j", + "deletion":"update" + } + }, + "internalId": "street_id", + "id": { + "name": "street_id", + "type": "String" + }, + "useDataLoader": false +} \ No newline at end of file diff --git a/test/mocha_integration.test.js b/test/mocha_integration.test.js index 2e37d410..edd17150 100644 --- a/test/mocha_integration.test.js +++ b/test/mocha_integration.test.js @@ -4301,3 +4301,498 @@ describe("validation API for local setup", () => { }); }); }); + +describe("SQL - Associations for Paired-end Foreign Keys (local)", () => { + // set up the environment + before(async () => { + let res = itHelpers.request_graph_ql_post_instance2( + `mutation{ + n0: addHospital(hospital_id:"h1", construction_year:1997) {hospital_id} + n1: addHospital(hospital_id:"h2", construction_year:1853) {hospital_id} + n2: addHospital(hospital_id:"h3", construction_year:2012) {hospital_id} + }` + ); + + expect(res.statusCode).to.equal(200); + }); + + // clean up records + after(async () => { + // Delete all hospitals + let res = itHelpers.request_graph_ql_post_instance2( + "{ hospitals(pagination:{limit:25}) {hospital_id} }" + ); + let hospitals = JSON.parse(res.body.toString("utf8")).data.hospitals; + + for (let i = 0; i < hospitals.length; i++) { + res = itHelpers.request_graph_ql_post_instance2( + `mutation { deleteHospital (hospital_id: "${hospitals[i].hospital_id}") }` + ); + expect(res.statusCode).to.equal(200); + } + + let cnt = await itHelpers.count_all_records_instance2("countHospitals"); + expect(cnt).to.equal(0); + + // Delete all areas + res = itHelpers.request_graph_ql_post_instance2( + "{ areas(pagination:{limit:25}) {area_id} }" + ); + let areas = JSON.parse(res.body.toString("utf8")).data.areas; + + for (let i = 0; i < areas.length; i++) { + res = itHelpers.request_graph_ql_post_instance2( + `mutation { deleteArea (area_id: "${areas[i].area_id}") }` + ); + expect(res.statusCode).to.equal(200); + } + + cnt = await itHelpers.count_all_records_instance2("countAreas"); + expect(cnt).to.equal(0); + + // Delete all leaders + res = itHelpers.request_graph_ql_post_instance2( + "{ leaders(pagination:{limit:25}) {leader_id} }" + ); + let leader = JSON.parse(res.body.toString("utf8")).data.leaders; + + for (let i = 0; i < leader.length; i++) { + res = itHelpers.request_graph_ql_post_instance2( + `mutation { deleteLeader (leader_id: "${leader[i].leader_id}") }` + ); + expect(res.statusCode).to.equal(200); + } + + cnt = await itHelpers.count_all_records_instance2("countLeaders"); + expect(cnt).to.equal(0); + }); + + it("01. Hospital : Area (n:1) - add hospitals to area", () => { + let res = itHelpers.request_graph_ql_post_instance2( + `mutation{ + addArea( area_id: "a1", area_name: "area A", addHospitals: ["h1", "h2"] ){ + area_name + hospital_ids + hospitalsFilter(pagination:{limit:10}){ + construction_year + } + } + }` + ); + let resBody = JSON.parse(res.body.toString("utf8")); + + expect(res.statusCode).to.equal(200); + expect(resBody).to.deep.equal({ + data: { + addArea: { + hospitalsFilter: [ + { + construction_year: 1997, + }, + { + construction_year: 1853, + }, + ], + area_name: "area A", + hospital_ids: ["h1", "h2"], + }, + }, + }); + }); + it("02. Hospital : Area (n:1) - read one associated hospital", () => { + let res = itHelpers.request_graph_ql_post_instance2(`{ + readOneHospital(hospital_id: "h1"){ + construction_year + area_id + } + }`); + let resBody = JSON.parse(res.body.toString("utf8")); + + expect(res.statusCode).to.equal(200); + expect(resBody).to.deep.equal({ + data: { + readOneHospital: { + construction_year: 1997, + area_id: "a1", + }, + }, + }); + }); + + it("03. Hospital : Area (n:1) - delete the associations in the area record", () => { + let res = itHelpers.request_graph_ql_post_instance2( + `mutation{updateArea(area_id: "a1", removeHospitals: ["h1", "h2"]) { + area_name + hospitalsFilter(pagination:{limit:10}){ + construction_year + } + hospitalsConnection(pagination:{first:5}){ + hospitals{ + hospital_id + } + } + } + }` + ); + + resBody = JSON.parse(res.body.toString("utf8")); + expect(res.statusCode).to.equal(200); + expect(resBody).to.deep.equal({ + data: { + updateArea: { + hospitalsFilter: [], + area_name: "area A", + hospitalsConnection: { + hospitals: [], + }, + }, + }, + }); + }); + + it("04. Hospital : Leader (1:1) - add hospital to leader", () => { + let res = itHelpers.request_graph_ql_post_instance2( + `mutation{ + addLeader(leader_id:"l1", name:"Maximillian", addUnique_hospital:"h3"){ + leader_id + hospital_id + } + }` + ); + let resBody = JSON.parse(res.body.toString("utf8")); + + expect(res.statusCode).to.equal(200); + expect(resBody).to.deep.equal({ + data: { + addLeader: { + leader_id: "l1", + hospital_id: "h3", + }, + }, + }); + }); + + it("05. Hospital : Leader (1:1) - read one associated hospital", () => { + let res = itHelpers.request_graph_ql_post_instance2(` + { + readOneHospital(hospital_id: "h3"){ + construction_year + leader_id + } + }`); + let resBody = JSON.parse(res.body.toString("utf8")); + + expect(res.statusCode).to.equal(200); + expect(resBody).to.deep.equal({ + data: { + readOneHospital: { + construction_year: 2012, + leader_id: "l1", + }, + }, + }); + }); + + it("06. Hospital : Leader (1:1) - update the existing association", () => { + res = itHelpers.request_graph_ql_post_instance2( + `mutation{ + addLeader(leader_id:"l2", name:"Lily", addUnique_hospital:"h3"){ + leader_id + hospital_id + } + }` + ); + resBody = JSON.parse(res.body.toString("utf8")); + expect(res.statusCode).to.equal(200); + expect(resBody).to.deep.equal({ + errors: [ + { + message: "Hint: update 1 existing association!", + locations: "", + }, + ], + data: { addLeader: { leader_id: "l2", hospital_id: "h3" } }, + }); + }); + + it("07. Hospital : Leader (1:1) - delete the associations in the leader record", () => { + let res = itHelpers.request_graph_ql_post_instance2( + `mutation{ + updateLeader(leader_id:"l2", removeUnique_hospital:"h3"){ + leader_id + hospital_id + } + }` + ); + resBody = JSON.parse(res.body.toString("utf8")); + expect(res.statusCode).to.equal(200); + expect(resBody).to.deep.equal({ + data: { + updateLeader: { + leader_id: "l2", + hospital_id: null, + }, + }, + }); + }); +}); + +describe("SQL - Associations for Paired-end Foreign Keys (distributed)", () => { + after(async () => { + // Delete all hospitals + let res = itHelpers.request_graph_ql_post_instance2( + "{ dist_hospitalsConnection(pagination:{first:10}) {edges {node {hospital_id}}}}" + ); + let edges = JSON.parse(res.body.toString("utf8")).data + .dist_hospitalsConnection.edges; + + for (let edge of edges) { + res = itHelpers.request_graph_ql_post_instance2( + `mutation { deleteDist_hospital (hospital_id: "${edge.node.hospital_id}") }` + ); + expect(res.statusCode).to.equal(200); + } + + let cnt = await itHelpers.count_all_records_instance2( + "countDist_hospitals" + ); + expect(cnt).to.equal(0); + + // Delete all areas + res = itHelpers.request_graph_ql_post_instance2( + "{ dist_areasConnection(pagination:{first:10}) {edges {node {area_id}}}}" + ); + edges = JSON.parse(res.body.toString("utf8")).data.dist_areasConnection + .edges; + + for (let edge of edges) { + res = itHelpers.request_graph_ql_post_instance2( + `mutation { deleteDist_area (area_id: "${edge.node.area_id}") }` + ); + expect(res.statusCode).to.equal(200); + } + + cnt = await itHelpers.count_all_records_instance2("countDist_areas"); + expect(cnt).to.equal(0); + + // Delete all leaders + res = itHelpers.request_graph_ql_post_instance2( + "{ dist_leadersConnection(pagination:{first:10}) {edges {node {leader_id}}}}" + ); + edges = JSON.parse(res.body.toString("utf8")).data.dist_leadersConnection + .edges; + + for (let edge of edges) { + res = itHelpers.request_graph_ql_post_instance2( + `mutation { deleteDist_leader (leader_id: "${edge.node.leader_id}") }` + ); + expect(res.statusCode).to.equal(200); + } + + cnt = await itHelpers.count_all_records_instance2("countDist_leaders"); + expect(cnt).to.equal(0); + }); + + it("01. Hospital DDM: create a area and 2 hospitals", () => { + let res = itHelpers.request_graph_ql_post_instance2( + `mutation { + addDist_area(area_id: "instance1-a1", area_name: "area A") { + area_id + area_name + } + }` + ); + let resBody = JSON.parse(res.body.toString("utf8")); + expect(res.statusCode).to.equal(200); + expect(resBody).to.deep.equal({ + data: { + addDist_area: { + area_id: "instance1-a1", + area_name: "area A", + }, + }, + }); + + const year = [1993, 2016, 1982]; + for (let i = 0; i < year.length; i++) { + res = itHelpers.request_graph_ql_post_instance2( + `mutation { + addDist_hospital(hospital_id: "instance1-h${ + i + 1 + }", construction_year: ${year[i]}) + { + hospital_id + construction_year + } + } + ` + ); + resBody = JSON.parse(res.body.toString("utf8")); + expect(res.statusCode).to.equal(200); + + expect(resBody).to.deep.equal({ + data: { + addDist_hospital: { + hospital_id: `instance1-h${i + 1}`, + construction_year: year[i], + }, + }, + }); + } + }); + + it("02. Hospital DDM: update the area to associate with hospitals", () => { + let res = itHelpers.request_graph_ql_post_instance2( + `mutation { + updateDist_area(area_id: "instance1-a1", addDist_hospitals: ["instance1-h1", "instance1-h2"]) { + area_name + countFilteredDist_hospitals + dist_hospitalsConnection(pagination: {first: 5}) { + edges { + node { + construction_year + } + } + dist_hospitals{ + hospital_id + } + } + } + } + ` + ); + let resBody = JSON.parse(res.body.toString("utf8")); + expect(res.statusCode).to.equal(200); + expect(resBody).to.deep.equal({ + data: { + updateDist_area: { + area_name: "area A", + countFilteredDist_hospitals: 2, + dist_hospitalsConnection: { + edges: [ + { + node: { + construction_year: 1993, + }, + }, + { + node: { + construction_year: 2016, + }, + }, + ], + dist_hospitals: [ + { hospital_id: "instance1-h1" }, + { hospital_id: "instance1-h2" }, + ], + }, + }, + }, + }); + }); + + it("03. Hospital DDM: update the area to remove associations", () => { + let res = itHelpers.request_graph_ql_post_instance2( + `mutation { + updateDist_area(area_id:"instance1-a1" removeDist_hospitals:["instance1-h1", "instance1-h2"]) { + area_name + countFilteredDist_hospitals + dist_hospitalsConnection(pagination:{first:5}){ + edges { + node { + construction_year + } + } + dist_hospitals{ + hospital_id + } + } + } + }` + ); + let resBody = JSON.parse(res.body.toString("utf8")); + expect(res.statusCode).to.equal(200); + expect(resBody).to.deep.equal({ + data: { + updateDist_area: { + area_name: "area A", + countFilteredDist_hospitals: 0, + dist_hospitalsConnection: { + edges: [], + dist_hospitals: [], + }, + }, + }, + }); + }); + + it("04. Hospital DDM: add hospital to leader", () => { + let res = itHelpers.request_graph_ql_post_instance2( + `mutation{ + addDist_leader(leader_id:"instance1-l1", name:"Haribo", addDist_unique_hospital:"instance1-h3"){ + leader_id + hospital_id + } + }` + ); + let resBody = JSON.parse(res.body.toString("utf8")); + + expect(res.statusCode).to.equal(200); + expect(resBody).to.deep.equal({ + data: { + addDist_leader: { + leader_id: "instance1-l1", + hospital_id: "instance1-h3", + }, + }, + }); + }); + + it("05. Hospital DDM: update the existing association", () => { + res = itHelpers.request_graph_ql_post_instance2( + `mutation{ + addDist_leader(leader_id:"instance1-l2", name:"Bing", addDist_unique_hospital:"instance1-h3"){ + leader_id + hospital_id + } + }` + ); + resBody = JSON.parse(res.body.toString("utf8")); + expect(res.statusCode).to.equal(200); + expect(resBody).to.deep.equal({ + errors: [ + { + message: "Hint: update 1 existing association!", + locations: "", + }, + ], + data: { + addDist_leader: { + leader_id: "instance1-l2", + hospital_id: "instance1-h3", + }, + }, + }); + }); + + it("06. Hospital DDM: delete the associations in the hospital record", () => { + let res = itHelpers.request_graph_ql_post_instance2( + `mutation{ + updateDist_hospital(hospital_id:"instance1-h3", removeDist_unique_leader:"instance1-l2"){ + leader_id + hospital_id + } + }` + ); + resBody = JSON.parse(res.body.toString("utf8")); + expect(res.statusCode).to.equal(200); + expect(resBody).to.deep.equal({ + data: { + updateDist_hospital: { + hospital_id: "instance1-h3", + leader_id: null, + }, + }, + }); + }); +}); diff --git a/test/mocha_integration_cassandra.test.js b/test/mocha_integration_cassandra.test.js index 43e02a70..f68bd175 100644 --- a/test/mocha_integration_cassandra.test.js +++ b/test/mocha_integration_cassandra.test.js @@ -2034,3 +2034,484 @@ describe("data loader for readById method", () => { }); }); }); + +describe("Cassandra - Associations for Paired-end Foreign Keys (local)", () => { + // set up the environment + before(async () => { + let res = itHelpers.request_graph_ql_post( + `mutation{ + n0: addBank(bank_id:"b1", foundation_year:1997) {bank_id} + n1: addBank(bank_id:"b2", foundation_year:1983) {bank_id} + n2: addBank(bank_id:"b3", foundation_year:2012) {bank_id} + }` + ); + + expect(res.statusCode).to.equal(200); + }); + + // clean up records + after(async () => { + // Delete all banks + let res = itHelpers.request_graph_ql_post( + "{ banksConnection(pagination:{first:25}) {banks{bank_id}} }" + ); + let banks = JSON.parse(res.body.toString("utf8")).data.banksConnection + .banks; + + for (let i = 0; i < banks.length; i++) { + res = itHelpers.request_graph_ql_post( + `mutation { deleteBank (bank_id: "${banks[i].bank_id}") }` + ); + expect(res.statusCode).to.equal(200); + } + + let cnt = await itHelpers.count_all_records("countBanks"); + expect(cnt).to.equal(0); + + // Delete all districts + res = itHelpers.request_graph_ql_post( + "{ districtsConnection(pagination:{first:25}) {districts{district_id}} }" + ); + let districts = JSON.parse(res.body.toString("utf8")).data + .districtsConnection.districts; + + for (let i = 0; i < districts.length; i++) { + res = itHelpers.request_graph_ql_post( + `mutation { deleteDistrict (district_id: "${districts[i].district_id}") }` + ); + expect(res.statusCode).to.equal(200); + } + + cnt = await itHelpers.count_all_records("countDistricts"); + expect(cnt).to.equal(0); + + // Delete all founders + res = itHelpers.request_graph_ql_post( + "{ foundersConnection(pagination:{first:25}) {founders{founder_id}} }" + ); + let founder = JSON.parse(res.body.toString("utf8")).data.foundersConnection + .founders; + + for (let i = 0; i < founder.length; i++) { + res = itHelpers.request_graph_ql_post( + `mutation { deleteFounder (founder_id: "${founder[i].founder_id}") }` + ); + expect(res.statusCode).to.equal(200); + } + + cnt = await itHelpers.count_all_records("countFounders"); + expect(cnt).to.equal(0); + }); + + it("01. Bank : District (n:1) - add banks to district", () => { + let res = itHelpers.request_graph_ql_post( + `mutation{ + addDistrict( district_id: "d1", district_name: "Aachen", addBanks: ["b1", "b2"] ){ + district_name + bank_ids + } + }` + ); + let resBody = JSON.parse(res.body.toString("utf8")); + + expect(res.statusCode).to.equal(200); + expect(resBody).to.deep.equal({ + data: { + addDistrict: { + district_name: "Aachen", + bank_ids: ["b1", "b2"], + }, + }, + }); + }); + it("02. Bank : District (n:1) - read one associated bank", () => { + let res = itHelpers.request_graph_ql_post(`{ + readOneBank(bank_id: "b1"){ + foundation_year + district_id + } + }`); + let resBody = JSON.parse(res.body.toString("utf8")); + + expect(res.statusCode).to.equal(200); + expect(resBody).to.deep.equal({ + data: { + readOneBank: { + foundation_year: 1997, + district_id: "d1", + }, + }, + }); + }); + + it("03. Bank : District (n:1) - delete the associations in the district record", () => { + let res = itHelpers.request_graph_ql_post( + `mutation{updateDistrict(district_id: "d1", removeBanks: ["b1", "b2"]) { + district_name + banksConnection(pagination:{first:5}){ + banks{ + bank_id + } + } + } + }` + ); + + resBody = JSON.parse(res.body.toString("utf8")); + expect(res.statusCode).to.equal(200); + expect(resBody).to.deep.equal({ + data: { + updateDistrict: { + district_name: "Aachen", + banksConnection: { + banks: [], + }, + }, + }, + }); + }); + + it("04. Bank : Founder (1:1) - add bank to founder", () => { + let res = itHelpers.request_graph_ql_post( + `mutation{ + addFounder(founder_id:"f1", name:"Frank", addUnique_bank:"b3"){ + founder_id + bank_id + } + }` + ); + let resBody = JSON.parse(res.body.toString("utf8")); + + expect(res.statusCode).to.equal(200); + expect(resBody).to.deep.equal({ + data: { + addFounder: { + founder_id: "f1", + bank_id: "b3", + }, + }, + }); + }); + + it("05. Bank : Founder (1:1) - read one associated bank", () => { + let res = itHelpers.request_graph_ql_post(` + { + readOneBank(bank_id: "b3"){ + foundation_year + founder_id + } + }`); + let resBody = JSON.parse(res.body.toString("utf8")); + + expect(res.statusCode).to.equal(200); + expect(resBody).to.deep.equal({ + data: { + readOneBank: { + foundation_year: 2012, + founder_id: "f1", + }, + }, + }); + }); + + it("06. Bank : Founder (1:1) - update the existing association", () => { + res = itHelpers.request_graph_ql_post( + `mutation{ + addFounder(founder_id:"f2", name:"Lily", addUnique_bank:"b3"){ + founder_id + bank_id + } + }` + ); + resBody = JSON.parse(res.body.toString("utf8")); + expect(res.statusCode).to.equal(200); + expect(resBody).to.deep.equal({ + errors: [ + { + message: "Hint: update 1 existing association!", + locations: "", + }, + ], + data: { addFounder: { founder_id: "f2", bank_id: "b3" } }, + }); + }); + + it("07. Bank : Founder (1:1) - delete the associations in the founder record", () => { + let res = itHelpers.request_graph_ql_post( + `mutation{ + updateFounder(founder_id:"f2", removeUnique_bank:"b3"){ + founder_id + bank_id + } + }` + ); + resBody = JSON.parse(res.body.toString("utf8")); + expect(res.statusCode).to.equal(200); + expect(resBody).to.deep.equal({ + data: { + updateFounder: { + founder_id: "f2", + bank_id: null, + }, + }, + }); + }); +}); + +describe("Cassandra - Associations for Paired-end Foreign Keys (distributed)", () => { + after(async () => { + // Delete all banks + let res = itHelpers.request_graph_ql_post( + "{ dist_banksConnection(pagination:{first:10}) {edges {node {bank_id}}}}" + ); + let edges = JSON.parse(res.body.toString("utf8")).data.dist_banksConnection + .edges; + + for (let edge of edges) { + res = itHelpers.request_graph_ql_post( + `mutation { deleteDist_bank (bank_id: "${edge.node.bank_id}") }` + ); + expect(res.statusCode).to.equal(200); + } + + let cnt = await itHelpers.count_all_records("countDist_banks"); + expect(cnt).to.equal(0); + + // Delete all districts + res = itHelpers.request_graph_ql_post( + "{ dist_districtsConnection(pagination:{first:10}) {edges {node {district_id}}}}" + ); + edges = JSON.parse(res.body.toString("utf8")).data.dist_districtsConnection + .edges; + + for (let edge of edges) { + res = itHelpers.request_graph_ql_post( + `mutation { deleteDist_district (district_id: "${edge.node.district_id}") }` + ); + expect(res.statusCode).to.equal(200); + } + + cnt = await itHelpers.count_all_records("countDist_districts"); + expect(cnt).to.equal(0); + + // Delete all founders + res = itHelpers.request_graph_ql_post( + "{ dist_foundersConnection(pagination:{first:10}) {edges {node {founder_id}}}}" + ); + edges = JSON.parse(res.body.toString("utf8")).data.dist_foundersConnection + .edges; + + for (let edge of edges) { + res = itHelpers.request_graph_ql_post( + `mutation { deleteDist_founder (founder_id: "${edge.node.founder_id}") }` + ); + expect(res.statusCode).to.equal(200); + } + + cnt = await itHelpers.count_all_records("countDist_founders"); + expect(cnt).to.equal(0); + }); + + it("01. Bank DDM: create a district and 2 banks", () => { + let res = itHelpers.request_graph_ql_post( + `mutation { + addDist_district(district_id: "instance1-d1", district_name: "Aachen") { + district_id + district_name + } + }` + ); + let resBody = JSON.parse(res.body.toString("utf8")); + expect(res.statusCode).to.equal(200); + expect(resBody).to.deep.equal({ + data: { + addDist_district: { + district_id: "instance1-d1", + district_name: "Aachen", + }, + }, + }); + + const year = [1993, 2016, 1982]; + for (let i = 0; i < year.length; i++) { + res = itHelpers.request_graph_ql_post( + `mutation { + addDist_bank(bank_id: "instance1-b${i + 1}", foundation_year: ${ + year[i] + }) + { + bank_id + foundation_year + } + } + ` + ); + resBody = JSON.parse(res.body.toString("utf8")); + expect(res.statusCode).to.equal(200); + + expect(resBody).to.deep.equal({ + data: { + addDist_bank: { + bank_id: `instance1-b${i + 1}`, + foundation_year: year[i], + }, + }, + }); + } + }); + + it("02. Bank DDM: update the district to associate with banks", () => { + let res = itHelpers.request_graph_ql_post( + `mutation { + updateDist_district(district_id: "instance1-d1", addDist_banks: ["instance1-b1", "instance1-b2"]) { + district_name + countFilteredDist_banks + dist_banksConnection(pagination: {first: 5}) { + edges { + node { + foundation_year + } + } + dist_banks{ + bank_id + } + } + } + } + ` + ); + let resBody = JSON.parse(res.body.toString("utf8")); + expect(res.statusCode).to.equal(200); + expect(resBody).to.deep.equal({ + data: { + updateDist_district: { + district_name: "Aachen", + countFilteredDist_banks: 2, + dist_banksConnection: { + edges: [ + { + node: { + foundation_year: 1993, + }, + }, + { + node: { + foundation_year: 2016, + }, + }, + ], + dist_banks: [ + { bank_id: "instance1-b1" }, + { bank_id: "instance1-b2" }, + ], + }, + }, + }, + }); + }); + + it("03. Bank DDM: update the district to remove associations", () => { + let res = itHelpers.request_graph_ql_post( + `mutation { + updateDist_district(district_id:"instance1-d1" removeDist_banks:["instance1-b1", "instance1-b2"]) { + district_name + countFilteredDist_banks + dist_banksConnection(pagination:{first:5}){ + edges { + node { + foundation_year + } + } + dist_banks{ + bank_id + } + } + } + }` + ); + let resBody = JSON.parse(res.body.toString("utf8")); + expect(res.statusCode).to.equal(200); + expect(resBody).to.deep.equal({ + data: { + updateDist_district: { + district_name: "Aachen", + countFilteredDist_banks: 0, + dist_banksConnection: { + edges: [], + dist_banks: [], + }, + }, + }, + }); + }); + + it("04. Bank DDM: add bank to founder", () => { + let res = itHelpers.request_graph_ql_post( + `mutation{ + addDist_founder(founder_id:"instance1-f1", name:"Haribo", addDist_unique_bank:"instance1-b3"){ + founder_id + bank_id + } + }` + ); + let resBody = JSON.parse(res.body.toString("utf8")); + + expect(res.statusCode).to.equal(200); + expect(resBody).to.deep.equal({ + data: { + addDist_founder: { + founder_id: "instance1-f1", + bank_id: "instance1-b3", + }, + }, + }); + }); + + it("05. Bank DDM: update the existing association", () => { + res = itHelpers.request_graph_ql_post( + `mutation{ + addDist_founder(founder_id:"instance1-f2", name:"Bing", addDist_unique_bank:"instance1-b3"){ + founder_id + bank_id + } + }` + ); + resBody = JSON.parse(res.body.toString("utf8")); + expect(res.statusCode).to.equal(200); + expect(resBody).to.deep.equal({ + errors: [ + { + message: "Hint: update 1 existing association!", + locations: "", + }, + ], + data: { + addDist_founder: { + founder_id: "instance1-f2", + bank_id: "instance1-b3", + }, + }, + }); + }); + + it("06. Bank DDM: delete the associations in the bank record", () => { + let res = itHelpers.request_graph_ql_post( + `mutation{ + updateDist_bank(bank_id:"instance1-b3", removeDist_unique_founder:"instance1-f2"){ + founder_id + bank_id + } + }` + ); + resBody = JSON.parse(res.body.toString("utf8")); + expect(res.statusCode).to.equal(200); + expect(resBody).to.deep.equal({ + data: { + updateDist_bank: { + bank_id: "instance1-b3", + founder_id: null, + }, + }, + }); + }); +}); diff --git a/test/mocha_integration_mongodb.test.js b/test/mocha_integration_mongodb.test.js index 1c0eb0df..58ff64c6 100644 --- a/test/mocha_integration_mongodb.test.js +++ b/test/mocha_integration_mongodb.test.js @@ -1873,3 +1873,492 @@ describe("Mongodb - Update Deletion Action", () => { } }); }); + +describe("Mongodb - Associations for Paired-end Foreign Keys (local)", () => { + // set up the environment + before(async () => { + let res = itHelpers.request_graph_ql_post( + `mutation{ + n0: addPlant(plant_id:"p1", plant_name:"Sally", category:"Paeonia suffruticosa", age:1, weight:0.5) {plant_id} + n1: addPlant(plant_id:"p2", plant_name:"Lily", category:"French hydrangea", age:2, weight:1.5) {plant_id} + n2: addPlant(plant_id:"p3", plant_name:"Milka", category:"Houttuynia cordata", age:1, weight:1.0) {plant_id} + }` + ); + + expect(res.statusCode).to.equal(200); + }); + + // clean up records + after(async () => { + // Delete all plants + let res = itHelpers.request_graph_ql_post( + "{ plants(pagination:{limit:25}) {plant_id} }" + ); + let plants = JSON.parse(res.body.toString("utf8")).data.plants; + + for (let i = 0; i < plants.length; i++) { + res = itHelpers.request_graph_ql_post( + `mutation { deletePlant (plant_id: "${plants[i].plant_id}") }` + ); + expect(res.statusCode).to.equal(200); + } + + let cnt = await itHelpers.count_all_records("countPlants"); + expect(cnt).to.equal(0); + + // Delete all fields + res = itHelpers.request_graph_ql_post( + "{ fields(pagination:{limit:25}) {field_id} }" + ); + let fields = JSON.parse(res.body.toString("utf8")).data.fields; + + for (let i = 0; i < fields.length; i++) { + res = itHelpers.request_graph_ql_post( + `mutation { deleteField (field_id: "${fields[i].field_id}") }` + ); + expect(res.statusCode).to.equal(200); + } + + cnt = await itHelpers.count_all_records("countFields"); + expect(cnt).to.equal(0); + + // Delete all spots + res = itHelpers.request_graph_ql_post( + "{ spots(pagination:{limit:25}) {spot_id} }" + ); + let spot = JSON.parse(res.body.toString("utf8")).data.spots; + + for (let i = 0; i < spot.length; i++) { + res = itHelpers.request_graph_ql_post( + `mutation { deleteSpot (spot_id: "${spot[i].spot_id}") }` + ); + expect(res.statusCode).to.equal(200); + } + + cnt = await itHelpers.count_all_records("countSpots"); + expect(cnt).to.equal(0); + }); + + it("01. Plant : Field (n:1) - add plants to field", () => { + let res = itHelpers.request_graph_ql_post( + `mutation{ + addField( field_id: "f1", field_name: "Flowers' Home", addPlants: ["p1", "p2"] ){ + field_name + plant_ids + plantsFilter(pagination:{limit:10}){ + plant_name + } + } + }` + ); + let resBody = JSON.parse(res.body.toString("utf8")); + + expect(res.statusCode).to.equal(200); + expect(resBody).to.deep.equal({ + data: { + addField: { + plantsFilter: [ + { + plant_name: "Sally", + }, + { + plant_name: "Lily", + }, + ], + field_name: "Flowers' Home", + plant_ids: ["p1", "p2"], + }, + }, + }); + }); + it("02. Plant : Field (n:1) - read one associated plant", () => { + let res = itHelpers.request_graph_ql_post(`{ + readOnePlant(plant_id: "p1"){ + plant_name + field_id + } + }`); + let resBody = JSON.parse(res.body.toString("utf8")); + + expect(res.statusCode).to.equal(200); + expect(resBody).to.deep.equal({ + data: { + readOnePlant: { + plant_name: "Sally", + field_id: "f1", + }, + }, + }); + }); + + it("03. Plant : Field (n:1) - delete the associations in the field record", () => { + let res = itHelpers.request_graph_ql_post( + `mutation{updateField(field_id: "f1", removePlants: ["p1", "p2"]) { + field_name + plantsFilter(pagination:{limit:10}){ + plant_name + } + plantsConnection(pagination:{first:5}){ + plants{ + plant_id + } + } + } + }` + ); + + resBody = JSON.parse(res.body.toString("utf8")); + expect(res.statusCode).to.equal(200); + expect(resBody).to.deep.equal({ + data: { + updateField: { + plantsFilter: [], + field_name: "Flowers' Home", + plantsConnection: { + plants: [], + }, + }, + }, + }); + }); + + it("04. Plant : Spot (1:1) - add plant to spot", () => { + let res = itHelpers.request_graph_ql_post( + `mutation{ + addSpot(spot_id:"s1", location:"spot1", addUnique_plant:"p3"){ + spot_id + plant_id + } + }` + ); + let resBody = JSON.parse(res.body.toString("utf8")); + + expect(res.statusCode).to.equal(200); + expect(resBody).to.deep.equal({ + data: { + addSpot: { + spot_id: "s1", + plant_id: "p3", + }, + }, + }); + }); + + it("05. Plant : Spot (1:1) - read one associated plant", () => { + let res = itHelpers.request_graph_ql_post(` + { + readOnePlant(plant_id: "p3"){ + plant_name + spot_id + } + }`); + let resBody = JSON.parse(res.body.toString("utf8")); + + expect(res.statusCode).to.equal(200); + expect(resBody).to.deep.equal({ + data: { + readOnePlant: { + plant_name: "Milka", + spot_id: "s1", + }, + }, + }); + }); + + it("06. Plant : Spot (1:1) - update the existing association", () => { + res = itHelpers.request_graph_ql_post( + `mutation{ + addSpot(spot_id:"s2", location:"spot2", addUnique_plant:"p3"){ + spot_id + plant_id + } + }` + ); + resBody = JSON.parse(res.body.toString("utf8")); + expect(res.statusCode).to.equal(200); + expect(resBody).to.deep.equal({ + errors: [ + { + message: "Hint: update 1 existing association!", + locations: "", + }, + ], + data: { addSpot: { spot_id: "s2", plant_id: "p3" } }, + }); + }); + + it("07. Plant : Spot (1:1) - delete the associations in the spot record", () => { + let res = itHelpers.request_graph_ql_post( + `mutation{ + updateSpot(spot_id:"s2", removeUnique_plant:"p3"){ + spot_id + plant_id + } + }` + ); + resBody = JSON.parse(res.body.toString("utf8")); + expect(res.statusCode).to.equal(200); + expect(resBody).to.deep.equal({ + data: { + updateSpot: { + spot_id: "s2", + plant_id: null, + }, + }, + }); + }); +}); + +describe("Mongodb - Associations for Paired-end Foreign Keys (distributed)", () => { + after(async () => { + // Delete all plants + let res = itHelpers.request_graph_ql_post( + "{ dist_plantsConnection(pagination:{first:10}) {edges {node {plant_id}}}}" + ); + let edges = JSON.parse(res.body.toString("utf8")).data.dist_plantsConnection + .edges; + + for (let edge of edges) { + res = itHelpers.request_graph_ql_post( + `mutation { deleteDist_plant (plant_id: "${edge.node.plant_id}") }` + ); + expect(res.statusCode).to.equal(200); + } + + let cnt = await itHelpers.count_all_records("countDist_plants"); + expect(cnt).to.equal(0); + + // Delete all fields + res = itHelpers.request_graph_ql_post( + "{ dist_fieldsConnection(pagination:{first:10}) {edges {node {field_id}}}}" + ); + edges = JSON.parse(res.body.toString("utf8")).data.dist_fieldsConnection + .edges; + + for (let edge of edges) { + res = itHelpers.request_graph_ql_post( + `mutation { deleteDist_field (field_id: "${edge.node.field_id}") }` + ); + expect(res.statusCode).to.equal(200); + } + + cnt = await itHelpers.count_all_records("countDist_fields"); + expect(cnt).to.equal(0); + + // Delete all spots + res = itHelpers.request_graph_ql_post( + "{ dist_spotsConnection(pagination:{first:10}) {edges {node {spot_id}}}}" + ); + edges = JSON.parse(res.body.toString("utf8")).data.dist_spotsConnection + .edges; + + for (let edge of edges) { + res = itHelpers.request_graph_ql_post( + `mutation { deleteDist_spot (spot_id: "${edge.node.spot_id}") }` + ); + expect(res.statusCode).to.equal(200); + } + + cnt = await itHelpers.count_all_records("countDist_spots"); + expect(cnt).to.equal(0); + }); + + it("01. Plant DDM: create a field and 2 plants", () => { + let res = itHelpers.request_graph_ql_post( + `mutation { + addDist_field(field_id: "instance1-f1", field_name: "Flowers' Home") { + field_id + field_name + } + }` + ); + let resBody = JSON.parse(res.body.toString("utf8")); + expect(res.statusCode).to.equal(200); + expect(resBody).to.deep.equal({ + data: { + addDist_field: { + field_id: "instance1-f1", + field_name: "Flowers' Home", + }, + }, + }); + + const name = ["Milka", "Sally", "Lily"]; + for (let i = 0; i < name.length; i++) { + res = itHelpers.request_graph_ql_post( + `mutation { + addDist_plant(plant_id: "instance1-p${i + 1}", + plant_name: "${name[i]}") + { + plant_id + plant_name + } + } + ` + ); + resBody = JSON.parse(res.body.toString("utf8")); + expect(res.statusCode).to.equal(200); + + expect(resBody).to.deep.equal({ + data: { + addDist_plant: { + plant_id: `instance1-p${i + 1}`, + plant_name: `${name[i]}`, + }, + }, + }); + } + }); + + it("02. Plant DDM: update the field to associate with plants", () => { + let res = itHelpers.request_graph_ql_post( + `mutation { + updateDist_field(field_id: "instance1-f1", addDist_plants: ["instance1-p1", "instance1-p2"]) { + field_name + countFilteredDist_plants + dist_plantsConnection(pagination: {first: 5}) { + edges { + node { + plant_name + } + } + dist_plants{ + plant_id + } + } + } + } + ` + ); + let resBody = JSON.parse(res.body.toString("utf8")); + expect(res.statusCode).to.equal(200); + expect(resBody).to.deep.equal({ + data: { + updateDist_field: { + field_name: "Flowers' Home", + countFilteredDist_plants: 2, + dist_plantsConnection: { + edges: [ + { + node: { + plant_name: "Milka", + }, + }, + { + node: { + plant_name: "Sally", + }, + }, + ], + dist_plants: [ + { plant_id: "instance1-p1" }, + { plant_id: "instance1-p2" }, + ], + }, + }, + }, + }); + }); + + it("03. Plant DDM: update the field to remove associations", () => { + let res = itHelpers.request_graph_ql_post( + `mutation { + updateDist_field(field_id:"instance1-f1" removeDist_plants:["instance1-p1", "instance1-p2"]) { + field_name + countFilteredDist_plants + dist_plantsConnection(pagination:{first:5}){ + edges { + node { + plant_name + } + } + dist_plants{ + plant_id + } + } + } + }` + ); + let resBody = JSON.parse(res.body.toString("utf8")); + expect(res.statusCode).to.equal(200); + expect(resBody).to.deep.equal({ + data: { + updateDist_field: { + field_name: "Flowers' Home", + countFilteredDist_plants: 0, + dist_plantsConnection: { + edges: [], + dist_plants: [], + }, + }, + }, + }); + }); + + it("04. Plant DDM: add plant to spot", () => { + let res = itHelpers.request_graph_ql_post( + `mutation{ + addDist_spot(spot_id:"instance1-s1", location:"spot1", addDist_unique_plant:"instance1-p3"){ + spot_id + plant_id + } + }` + ); + let resBody = JSON.parse(res.body.toString("utf8")); + + expect(res.statusCode).to.equal(200); + expect(resBody).to.deep.equal({ + data: { + addDist_spot: { + spot_id: "instance1-s1", + plant_id: "instance1-p3", + }, + }, + }); + }); + + it("05. Plant DDM: update the existing association", () => { + res = itHelpers.request_graph_ql_post( + `mutation{ + addDist_spot(spot_id:"instance1-s2", location:"spot2", addDist_unique_plant:"instance1-p3"){ + spot_id + plant_id + } + }` + ); + resBody = JSON.parse(res.body.toString("utf8")); + expect(res.statusCode).to.equal(200); + expect(resBody).to.deep.equal({ + errors: [ + { + message: "Hint: update 1 existing association!", + locations: "", + }, + ], + data: { + addDist_spot: { spot_id: "instance1-s2", plant_id: "instance1-p3" }, + }, + }); + }); + + it("06. Plant DDM: delete the associations in the plant record", () => { + let res = itHelpers.request_graph_ql_post( + `mutation{ + updateDist_plant(plant_id:"instance1-p3", removeDist_unique_spot:"instance1-s2"){ + spot_id + plant_id + } + }` + ); + resBody = JSON.parse(res.body.toString("utf8")); + expect(res.statusCode).to.equal(200); + expect(resBody).to.deep.equal({ + data: { + updateDist_plant: { + plant_id: "instance1-p3", + spot_id: null, + }, + }, + }); + }); +}); diff --git a/test/mocha_integration_neo4j.test.js b/test/mocha_integration_neo4j.test.js index 65436139..cbe25034 100644 --- a/test/mocha_integration_neo4j.test.js +++ b/test/mocha_integration_neo4j.test.js @@ -31,8 +31,6 @@ describe("Neo4j - Basic CRUD Operations", () => { }); it("02. Movie: add", async () => { - // movie_id,release,runtime,box_office,is_adult,genres,votes - // m1,2008-12-03T10:15:30Z,130,17845632.32,true,action;thriller,50;200;140;1200;150 let res = itHelpers.request_graph_ql_post_instance2( `mutation{ addMovie(movie_id:"m0", release:"1998-12-03T10:15:30Z", runtime:110, box_office:13145632.32, @@ -1586,3 +1584,493 @@ describe("data loader for readById method", () => { }); }); }); + +describe("Neo4j - Associations for Paired-end Foreign Keys (local)", () => { + // set up the environment + before(async () => { + let res = itHelpers.request_graph_ql_post_instance2( + `mutation{ + n0: addHouse(house_id:"h1", construction_year:1997) {house_id} + n1: addHouse(house_id:"h2", construction_year:1853) {house_id} + n2: addHouse(house_id:"h3", construction_year:2012) {house_id} + }` + ); + + expect(res.statusCode).to.equal(200); + }); + + // clean up records + after(async () => { + // Delete all houses + let res = itHelpers.request_graph_ql_post_instance2( + "{ houses(pagination:{limit:25}) {house_id} }" + ); + let houses = JSON.parse(res.body.toString("utf8")).data.houses; + + for (let i = 0; i < houses.length; i++) { + res = itHelpers.request_graph_ql_post_instance2( + `mutation { deleteHouse (house_id: "${houses[i].house_id}") }` + ); + expect(res.statusCode).to.equal(200); + } + + let cnt = await itHelpers.count_all_records_instance2("countHouses"); + expect(cnt).to.equal(0); + + // Delete all streets + res = itHelpers.request_graph_ql_post_instance2( + "{ streets(pagination:{limit:25}) {street_id} }" + ); + let streets = JSON.parse(res.body.toString("utf8")).data.streets; + + for (let i = 0; i < streets.length; i++) { + res = itHelpers.request_graph_ql_post_instance2( + `mutation { deleteStreet (street_id: "${streets[i].street_id}") }` + ); + expect(res.statusCode).to.equal(200); + } + + cnt = await itHelpers.count_all_records_instance2("countStreets"); + expect(cnt).to.equal(0); + + // Delete all owners + res = itHelpers.request_graph_ql_post_instance2( + "{ owners(pagination:{limit:25}) {owner_id} }" + ); + let owner = JSON.parse(res.body.toString("utf8")).data.owners; + + for (let i = 0; i < owner.length; i++) { + res = itHelpers.request_graph_ql_post_instance2( + `mutation { deleteOwner (owner_id: "${owner[i].owner_id}") }` + ); + expect(res.statusCode).to.equal(200); + } + + cnt = await itHelpers.count_all_records_instance2("countOwners"); + expect(cnt).to.equal(0); + }); + + it("01. House : Street (n:1) - add houses to street", () => { + let res = itHelpers.request_graph_ql_post_instance2( + `mutation{ + addStreet( street_id: "s1", street_name: "Melatener Str.", addHouses: ["h1", "h2"] ){ + street_name + house_ids + housesFilter(pagination:{limit:10}){ + construction_year + } + } + }` + ); + let resBody = JSON.parse(res.body.toString("utf8")); + + expect(res.statusCode).to.equal(200); + expect(resBody).to.deep.equal({ + data: { + addStreet: { + housesFilter: [ + { + construction_year: 1997, + }, + { + construction_year: 1853, + }, + ], + street_name: "Melatener Str.", + house_ids: ["h1", "h2"], + }, + }, + }); + }); + it("02. House : Street (n:1) - read one associated house", () => { + let res = itHelpers.request_graph_ql_post_instance2(`{ + readOneHouse(house_id: "h1"){ + construction_year + street_id + } + }`); + let resBody = JSON.parse(res.body.toString("utf8")); + + expect(res.statusCode).to.equal(200); + expect(resBody).to.deep.equal({ + data: { + readOneHouse: { + construction_year: 1997, + street_id: "s1", + }, + }, + }); + }); + + it("03. House : Street (n:1) - delete the associations in the street record", () => { + let res = itHelpers.request_graph_ql_post_instance2( + `mutation{updateStreet(street_id: "s1", removeHouses: ["h1", "h2"]) { + street_name + housesFilter(pagination:{limit:10}){ + construction_year + } + housesConnection(pagination:{first:5}){ + houses{ + house_id + } + } + } + }` + ); + + resBody = JSON.parse(res.body.toString("utf8")); + expect(res.statusCode).to.equal(200); + expect(resBody).to.deep.equal({ + data: { + updateStreet: { + housesFilter: [], + street_name: "Melatener Str.", + housesConnection: { + houses: [], + }, + }, + }, + }); + }); + + it("04. House : Owner (1:1) - add house to owner", () => { + let res = itHelpers.request_graph_ql_post_instance2( + `mutation{ + addOwner(owner_id:"o1", name:"Maximillian", addUnique_house:"h3"){ + owner_id + house_id + } + }` + ); + let resBody = JSON.parse(res.body.toString("utf8")); + + expect(res.statusCode).to.equal(200); + expect(resBody).to.deep.equal({ + data: { + addOwner: { + owner_id: "o1", + house_id: "h3", + }, + }, + }); + }); + + it("05. House : Owner (1:1) - read one associated house", () => { + let res = itHelpers.request_graph_ql_post_instance2(` + { + readOneHouse(house_id: "h3"){ + construction_year + owner_id + } + }`); + let resBody = JSON.parse(res.body.toString("utf8")); + + expect(res.statusCode).to.equal(200); + expect(resBody).to.deep.equal({ + data: { + readOneHouse: { + construction_year: 2012, + owner_id: "o1", + }, + }, + }); + }); + + it("06. House : Owner (1:1) - update the existing association", () => { + res = itHelpers.request_graph_ql_post_instance2( + `mutation{ + addOwner(owner_id:"o2", name:"Lily", addUnique_house:"h3"){ + owner_id + house_id + } + }` + ); + resBody = JSON.parse(res.body.toString("utf8")); + expect(res.statusCode).to.equal(200); + expect(resBody).to.deep.equal({ + errors: [ + { + message: "Hint: update 1 existing association!", + locations: "", + }, + ], + data: { addOwner: { owner_id: "o2", house_id: "h3" } }, + }); + }); + + it("07. House : Owner (1:1) - delete the associations in the owner record", () => { + let res = itHelpers.request_graph_ql_post_instance2( + `mutation{ + updateOwner(owner_id:"o2", removeUnique_house:"h3"){ + owner_id + house_id + } + }` + ); + resBody = JSON.parse(res.body.toString("utf8")); + expect(res.statusCode).to.equal(200); + expect(resBody).to.deep.equal({ + data: { + updateOwner: { + owner_id: "o2", + house_id: null, + }, + }, + }); + }); +}); + +describe("Neo4j - Associations for Paired-end Foreign Keys (distributed)", () => { + after(async () => { + // Delete all houses + let res = itHelpers.request_graph_ql_post_instance2( + "{ dist_housesConnection(pagination:{first:10}) {edges {node {house_id}}}}" + ); + let edges = JSON.parse(res.body.toString("utf8")).data.dist_housesConnection + .edges; + + for (let edge of edges) { + res = itHelpers.request_graph_ql_post_instance2( + `mutation { deleteDist_house (house_id: "${edge.node.house_id}") }` + ); + expect(res.statusCode).to.equal(200); + } + + let cnt = await itHelpers.count_all_records_instance2("countDist_houses"); + expect(cnt).to.equal(0); + + // Delete all streets + res = itHelpers.request_graph_ql_post_instance2( + "{ dist_streetsConnection(pagination:{first:10}) {edges {node {street_id}}}}" + ); + edges = JSON.parse(res.body.toString("utf8")).data.dist_streetsConnection + .edges; + + for (let edge of edges) { + res = itHelpers.request_graph_ql_post_instance2( + `mutation { deleteDist_street (street_id: "${edge.node.street_id}") }` + ); + expect(res.statusCode).to.equal(200); + } + + cnt = await itHelpers.count_all_records_instance2("countDist_streets"); + expect(cnt).to.equal(0); + + // Delete all owners + res = itHelpers.request_graph_ql_post_instance2( + "{ dist_ownersConnection(pagination:{first:10}) {edges {node {owner_id}}}}" + ); + edges = JSON.parse(res.body.toString("utf8")).data.dist_ownersConnection + .edges; + + for (let edge of edges) { + res = itHelpers.request_graph_ql_post_instance2( + `mutation { deleteDist_owner (owner_id: "${edge.node.owner_id}") }` + ); + expect(res.statusCode).to.equal(200); + } + + cnt = await itHelpers.count_all_records_instance2("countDist_owners"); + expect(cnt).to.equal(0); + }); + + it("01. House DDM: create a street and 2 houses", () => { + let res = itHelpers.request_graph_ql_post_instance2( + `mutation { + addDist_street(street_id: "instance1-s1", street_name: "Melatener Str.") { + street_id + street_name + } + }` + ); + let resBody = JSON.parse(res.body.toString("utf8")); + expect(res.statusCode).to.equal(200); + expect(resBody).to.deep.equal({ + data: { + addDist_street: { + street_id: "instance1-s1", + street_name: "Melatener Str.", + }, + }, + }); + + const year = [1993, 2016, 1982]; + for (let i = 0; i < year.length; i++) { + res = itHelpers.request_graph_ql_post_instance2( + `mutation { + addDist_house(house_id: "instance1-h${i + 1}", construction_year: ${ + year[i] + }) + { + house_id + construction_year + } + } + ` + ); + resBody = JSON.parse(res.body.toString("utf8")); + expect(res.statusCode).to.equal(200); + + expect(resBody).to.deep.equal({ + data: { + addDist_house: { + house_id: `instance1-h${i + 1}`, + construction_year: year[i], + }, + }, + }); + } + }); + + it("02. House DDM: update the street to associate with houses", () => { + let res = itHelpers.request_graph_ql_post_instance2( + `mutation { + updateDist_street(street_id: "instance1-s1", addDist_houses: ["instance1-h1", "instance1-h2"]) { + street_name + countFilteredDist_houses + dist_housesConnection(pagination: {first: 5}) { + edges { + node { + construction_year + } + } + dist_houses{ + house_id + } + } + } + } + ` + ); + let resBody = JSON.parse(res.body.toString("utf8")); + expect(res.statusCode).to.equal(200); + expect(resBody).to.deep.equal({ + data: { + updateDist_street: { + street_name: "Melatener Str.", + countFilteredDist_houses: 2, + dist_housesConnection: { + edges: [ + { + node: { + construction_year: 1993, + }, + }, + { + node: { + construction_year: 2016, + }, + }, + ], + dist_houses: [ + { house_id: "instance1-h1" }, + { house_id: "instance1-h2" }, + ], + }, + }, + }, + }); + }); + + it("03. House DDM: update the street to remove associations", () => { + let res = itHelpers.request_graph_ql_post_instance2( + `mutation { + updateDist_street(street_id:"instance1-s1" removeDist_houses:["instance1-h1", "instance1-h2"]) { + street_name + countFilteredDist_houses + dist_housesConnection(pagination:{first:5}){ + edges { + node { + construction_year + } + } + dist_houses{ + house_id + } + } + } + }` + ); + let resBody = JSON.parse(res.body.toString("utf8")); + expect(res.statusCode).to.equal(200); + expect(resBody).to.deep.equal({ + data: { + updateDist_street: { + street_name: "Melatener Str.", + countFilteredDist_houses: 0, + dist_housesConnection: { + edges: [], + dist_houses: [], + }, + }, + }, + }); + }); + + it("04. House DDM: add house to owner", () => { + let res = itHelpers.request_graph_ql_post_instance2( + `mutation{ + addDist_owner(owner_id:"instance1-o1", name:"Haribo", addDist_unique_house:"instance1-h3"){ + owner_id + house_id + } + }` + ); + let resBody = JSON.parse(res.body.toString("utf8")); + + expect(res.statusCode).to.equal(200); + expect(resBody).to.deep.equal({ + data: { + addDist_owner: { + owner_id: "instance1-o1", + house_id: "instance1-h3", + }, + }, + }); + }); + + it("05. House DDM: update the existing association", () => { + res = itHelpers.request_graph_ql_post_instance2( + `mutation{ + addDist_owner(owner_id:"instance1-o2", name:"Bing", addDist_unique_house:"instance1-h3"){ + owner_id + house_id + } + }` + ); + resBody = JSON.parse(res.body.toString("utf8")); + expect(res.statusCode).to.equal(200); + expect(resBody).to.deep.equal({ + errors: [ + { + message: "Hint: update 1 existing association!", + locations: "", + }, + ], + data: { + addDist_owner: { owner_id: "instance1-o2", house_id: "instance1-h3" }, + }, + }); + }); + + it("06. House DDM: delete the associations in the house record", () => { + let res = itHelpers.request_graph_ql_post_instance2( + `mutation{ + updateDist_house(house_id:"instance1-h3", removeDist_unique_owner:"instance1-o2"){ + owner_id + house_id + } + }` + ); + resBody = JSON.parse(res.body.toString("utf8")); + expect(res.statusCode).to.equal(200); + expect(resBody).to.deep.equal({ + data: { + updateDist_house: { + house_id: "instance1-h3", + owner_id: null, + }, + }, + }); + }); +}); diff --git a/test/mocha_unit.test.js b/test/mocha_unit.test.js index fe669d10..408c0054 100644 --- a/test/mocha_unit.test.js +++ b/test/mocha_unit.test.js @@ -374,9 +374,7 @@ describe("All webservice (generic) models", function () { it("Resolvers - person", async function () { let opts = funks.getOptions(models_generic_webservice.person); let generated_resolvers = await funks.generateJs("create-resolvers", opts); - let g_resolvers = generated_resolvers.replace(/\s/g, ""); - let test_resolvers = data_test.resolvers_person.replace(/\s/g, ""); - expect(g_resolvers).to.have.string(test_resolvers); + testCompare(generated_resolvers, data_test.resolvers_person); }); it("Model - person", async function () { @@ -1338,6 +1336,7 @@ describe("Parse associations", function () { targetKey: "projectId", targetKey_cp: "ProjectId", sourceKey: "researcherId", + sourceKey_cp: "ResearcherId", keysIn: "project_to_researcher", keysIn_lc: "project_to_researcher", targetStorageType: "sql", @@ -1430,6 +1429,7 @@ describe("Parse associations", function () { targetKey: "bookId", targetKey_cp: "BookId", sourceKey: "personId", + sourceKey_cp: "PersonId", keysIn: "books_to_people", keysIn_lc: "books_to_people", targetStorageType: "sql", @@ -2985,4 +2985,46 @@ describe("Neo4j Unit Test", function () { let generated_model = await funks.generateJs("create-neo4j-adapter", opts); testCompare(generated_model, data_test.neo4j_adapter_readById); }); + + it("neo4j model - paired end FK - manyToOne - add steet to house", async function () { + let opts = funks.getOptions(models_neo4j.house); + let generated_model = await funks.generateJs("create-models-neo4j", opts); + testCompare(generated_model, data_test.house_pairedEnd_add_street); + }); + + it("neo4j model - paired end FK - manyToOne - remove street from house", async function () { + let opts = funks.getOptions(models_neo4j.house); + let generated_model = await funks.generateJs("create-models-neo4j", opts); + testCompare(generated_model, data_test.house_pairedEnd_remove_street); + }); + + it("neo4j model - paired end oneToOne - add owner to house", async function () { + let opts = funks.getOptions(models_neo4j.house); + let generated_model = await funks.generateJs("create-models-neo4j", opts); + testCompare(generated_model, data_test.house_pairedEnd_add_owner); + }); + + it("neo4j model - paired end oneToOne - remove owner from house", async function () { + let opts = funks.getOptions(models_neo4j.house); + let generated_model = await funks.generateJs("create-models-neo4j", opts); + testCompare(generated_model, data_test.house_pairedEnd_remove_owner); + }); + + it("neo4j ddm - paired end FK - manyToOne - add dist_steet to dist_house", async function () { + let opts = funks.getOptions(models_neo4j.dist_house); + let generated_model = await funks.generateJs( + "create-distributed-model", + opts + ); + testCompare(generated_model, data_test.neo4j_ddm_add_dist_steet); + }); + + it("neo4j ddm - paired end FK - manyToOne - remove dist_steet from dist_house", async function () { + let opts = funks.getOptions(models_neo4j.dist_house); + let generated_model = await funks.generateJs( + "create-distributed-model", + opts + ); + testCompare(generated_model, data_test.neo4j_ddm_remove_dist_steet); + }); }); diff --git a/test/unit_test_misc/data_models_neo4j.js b/test/unit_test_misc/data_models_neo4j.js index 1101bc5a..dbce0b14 100644 --- a/test/unit_test_misc/data_models_neo4j.js +++ b/test/unit_test_misc/data_models_neo4j.js @@ -71,3 +71,82 @@ module.exports.dist_movie_instance1 = { internalId: "movie_id", }; + +module.exports.house = { + model: "house", + storageType: "neo4j", + attributes: { + house_id: "String", + construction_year: "Int", + street_id: "String", + owner_id: "String", + }, + associations: { + street: { + type: "many_to_one", + implementation: "foreignkeys", + target: "street", + targetKey: "house_ids", + sourceKey: "street_id", + keysIn: "house", + targetStorageType: "neo4j", + deletion: "update", + }, + unique_owner: { + type: "one_to_one", + implementation: "foreignkeys", + target: "owner", + targetKey: "house_id", + sourceKey: "owner_id", + keysIn: "house", + targetStorageType: "neo4j", + deletion: "update", + }, + }, + internalId: "house_id", + id: { + name: "house_id", + type: "String", + }, + useDataLoader: true, +}; + +module.exports.dist_house = { + model: "dist_house", + storageType: "distributed-data-model", + registry: ["dist_house_instance1"], + attributes: { + house_id: "String", + construction_year: "Int", + street_id: "String", + owner_id: "String", + }, + associations: { + dist_street: { + type: "many_to_one", + implementation: "foreignkeys", + target: "dist_street", + targetKey: "house_ids", + sourceKey: "street_id", + keysIn: "dist_house", + targetStorageType: "distributed-data-model", + deletion: "update", + }, + dist_unique_owner: { + type: "one_to_one", + implementation: "foreignkeys", + target: "dist_owner", + targetKey: "house_id", + sourceKey: "owner_id", + keysIn: "dist_house", + targetStorageType: "distributed-data-model", + deletion: "update", + }, + }, + internalId: "house_id", + id: { + name: "house_id", + type: "String", + }, + useDataLoader: true, +}; diff --git a/test/unit_test_misc/test-describe/all-generic-webservice.js b/test/unit_test_misc/test-describe/all-generic-webservice.js index 344102c9..a78b2ae2 100644 --- a/test/unit_test_misc/test-describe/all-generic-webservice.js +++ b/test/unit_test_misc/test-describe/all-generic-webservice.js @@ -160,21 +160,24 @@ person.prototype.worksFilter = function({ order, pagination }, context) { - //build new search filter - let nsearch = helper.addSearchField({ - "search": search, - "field": "book_id", - "value": this.getIdValue(), - "operator": "eq" - }); - - return resolvers.books({ - search: nsearch, - order: order, - pagination: pagination - }, context); -} + //return an empty response if the foreignKey Array is empty, no need to query the database + if (!Array.isArray(this.person_id) || this.person_id.length === 0) { + return []; + } + let nsearch = helper.addSearchField({ + "search": search, + "field": models.book.idAttribute(), + "value": this.person_id.join(','), + "valueType": "Array", + "operator": "in" + }); + return resolvers.books({ + search: nsearch, + order: order, + pagination: pagination + }, context); +} `; module.exports.class_name_model_person = ` diff --git a/test/unit_test_misc/test-describe/all-generic.js b/test/unit_test_misc/test-describe/all-generic.js index c88cd424..4a2f7245 100644 --- a/test/unit_test_misc/test-describe/all-generic.js +++ b/test/unit_test_misc/test-describe/all-generic.js @@ -276,7 +276,7 @@ module.exports.test16_11 = module.exports.test16_12 = /countAssociatedRecordsWithRejectReaction.+letget_generic_to_many_associated=result_generic_to_many\.reduce\(\(accumulator,current_val\)=>accumulator\+current_val,0\);/; module.exports.test16_13 = - /countAssociatedRecordsWithRejectReaction.+returnget_to_one_associated\+get_to_many_associated_fk\+get_to_many_associated\+get_generic_to_many_associated;/; + /countAssociatedRecordsWithRejectReaction.+returnget_to_one_associated\+get_to_many_associated_fk\+get_to_many_associated\+get_to_one_associated_fk\+get_generic_to_many_associated;/; /** * 17. generic - dog @@ -303,7 +303,7 @@ module.exports.test17_9 = module.exports.test17_10 = /countAssociatedRecordsWithRejectReaction.+letget_generic_to_one_associated=result_generic_to_one\.filter\(\(r,index\)=>helper\.isNotUndefinedAndNotNull\(r\)\).length;/; module.exports.test17_11 = - /countAssociatedRecordsWithRejectReaction.+returnget_to_one_associated\+get_to_many_associated_fk\+get_to_many_associated\+get_generic_to_one_associated;/; + /countAssociatedRecordsWithRejectReaction.+returnget_to_one_associated\+get_to_many_associated_fk\+get_to_many_associated\+get_to_one_associated_fk\+get_generic_to_one_associated;/; /* Models */ diff --git a/test/unit_test_misc/test-describe/foreign-key-array.js b/test/unit_test_misc/test-describe/foreign-key-array.js index 70a2310e..0a129300 100644 --- a/test/unit_test_misc/test-describe/foreign-key-array.js +++ b/test/unit_test_misc/test-describe/foreign-key-array.js @@ -117,12 +117,12 @@ author.prototype.remove_books = async function(input, benignErrorReporter, token `; module.exports.model_add_association = ` - static async add_book_ids(id, book_ids, benignErrorReporter, handle_inverse = true){ + static async add_book_ids(id, book_ids, benignErrorReporter, token, handle_inverse = true){ //handle inverse association if (handle_inverse) { let promises = []; book_ids.forEach( idx => { - promises.push( models.book.add_author_ids( idx ,[ \`\${id}\`], benignErrorReporter, false ) ); + promises.push( models.book.add_author_ids( idx ,[ \`\${id}\`], benignErrorReporter, token, false ) ); }); await Promise.all(promises); } @@ -137,12 +137,12 @@ module.exports.model_add_association = ` `; module.exports.model_remove_association = ` - static async remove_book_ids(id, book_ids, benignErrorReporter, handle_inverse = true){ + static async remove_book_ids(id, book_ids, benignErrorReporter, token, handle_inverse = true){ //handle inverse association if (handle_inverse) { let promises = []; book_ids.forEach( idx => { - promises.push( models.book.remove_author_ids(idx, [ \`\${id}\`], benignErrorReporter, false )); + promises.push( models.book.remove_author_ids(idx, [ \`\${id}\`], benignErrorReporter, token, false )); }); await Promise.all(promises); } @@ -264,21 +264,21 @@ module.exports.remote_model_remove_association = ` `; module.exports.ddm_model_add = ` -static async add_book_ids(id, book_ids, benignErrorReporter, handle_inverse = true, token) { +static async add_book_ids(id, book_ids, benignErrorReporter, token, handle_inverse) { let responsibleAdapter = this.adapterForIri(id); - return await adapters[responsibleAdapter].add_book_ids(id, book_ids, benignErrorReporter, handle_inverse, token); + return await adapters[responsibleAdapter].add_book_ids(id, book_ids, benignErrorReporter, token, handle_inverse); } `; module.exports.sql_adapter_add = ` -static async add_book_ids(id, book_ids, benignErrorReporter, handle_inverse = true) { +static async add_book_ids(id, book_ids, benignErrorReporter, token, handle_inverse = true) { //handle inverse association if (handle_inverse) { let promises = []; book_ids.forEach(idx => { - promises.push(models.sq_book.add_author_ids(idx, [\`\${id}\`], benignErrorReporter, false)); + promises.push(models.sq_book.add_author_ids(idx, [\`\${id}\`], benignErrorReporter, token, false)); }); await Promise.all(promises); } diff --git a/test/unit_test_misc/test-describe/mongodb-unittest.js b/test/unit_test_misc/test-describe/mongodb-unittest.js index bed7966f..06886e82 100644 --- a/test/unit_test_misc/test-describe/mongodb-unittest.js +++ b/test/unit_test_misc/test-describe/mongodb-unittest.js @@ -291,12 +291,12 @@ static async remove_farm_id(animal_id, farm_id, benignErrorReporter) { `; module.exports.animal_fieldMutation_add_food = ` -static async add_food_ids(animal_id, food_ids, benignErrorReporter, handle_inverse = true) { +static async add_food_ids(animal_id, food_ids, benignErrorReporter, token, handle_inverse = true) { //handle inverse association if (handle_inverse) { let promises = []; food_ids.forEach(idx => { - promises.push(models.food.add_animal_ids(idx, [\`\${animal_id}\`], benignErrorReporter, false)); + promises.push(models.food.add_animal_ids(idx, [\`\${animal_id}\`], benignErrorReporter, token, false)); }); await Promise.all(promises); } @@ -322,12 +322,12 @@ static async add_food_ids(animal_id, food_ids, benignErrorReporter, handle_inver `; module.exports.animal_fieldMutation_remove_food = ` -static async remove_food_ids(animal_id, food_ids, benignErrorReporter, handle_inverse = true) { +static async remove_food_ids(animal_id, food_ids, benignErrorReporter, token, handle_inverse = true) { //handle inverse association if (handle_inverse) { let promises = []; food_ids.forEach(idx => { - promises.push(models.food.remove_animal_ids(idx, [\`\${animal_id}\`], benignErrorReporter, false)); + promises.push(models.food.remove_animal_ids(idx, [\`\${animal_id}\`], benignErrorReporter, token, false)); }); await Promise.all(promises); } diff --git a/test/unit_test_misc/test-describe/neo4j-unittest.js b/test/unit_test_misc/test-describe/neo4j-unittest.js index e0f9a4c1..39e4786a 100644 --- a/test/unit_test_misc/test-describe/neo4j-unittest.js +++ b/test/unit_test_misc/test-describe/neo4j-unittest.js @@ -413,12 +413,12 @@ static async remove_director_id(movie_id, director_id, benignErrorReporter) { `; module.exports.movie_fieldMutation_add_actor = ` -static async add_actor_ids(movie_id, actor_ids, benignErrorReporter, handle_inverse = true) { +static async add_actor_ids(movie_id, actor_ids, benignErrorReporter, token, handle_inverse = true) { //handle inverse association if (handle_inverse) { let promises = []; actor_ids.forEach(idx => { - promises.push(models.actor.add_movie_ids(idx, [\`\${movie_id}\`], benignErrorReporter, false)); + promises.push(models.actor.add_movie_ids(idx, [\`\${movie_id}\`], benignErrorReporter, token, false)); }); await Promise.all(promises); } @@ -459,12 +459,12 @@ static async add_actor_ids(movie_id, actor_ids, benignErrorReporter, handle_inve `; module.exports.movie_fieldMutation_remove_actor = ` -static async remove_actor_ids(movie_id, actor_ids, benignErrorReporter, handle_inverse = true) { +static async remove_actor_ids(movie_id, actor_ids, benignErrorReporter, token, handle_inverse = true) { //handle inverse association if (handle_inverse) { let promises = []; actor_ids.forEach(idx => { - promises.push(models.actor.remove_movie_ids(idx, [\`\${movie_id}\`], benignErrorReporter, false)); + promises.push(models.actor.remove_movie_ids(idx, [\`\${movie_id}\`], benignErrorReporter, token, false)); }); await Promise.all(promises); } @@ -622,3 +622,181 @@ static async readById(id) { return await dist_movie_instance1.readByIdLoader.load(id); } `; + +module.exports.house_pairedEnd_add_street = ` +static async add_street_id(house_id, street_id, benignErrorReporter, token, handle_inverse = true) { + //handle inverse association + if (handle_inverse) { + await models.street.add_house_ids(street_id, [\`\${house_id}\`], benignErrorReporter, token, false); + } + const driver = await this.storageHandler; + const session = driver.session({ + database: config.database, + defaultAccessMode: neo4j.session.WRITE, + }); + let foreignKey = \`MATCH (n:houses ) WHERE n.house_id = \$id + SET n.street_id = \$target RETURN count(n)\`; + const target_model = models.street.definition.model_name_in_storage ?? "streets"; + + let create_relationships = \`MATCH (a:houses), (b:\${target_model}) + WHERE a.house_id = $id AND b.\${models.street.idAttribute()} = $target + CREATE (a)-[r:\${"street".toUpperCase() + "_EDGE"}]->(b)\` + try { + const result = await session.run(foreignKey, { + id: house_id, + target: street_id + }); + await session.run(create_relationships, { + id: house_id, + target: street_id, + }) + return result.records[0].get(0); + } catch (error) { + benignErrorReporter.push({ + message: error + }); + } finally { + await session.close(); + } +} +`; + +module.exports.house_pairedEnd_remove_street = ` +static async remove_street_id(house_id, street_id, benignErrorReporter, token, handle_inverse = true) { + //handle inverse association + if (handle_inverse) { + await models.street.remove_house_ids(street_id, [\`\${house_id}\`], benignErrorReporter, token, false); + } + const driver = await this.storageHandler; + const session = driver.session({ + database: config.database, + defaultAccessMode: neo4j.session.WRITE, + }); + let foreignKey = \`MATCH (n:houses ) WHERE n.house_id = \$id + SET n.street_id = \$target RETURN count(n)\`; + const target_model = models.street.definition.model_name_in_storage ?? "streets"; + + let delete_relationships = \`MATCH (a:houses)-[r:\${"street".toUpperCase() + "_EDGE"}]-> (b:\${target_model}) + WHERE a.house_id = $id AND b.\${models.street.idAttribute()} = $target + DELETE r\` + try { + const result = await session.run(foreignKey, { + id: house_id, + target: null + }); + await session.run(delete_relationships, { + id: house_id, + target: street_id, + }) + return result.records[0].get(0); + } catch (error) { + benignErrorReporter.push({ + message: error + }); + } finally { + await session.close(); + } +} +`; + +module.exports.house_pairedEnd_add_owner = ` +static async add_owner_id(house_id, owner_id, benignErrorReporter, token, handle_inverse = true) { + //handle inverse association + if (handle_inverse) { + await models.owner.add_house_id(owner_id, house_id, benignErrorReporter, token, false); + } + const driver = await this.storageHandler; + const session = driver.session({ + database: config.database, + defaultAccessMode: neo4j.session.WRITE, + }); + let foreignKey = \`MATCH (n:houses ) WHERE n.house_id = \$id + SET n.owner_id = \$target RETURN count(n)\`; + const target_model = models.owner.definition.model_name_in_storage ?? "owners"; + + let create_relationships = \`MATCH (a:houses), (b:\${target_model}) + WHERE a.house_id = $id AND b.\${models.owner.idAttribute()} = $target + CREATE (a)-[r:\${"unique_owner".toUpperCase() + "_EDGE"}]->(b)\` + try { + const result = await session.run(foreignKey, { + id: house_id, + target: owner_id + }); + await session.run(create_relationships, { + id: house_id, + target: owner_id, + }) + return result.records[0].get(0); + } catch (error) { + benignErrorReporter.push({ + message: error + }); + } finally { + await session.close(); + } +} +`; + +module.exports.house_pairedEnd_remove_owner = ` +static async remove_owner_id(house_id, owner_id, benignErrorReporter, token, handle_inverse = true) { + //handle inverse association + if (handle_inverse) { + await models.owner.remove_house_id(owner_id, house_id, benignErrorReporter, token, false); + } + const driver = await this.storageHandler; + const session = driver.session({ + database: config.database, + defaultAccessMode: neo4j.session.WRITE, + }); + let foreignKey = \`MATCH (n:houses ) WHERE n.house_id = \$id + SET n.owner_id = \$target RETURN count(n)\`; + const target_model = models.owner.definition.model_name_in_storage ?? "owners"; + + let delete_relationships = \`MATCH (a:houses)-[r:\${"unique_owner".toUpperCase() + "_EDGE"}]-> (b:\${target_model}) + WHERE a.house_id = $id AND b.\${models.owner.idAttribute()} = $target + DELETE r\` + try { + const result = await session.run(foreignKey, { + id: house_id, + target: null + }); + await session.run(delete_relationships, { + id: house_id, + target: owner_id, + }) + return result.records[0].get(0); + } catch (error) { + benignErrorReporter.push({ + message: error + }); + } finally { + await session.close(); + } +} +`; + +module.exports.neo4j_ddm_add_dist_steet = ` +static async add_street_id(house_id, street_id, benignErrorReporter, token, handle_inverse) { + try { + let responsibleAdapter = this.adapterForIri(house_id); + return await adapters[responsibleAdapter].add_street_id(house_id, street_id, benignErrorReporter, token, handle_inverse); + } catch (error) { + benignErrorReporter.push({ + message: error, + }); + } +} +`; + +module.exports.neo4j_ddm_remove_dist_steet = ` +static async remove_street_id(house_id, street_id, benignErrorReporter, token, handle_inverse) { + try { + let responsibleAdapter = this.adapterForIri(house_id); + return await adapters[responsibleAdapter].remove_street_id(house_id, street_id, benignErrorReporter, token, handle_inverse); + } catch (error) { + benignErrorReporter.push({ + message: error, + }); + } +} +`; diff --git a/test/unit_test_misc/test-describe/refactoring-associations.js b/test/unit_test_misc/test-describe/refactoring-associations.js index 4ce52120..b23a98fb 100644 --- a/test/unit_test_misc/test-describe/refactoring-associations.js +++ b/test/unit_test_misc/test-describe/refactoring-associations.js @@ -17,6 +17,7 @@ module.exports.count_associations = ` let promises_to_many = []; let promises_to_one = []; let get_to_many_associated_fk = 0; + let get_to_one_associated_fk = 0; promises_to_many.push(accession.countFilteredIndividuals({}, context)); promises_to_many.push(accession.countFilteredMeasurements({}, context)); @@ -28,7 +29,7 @@ module.exports.count_associations = ` let get_to_many_associated = result_to_many.reduce((accumulator, current_val) => accumulator + current_val, 0); let get_to_one_associated = result_to_one.filter((r, index) => helper.isNotUndefinedAndNotNull(r)).length; - return get_to_one_associated + get_to_many_associated_fk + get_to_many_associated; + return get_to_one_associated + get_to_many_associated_fk + get_to_many_associated + get_to_one_associated_fk; } `; @@ -336,11 +337,12 @@ module.exports.add_assoc_ddm_model = ` * @param {Id} locationId Foreign Key (stored in "Me") of the Association to be updated. * @param {BenignErrorReporter} benignErrorReporter Error Reporter used for reporting Errors from remote zendro services * @param {string} token The token used for authorization + * @param {boolean} handle_inverse Handle inverse association */ -static async add_locationId(accession_id, locationId, benignErrorReporter, token) { +static async add_locationId(accession_id, locationId, benignErrorReporter, token, handle_inverse) { try { let responsibleAdapter = this.adapterForIri(accession_id); - return await adapters[responsibleAdapter].add_locationId(accession_id, locationId, benignErrorReporter, token); + return await adapters[responsibleAdapter].add_locationId(accession_id, locationId, benignErrorReporter, token, handle_inverse); } catch (error) { benignErrorReporter.push({ message: error, @@ -357,11 +359,12 @@ module.exports.remove_assoc_ddm_model = ` * @param {Id} locationId Foreign Key (stored in "Me") of the Association to be updated. * @param {BenignErrorReporter} benignErrorReporter Error Reporter used for reporting Errors from remote zendro services * @param {string} token The token used for authorization + * @param {boolean} handle_inverse Handle inverse association */ -static async remove_locationId(accession_id, locationId, benignErrorReporter, token) { +static async remove_locationId(accession_id, locationId, benignErrorReporter, token, handle_inverse) { try { let responsibleAdapter = this.adapterForIri(accession_id); - return await adapters[responsibleAdapter].remove_locationId(accession_id, locationId, benignErrorReporter, token); + return await adapters[responsibleAdapter].remove_locationId(accession_id, locationId, benignErrorReporter, token, handle_inverse); } catch (error) { benignErrorReporter.push({ message: error, diff --git a/views/create-resolvers-ddm.ejs b/views/create-resolvers-ddm.ejs index 5e97f8a8..94ca9f0f 100644 --- a/views/create-resolvers-ddm.ejs +++ b/views/create-resolvers-ddm.ejs @@ -35,7 +35,26 @@ const associationArgsDef = { * @return {type} Associated record */ <%- nameLc -%>.prototype.<%=associations_one[i].name%> = async function({search}, context){ - <% if (associations_one[i].holdsForeignKey) { %> + <% if (associations_one[i].assocThroughArray) { %> + if(helper.isNotUndefinedAndNotNull(this.<%=associations_one[i].sourceKey%>)){ + if (search === undefined || search === null) { + return resolvers.readOne<%=associations_one[i].target_cp%>({[models.<%=associations_one[i].target_lc-%>.idAttribute()]: this.<%=associations_one[i].sourceKey%>},context) + } else { + //build new search filter + let nsearch = helper.addSearchField({ + "search": search, + "field": models.<%=associations_one[i].target_lc-%>.idAttribute(), + "value": this.<%= associations_one[i].sourceKey -%>, + "operator": "eq" + }); + let found = (await resolvers.<%=associations_one[i].target_lc_pl%>Connection({search: nsearch, pagination: {first:1}}, context)).edges; + if (found.length > 0) { + return found[0].node + } + return found; + } + } + <%} else if (associations_one[i].holdsForeignKey) { %> if(helper.isNotUndefinedAndNotNull(this.<%=associations_one[i].targetKey%>)){ if (search === undefined || search === null) { return resolvers.readOne<%=associations_one[i].target_cp%>({[models.<%=associations_one[i].target_lc-%>.idAttribute()]: this.<%=associations_one[i].targetKey%>},context) @@ -81,28 +100,27 @@ const associationArgsDef = { <% associations_temp = associationsArguments["to_many"]-%> <% for(let i=0; i < associations_temp.length; i++){ -%> - /** - * <%- nameLc -%>.prototype.countFiltered<%=associations_temp[i].name_cp%> - Count number of associated records that holds the conditions specified in the search argument - * - * @param {object} {search} description - * @param {object} context Provided to every resolver holds contextual information like the resquest query and user info. - * @return {type} Number of associated records that holds the conditions specified in the search argument - */ - <%- nameLc -%>.prototype.countFiltered<%=associations_temp[i].name_cp%> = function({search}, context){ - - <%if(associations_temp[i].assocThroughArray){%> - //return 0 if the foreignKey Array is empty, no need to query the database - if (!Array.isArray(this.<%=associations_temp[i].sourceKey%>) || this.<%=associations_temp[i].sourceKey%>.length === 0 ) { - return 0; - } - let nsearch = helper.addSearchField({ - "search": search, - "field": models.<%=associations_temp[i].target_lc-%>.idAttribute(), - "value": this.<%=associations_temp[i].sourceKey%>.join(','), - "valueType": "Array", - "operator": "in" - }); - <%}else{-%> + /** + * <%- nameLc -%>.prototype.countFiltered<%=associations_temp[i].name_cp%> - Count number of associated records that holds the conditions specified in the search argument + * + * @param {object} {search} description + * @param {object} context Provided to every resolver holds contextual information like the resquest query and user info. + * @return {type} Number of associated records that holds the conditions specified in the search argument + */ + <%- nameLc -%>.prototype.countFiltered<%=associations_temp[i].name_cp%> = function({search}, context){ + <%if(associations_temp[i].assocThroughArray){%> + //return 0 if the foreignKey Array is empty, no need to query the database + if (!Array.isArray(this.<%=associations_temp[i].sourceKey%>) || this.<%=associations_temp[i].sourceKey%>.length === 0 ) { + return 0; + } + let nsearch = helper.addSearchField({ + "search": search, + "field": models.<%=associations_temp[i].target_lc-%>.idAttribute(), + "value": this.<%=associations_temp[i].sourceKey%>.join(','), + "valueType": "Array", + "operator": "in" + }); + <%}else{-%> //build new search filter let nsearch = helper.addSearchField({ "search": search, @@ -110,8 +128,8 @@ const associationArgsDef = { "value": this.getIdValue(), "operator": "eq" }); - <%}-%> - return resolvers.count<%=associations_temp[i].target_cp_pl%>({search: nsearch},context); + <%}-%> + return resolvers.count<%=associations_temp[i].target_cp_pl%>({search: nsearch},context); } @@ -126,29 +144,29 @@ const associationArgsDef = { * @param {object} context Provided to every resolver holds contextual information like the resquest query and user info. * @return {array} Array of records as grapqhql connections holding conditions specified by search, order and pagination argument */ - <%- nameLc -%>.prototype.<%=associations_temp[i].name%>Connection = function({search,order,pagination}, context){ - <%if(associations_temp[i].assocThroughArray){%> - //return an empty response if the foreignKey Array is empty, no need to query the database - if (!Array.isArray(this.<%=associations_temp[i].sourceKey%>) || this.<%=associations_temp[i].sourceKey%>.length === 0 ) { - return { - edges: [], - <%=associations_temp[i].target_lc_pl%>: [], - pageInfo: { - startCursor: null, - endCursor: null, - hasPreviousPage: false, - hasNextPage: false - } - }; - } - let nsearch = helper.addSearchField({ - "search": search, - "field": models.<%=associations_temp[i].target_lc-%>.idAttribute(), - "value": this.<%=associations_temp[i].sourceKey%>.join(','), - "valueType": "Array", - "operator": "in" - }); - <%}else{-%> + <%- nameLc -%>.prototype.<%=associations_temp[i].name%>Connection = function({search,order,pagination}, context){ + <%if(associations_temp[i].assocThroughArray){%> + //return an empty response if the foreignKey Array is empty, no need to query the database + if (!Array.isArray(this.<%=associations_temp[i].sourceKey%>) || this.<%=associations_temp[i].sourceKey%>.length === 0 ) { + return { + edges: [], + <%=associations_temp[i].target_lc_pl%>: [], + pageInfo: { + startCursor: null, + endCursor: null, + hasPreviousPage: false, + hasNextPage: false + } + }; + } + let nsearch = helper.addSearchField({ + "search": search, + "field": models.<%=associations_temp[i].target_lc-%>.idAttribute(), + "value": this.<%=associations_temp[i].sourceKey%>.join(','), + "valueType": "Array", + "operator": "in" + }); + <%}else{-%> //build new search filter let nsearch = helper.addSearchField({ "search": search, @@ -156,9 +174,8 @@ const associationArgsDef = { "value": this.getIdValue(), "operator": "eq" }); - - <%}-%> - return resolvers.<%=associations_temp[i].target_lc_pl%>Connection({search: nsearch,order: order,pagination: pagination},context); + <%}-%> + return resolvers.<%=associations_temp[i].target_lc_pl%>Connection({search: nsearch,order: order,pagination: pagination},context); } <%}-%> @@ -310,6 +327,7 @@ async function countAssociatedRecordsWithRejectReaction(id, context){ let promises_to_many = []; let promises_to_one = []; let get_to_many_associated_fk = 0; + let get_to_one_associated_fk = 0; <%if(associationsArguments["generic_to_many"].length > 0) {-%> let promises_generic_to_many = []; <%}-%> @@ -331,7 +349,11 @@ async function countAssociatedRecordsWithRejectReaction(id, context){ <%associations_temp = associationsArguments["to_one"] -%> <% for(let i=0; i < associations_temp.length; i++){ -%> <% if (associations_temp[i].delete_action==='reject'){-%> - promises_to_one.push(<%- nameLc %>.<%=associations_temp[i].name%>({}, context) ); + <%if(associations_temp[i].assocThroughArray){%> + get_to_one_associated_fk += [null, undefined].includes(<%- nameLc %>.<%=associations_temp[i].sourceKey%>) ? 0 : 1; + <%}else{-%> + promises_to_one.push(<%- nameLc %>.<%=associations_temp[i].name%>({}, context) ); + <%}-%> <%}-%> <%}-%> <%associations_temp = associationsArguments["generic_to_many"] -%> @@ -365,7 +387,7 @@ async function countAssociatedRecordsWithRejectReaction(id, context){ let get_generic_to_one_associated = result_generic_to_one.filter( (r, index) => helper.isNotUndefinedAndNotNull(r) ).length; <%}-%> - return get_to_one_associated + get_to_many_associated_fk + get_to_many_associated <%if(associationsArguments["generic_to_many"].length > 0) {%>+ get_generic_to_many_associated<%}-%><%if(associationsArguments["generic_to_one"].length > 0) {%> + get_generic_to_one_associated<%}%>; + return get_to_one_associated + get_to_many_associated_fk + get_to_many_associated + get_to_one_associated_fk <%if(associationsArguments["generic_to_many"].length > 0) {%>+ get_generic_to_many_associated<%}-%><%if(associationsArguments["generic_to_one"].length > 0) {%> + get_generic_to_one_associated<%}%>; } /** @@ -397,7 +419,15 @@ const updateAssociations = async (id, context) => { const pagi_first = globals.LIMIT_RECORDS; <% for(let i=0; i < associations_one.length; i++){ -%> <% if (associations_one[i].delete_action==='update'){-%> - <% if (associations_one[i].holdsForeignKey) { %> + <%if(associations_one[i].assocThroughArray){%> + const <%=`${associations_one[i].name}_id`%> = <%- nameLc -%>_record.<%=associations_one[i].sourceKey%>; + if (<%=`${associations_one[i].name}_id`%>) { + await resolvers.update<%- nameCp -%>( + { <%- idAttribute -%>: id, remove<%=associations_one[i].name_cp%>: <%=`${associations_one[i].name}_id`%> }, + context + ); + } + <%} else if (associations_one[i].holdsForeignKey) { %> const <%=`${associations_one[i].name}_id`%> = <%- nameLc -%>_record.<%=associations_one[i].targetKey%>; if (<%=`${associations_one[i].name}_id`%>) { await resolvers.update<%- nameCp -%>( @@ -414,8 +444,8 @@ const updateAssociations = async (id, context) => { context ); } - <%}-%> - <%}-%> + <%}-%> + <%}-%> <%}-%> <% let associations_many = associationsArguments["to_many"]-%> diff --git a/views/create-resolvers.ejs b/views/create-resolvers.ejs index 8de033aa..cfb0bf30 100644 --- a/views/create-resolvers.ejs +++ b/views/create-resolvers.ejs @@ -149,7 +149,41 @@ const associationArgsDef = { * @return {type} Associated record */ <%- nameLc -%>.prototype.<%=associations_one[i].name%> = async function({search}, context){ - <% if (associations_one[i].holdsForeignKey) { %> + <% if (associations_one[i].assocThroughArray) { %> + if(helper.isNotUndefinedAndNotNull(this.<%=associations_one[i].sourceKey%>)){ + if (search === undefined || search === null) { + return resolvers.readOne<%=associations_one[i].target_cp%>({[models.<%=associations_one[i].target_lc-%>.idAttribute()]: this.<%=associations_one[i].sourceKey%>},context) + } else { + <%# WORKAROUND FOR Cassandra targetStorageType: + In case of an association to a model within cassandra we need to do intersections + of the search parameters with the foreignkey array if the search is on the idAttribute + and with operator "eq" / "in", since cassandra doesn't support multiple restricions + with an "eq" / "in" on the primary key field. %> + <%if(associations_one[i].targetStorageType === 'cassandra'){%> + //WORKAROUND for cassandra targetStorageType. Mainpulate search to intersect Equal searches on the primaryKey + const hasIdSearch = helper.parseFieldResolverSearchArgForCassandra(search, this.<%=associations_one[i].sourceKey%>, models.<%=associations_one[i].target_lc-%>.idAttribute()); + <%}-%> + //build new search filter + let nsearch = <%if(associations_one[i].targetStorageType === 'cassandra'){%>hasIdSearch ? search : <%}-%>helper.addSearchField({ + "search": search, + "field": models.<%=associations_one[i].target_lc-%>.idAttribute(), + "value": this.<%= associations_one[i].sourceKey -%>, + "operator": "eq" + }); + let found = (await resolvers.<%=associations_one[i].target_lc_pl%>Connection({search: nsearch, pagination: {first:1}}, context)).edges; + if (found.length > 0) { + if(found.length > 1){ + context.benignErrors.push(new Error( + `Not unique "to_one" association Error: Found > 1 <%=associations_one[i].target_lc_pl%> matching <%- nameLc -%> with <%- idAttribute-%> ${this.<%= associations_one[i].sourceKey -%>}. + Consider making this a "to_many" association, or using unique constraints, or moving the foreign key into the <%- name -%> model. Returning first <%=associations_one[i].target-%>.` + )); + } + return found[0].node; + } + return null; + } + } + <% } else if (associations_one[i].holdsForeignKey) { %> if(helper.isNotUndefinedAndNotNull(this.<%=associations_one[i].targetKey%>)){ if (search === undefined || search === null) { return resolvers.readOne<%=associations_one[i].target_cp%>({[models.<%=associations_one[i].target_lc-%>.idAttribute()]: this.<%=associations_one[i].targetKey%>},context) @@ -228,21 +262,19 @@ const associationArgsDef = { * @return {array} Array of associated records holding conditions specified by search, order and pagination argument */ <%- nameLc -%>.prototype.<%=associations_temp[i].name%>Filter = function({search,order,pagination}, context){ - <%if(associations_temp[i].assocThroughArray){%> - //return an empty response if the foreignKey Array is empty, no need to query the database - if (!Array.isArray(this.<%=associations_temp[i].sourceKey%>) || this.<%=associations_temp[i].sourceKey%>.length === 0 ) { - return []; - } - let nsearch = helper.addSearchField({ - "search": search, - "field": models.<%=associations_temp[i].target_lc-%>.idAttribute(), - "value": this.<%=associations_temp[i].sourceKey%>.join(','), - "valueType": "Array", - "operator": "in" - }); + //return an empty response if the foreignKey Array is empty, no need to query the database + if (!Array.isArray(this.<%=associations_temp[i].sourceKey%>) || this.<%=associations_temp[i].sourceKey%>.length === 0 ) { + return []; + } + let nsearch = helper.addSearchField({ + "search": search, + "field": models.<%=associations_temp[i].target_lc-%>.idAttribute(), + "value": this.<%=associations_temp[i].sourceKey%>.join(','), + "valueType": "Array", + "operator": "in" + }); <%}else{-%> - //build new search filter let nsearch = helper.addSearchField({ "search": search, @@ -250,7 +282,6 @@ const associationArgsDef = { "value": this.getIdValue(), "operator": "eq" }); - <%}-%> return resolvers.<%=associations_temp[i].target_lc_pl%>({search: nsearch,order: order,pagination: pagination},context); } @@ -263,7 +294,6 @@ const associationArgsDef = { * @return {type} Number of associated records that holds the conditions specified in the search argument */ <%- nameLc -%>.prototype.countFiltered<%=associations_temp[i].name_cp%> = function({search}, context){ - <%if(associations_temp[i].assocThroughArray){%> //return 0 if the foreignKey Array is empty, no need to query the database if (!Array.isArray(this.<%=associations_temp[i].sourceKey%>) || this.<%=associations_temp[i].sourceKey%>.length === 0 ) { @@ -272,8 +302,8 @@ const associationArgsDef = { <%# WORKAROUND FOR Cassandra targetStorageType: In case of an association to a model within cassandra we need to do intersections of the search parameters with the foreignkey array if the search is on the idAttribute - and with operator "eq" / "in", since cassandra doesn't support multiple restricions - with an "eq" / "in" on the primary key field. %> + and with operator "eq" / "in", since cassandra doesn't support multiple restricions + with an "eq" / "in" on the primary key field. %> <%if(associations_temp[i].targetStorageType === 'cassandra'){%> //WORKAROUND for cassandra targetStorageType. Mainpulate search to intersect Equal searches on the primaryKey const hasIdSearch = helper.parseFieldResolverSearchArgForCassandra(search, this.<%=associations_temp[i].sourceKey%>, models.<%=associations_temp[i].target_lc-%>.idAttribute()); @@ -285,7 +315,6 @@ const associationArgsDef = { "valueType": "Array", "operator": "in" }); - <%}else{-%> //build new search filter let nsearch = helper.addSearchField({ @@ -310,7 +339,6 @@ const associationArgsDef = { * @return {array} Array of records as grapqhql connections holding conditions specified by search, order and pagination argument */ <%- nameLc -%>.prototype.<%=associations_temp[i].name%>Connection = function({search,order,pagination}, context){ - <%if(associations_temp[i].assocThroughArray){%> //return an empty response if the foreignKey Array is empty, no need to query the database if (!Array.isArray(this.<%=associations_temp[i].sourceKey%>) || this.<%=associations_temp[i].sourceKey%>.length === 0 ) { @@ -328,8 +356,8 @@ const associationArgsDef = { <%# WORKAROUND FOR Cassandra targetStorageType: In case of an association to a model within cassandra we need to do intersections of the search parameters with the foreignkey array if the search is on the idAttribute - and with operator "eq" / "in", since cassandra doesn't support multiple restricions - with an "eq" / "in" on the primary key field. %> + and with operator "eq" / "in", since cassandra doesn't support multiple restricions + with an "eq" / "in" on the primary key field. %> <%if(associations_temp[i].targetStorageType === 'cassandra'){%> const hasIdSearch = helper.parseFieldResolverSearchArgForCassandra(search, this.<%=associations_temp[i].sourceKey%>, models.<%=associations_temp[i].target_lc-%>.idAttribute()); <%}-%> @@ -341,7 +369,6 @@ const associationArgsDef = { "operator": "in" }); <%}else{-%> - //build new search filter let nsearch = helper.addSearchField({ "search": search, @@ -503,6 +530,7 @@ const associationArgsDef = { let promises_to_many = []; let promises_to_one = []; let get_to_many_associated_fk = 0; + let get_to_one_associated_fk = 0; <%if(associationsArguments["generic_to_many"].length > 0) {-%> let promises_generic_to_many = []; <%}-%> @@ -526,7 +554,11 @@ const associationArgsDef = { <%associations_temp = associationsArguments["to_one"] -%> <% for(let i=0; i < associations_temp.length; i++){ -%> <% if (associations_temp[i].delete_action==='reject'){-%> - promises_to_one.push(<%- nameLc %>.<%=associations_temp[i].name%>({}, context) ); + <%if(associations_temp[i].assocThroughArray){%> + get_to_one_associated_fk += [null, undefined].includes(<%- nameLc %>.<%=associations_temp[i].sourceKey%>) ? 0 : 1; + <%}else{-%> + promises_to_one.push(<%- nameLc %>.<%=associations_temp[i].name%>({}, context) ); + <%}-%> <%}-%> <%}-%> @@ -573,7 +605,7 @@ const associationArgsDef = { let get_cross_to_many_associated = result_cross_to_many.reduce( (accumulator, current_val )=> accumulator + current_val , 0 ); <%}-%> - return get_to_one_associated + get_to_many_associated_fk + get_to_many_associated <%if(associationsArguments["generic_to_many"].length > 0) {%>+ get_generic_to_many_associated<%}-%><%if(associationsArguments["generic_to_one"].length > 0) {%> + get_generic_to_one_associated<%}%><%if(associationsArguments["to_many_through_sql_cross_table"].length > 0) {%>+ get_cross_to_many_associated<%}-%>; + return get_to_one_associated + get_to_many_associated_fk + get_to_many_associated + get_to_one_associated_fk <%if(associationsArguments["generic_to_many"].length > 0) {%>+ get_generic_to_many_associated<%}-%><%if(associationsArguments["generic_to_one"].length > 0) {%> + get_generic_to_one_associated<%}%><%if(associationsArguments["to_many_through_sql_cross_table"].length > 0) {%>+ get_cross_to_many_associated<%}-%>; } /** @@ -604,7 +636,15 @@ const updateAssociations = async (id, context) => { const pagi_first = globals.LIMIT_RECORDS; <% for(let i=0; i < associations_one.length; i++){ -%> <% if (associations_one[i].delete_action==='update'){-%> - <% if (associations_one[i].holdsForeignKey) { %> + <%if(associations_one[i].assocThroughArray){%> + const <%=`${associations_one[i].name}_id`%> = <%- nameLc -%>_record.<%=associations_one[i].sourceKey%>; + if (<%=`${associations_one[i].name}_id`%>) { + await resolvers.update<%- nameCp -%>( + { <%- idAttribute -%>: id, remove<%=associations_one[i].name_cp%>: <%=`${associations_one[i].name}_id`%> }, + context + ); + } + <%} else if (associations_one[i].holdsForeignKey) { %> const <%=`${associations_one[i].name}_id`%> = <%- nameLc -%>_record.<%=associations_one[i].targetKey%>; if (<%=`${associations_one[i].name}_id`%>) { await resolvers.update<%- nameCp -%>( @@ -621,8 +661,8 @@ const updateAssociations = async (id, context) => { context ); } - <%}-%> - <%}-%> + <%}-%> + <%}-%> <%}-%> <% let associations_many = associationsArguments["to_many"]-%> diff --git a/views/create-schemas-ddm.ejs b/views/create-schemas-ddm.ejs index 59245c7b..63f300e6 100644 --- a/views/create-schemas-ddm.ejs +++ b/views/create-schemas-ddm.ejs @@ -208,7 +208,7 @@ type Mutation { delete<%- nameCp -%>(<%- idAttribute -%>: ID!): String! <%# bulkAssociatons -%> <%_for(let i=0; i < associationsArguments["to_one"].length; i++){_%><%_if (associationsArguments["to_one"][i].holdsForeignKey){_%> -bulkAssociate<%-nameCp-%>With<%-associationsArguments["to_one"][i].targetKey_cp-%>(bulkAssociationInput: [bulkAssociation<%-nameCp-%>With<%-associationsArguments["to_one"][i].targetKey_cp-%>Input], skipAssociationsExistenceChecks:Boolean = false): String! + bulkAssociate<%-nameCp-%>With<%-associationsArguments["to_one"][i].targetKey_cp-%>(bulkAssociationInput: [bulkAssociation<%-nameCp-%>With<%-associationsArguments["to_one"][i].targetKey_cp-%>Input], skipAssociationsExistenceChecks:Boolean = false): String! bulkDisAssociate<%-nameCp-%>With<%-associationsArguments["to_one"][i].targetKey_cp-%>(bulkAssociationInput: [bulkAssociation<%-nameCp-%>With<%-associationsArguments["to_one"][i].targetKey_cp-%>Input], skipAssociationsExistenceChecks:Boolean = false): String! <%_}-%><%_}-%>} `; diff --git a/views/create-schemas.ejs b/views/create-schemas.ejs index bfdd4bb8..3934ba2e 100644 --- a/views/create-schemas.ejs +++ b/views/create-schemas.ejs @@ -203,7 +203,7 @@ type <%- nameCp -%>Edge{ delete<%- nameCp -%>(<%- idAttribute -%>: ID!): String! <%# bulkAssociatons -%> <%_for(let i=0; i < associationsArguments["to_one"].length; i++){_%><%_if (associationsArguments["to_one"][i].holdsForeignKey){_%> -bulkAssociate<%-nameCp-%>With<%-associationsArguments["to_one"][i].targetKey_cp-%>(bulkAssociationInput: [bulkAssociation<%-nameCp-%>With<%-associationsArguments["to_one"][i].targetKey_cp-%>Input], skipAssociationsExistenceChecks:Boolean = false): String! + bulkAssociate<%-nameCp-%>With<%-associationsArguments["to_one"][i].targetKey_cp-%>(bulkAssociationInput: [bulkAssociation<%-nameCp-%>With<%-associationsArguments["to_one"][i].targetKey_cp-%>Input], skipAssociationsExistenceChecks:Boolean = false): String! bulkDisAssociate<%-nameCp-%>With<%-associationsArguments["to_one"][i].targetKey_cp-%>(bulkAssociationInput: [bulkAssociation<%-nameCp-%>With<%-associationsArguments["to_one"][i].targetKey_cp-%>Input], skipAssociationsExistenceChecks:Boolean = false): String! <%_}-%><%_}-%> } `; diff --git a/views/includes/bulkAssociations-models.ejs b/views/includes/bulkAssociations-models.ejs index 745fb3fc..c9962e46 100644 --- a/views/includes/bulkAssociations-models.ejs +++ b/views/includes/bulkAssociations-models.ejs @@ -1,6 +1,6 @@ <%# bulkAssociations functions for model-layer; paramater: op (add or remove)_%> <%for(let i=0; i < associationsArguments["to_one"].length; i++){_%> - <% if (associationsArguments["to_one"][i].holdsForeignKey && associationsArguments["to_one"][i].type!=="one_to_one") { _%> + <% if (!associationsArguments["to_one"][i].assocThroughArray && associationsArguments["to_one"][i].holdsForeignKey && associationsArguments["to_one"][i].type!=="one_to_one") { _%> /** * bulk<% if(op === 'remove'){ %>Dis<% } %>Associate<%-nameCp-%>With<%-associationsArguments["to_one"][i].targetKey_cp-%> - bulk<% if(op === 'remove'){ %>Dis<% } %>Associaton of given ids * diff --git a/views/includes/bulkAssociations-resolvers.ejs b/views/includes/bulkAssociations-resolvers.ejs index d87a42d8..137e062e 100644 --- a/views/includes/bulkAssociations-resolvers.ejs +++ b/views/includes/bulkAssociations-resolvers.ejs @@ -1,5 +1,5 @@ <%_for(let i=0; i < associationsArguments["to_one"].length; i++){_%> - <%_ if (associationsArguments["to_one"][i].holdsForeignKey){_%> + <%_ if (!associationsArguments["to_one"][i].assocThroughArray && associationsArguments["to_one"][i].holdsForeignKey){_%> /** * bulk<% if(op === 'remove'){ %>Dis<% } %>Associate<%-nameCp-%>With<%-associationsArguments["to_one"][i].targetKey_cp-%> - bulk<% if(op === 'remove'){ %>Dis<% } %>Associaton resolver of given ids * diff --git a/views/includes/create-adapter-fields-mutations.ejs b/views/includes/create-adapter-fields-mutations.ejs index 1bab9a81..ff19e180 100644 --- a/views/includes/create-adapter-fields-mutations.ejs +++ b/views/includes/create-adapter-fields-mutations.ejs @@ -1,5 +1,221 @@ <%for(let i=0; i < associationsArguments["to_one"].length; i++){-%> - <% if (associationsArguments["to_one"][i].holdsForeignKey) { -%> + <% if (associationsArguments["to_one"][i].assocThroughArray) { -%> + /** + * <%- op %>_<%-associationsArguments["to_one"][i].sourceKey-%> - field Mutation (adapter-layer) for to_one associationsArguments to <%- op %> + * + * @param {Id} <%- idAttribute-%> IdAttribute of the root model to be updated + * @param {Id} <%-associationsArguments["to_one"][i].sourceKey-%> Foreign Key (stored in "Me") of the Association to be updated. + * @param {BenignErrorReporter} benignErrorReporter Error Reporter used for reporting Errors from remote zendro services + * @param {string} token The token used for authorization + <% if(storageType != 'zendro-webservice-adapter' || storageType != 'ddm-adapter'){-%> + * @param {boolean} handle_inverse Handle inverse association + <% } _%> + */ + <%# + /** + *check the type of adapter and handle zendro-webservice-adapter and ddm-adapter + */ + -%> + <% if(storageType === 'zendro-webservice-adapter' || storageType === 'ddm-adapter'){-%> + static async <%- op -%>_<%-associationsArguments["to_one"][i].sourceKey-%>(<%- idAttribute-%>, <%-associationsArguments["to_one"][i].sourceKey-%>, benignErrorReporter, token) { + let query = ` + mutation + update<%- nameCp-%>{ + update<%- nameCp-%>( + <%- idAttribute -%>:"${<%- idAttribute -%>}" + <%- op %><%= associationsArguments["to_one"][i].name_cp%>:"${<%-associationsArguments["to_one"][i].sourceKey-%>}" + skipAssociationsExistenceChecks: true + ){ + <%- idAttribute -%> + <%-associationsArguments["to_one"][i].sourceKey-%> + } + }` + + try { + // Send an HTTP request to the remote server + let opts = { + headers: { + "Content-Type": "application/json", + Accept: "application/graphql", + }, + }; + if (token) { + opts.headers["authorization"] = token; + } + let response = await axios.post( + remoteZendroURL, + { + query: query, + }, + opts + ); + //check if remote service returned benign Errors in the response and add them to the benignErrorReporter + if(helper.isNonEmptyArray(response.data.errors)) { + benignErrorReporter.push(errorHelper.handleRemoteErrors(response.data.errors, remoteZendroURL)); + } + // STATUS-CODE is 200 + // NO ERROR as such has been detected by the server (Express) + // check if data was send + if(response && response.data && response.data.data) { + return 1; + } else { + benignErrorReporter.push({ + message: `Remote zendro-server (${remoteZendroURL}) did not respond with data.`, + }); + } + } catch(error) { + //handle caught errors + benignErrorReporter.push(errorHelper.handleCaughtErrorAndBenignErrors(error, benignErrorReporter, remoteZendroURL)); + } + } + <%}-%> + <%# /** End of the zendro-webservice-adapter case and the ddm-adapter case */ -%> + + + <%# + /** + *check the type of adapter and handle sql-adapter + */ + -%> + <%if(storageType === 'sql-adapter'){-%> + static async <%- op -%>_<%-associationsArguments["to_one"][i].sourceKey-%>(<%- idAttribute-%>, <%-associationsArguments["to_one"][i].sourceKey-%>, benignErrorReporter, token, handle_inverse = true) { + try{ + //handle inverse association + if(handle_inverse){ + <%if (associationsArguments["to_one"][i].type==="many_to_one") {-%> + await models.<%-associationsArguments["to_one"][i].target_lc-%>.<%- op %>_<%-associationsArguments["to_one"][i].targetKey%>(<%-associationsArguments["to_one"][i].sourceKey-%>, [`${<%- idAttribute-%>}`], benignErrorReporter, token, false); + <%} else if (associationsArguments["to_one"][i].type==="one_to_one") {-%> + await models.<%-associationsArguments["to_one"][i].target_lc-%>.<%- op %>_<%-associationsArguments["to_one"][i].targetKey%>(<%-associationsArguments["to_one"][i].sourceKey-%>, <%- idAttribute-%>, benignErrorReporter, token, false); + <%}-%> + } + let updated = await super.update({ <%-associationsArguments["to_one"][i].sourceKey-%>: <% if (op == 'remove') { %>null<% } else { %><%-associationsArguments["to_one"][i].sourceKey-%><%}-%>},{where: {<%- idAttribute -%>: <%- idAttribute -%><% if (op == 'remove') { -%>,<%-associationsArguments["to_one"][i].sourceKey-%>: <%-associationsArguments["to_one"][i].sourceKey-%> <%}-%>}}); + return updated[0]; + } catch (error) { + benignErrorReporter.push({ + message:error + }); + } + } + <%}-%> + <%# /*** End of the the sql-adapter case */ -%> + + <%# + /** + * check the type of adapter and handle mongodb-adapter + */ + -%> + <%if(storageType === 'mongodb-adapter'){-%> + static async <%- op -%>_<%-associationsArguments["to_one"][i].sourceKey-%>(<%- idAttribute-%>, <%-associationsArguments["to_one"][i].sourceKey-%>, benignErrorReporter, token, handle_inverse = true) { + try { + //handle inverse association + if(handle_inverse){ + <%if (associationsArguments["to_one"][i].type==="many_to_one") {-%> + await models.<%-associationsArguments["to_one"][i].target_lc-%>.<%- op %>_<%-associationsArguments["to_one"][i].targetKey%>(<%-associationsArguments["to_one"][i].sourceKey-%>, [`${<%- idAttribute-%>}`], benignErrorReporter, token, false); + <%} else if (associationsArguments["to_one"][i].type==="one_to_one") {-%> + await models.<%-associationsArguments["to_one"][i].target_lc-%>.<%- op %>_<%-associationsArguments["to_one"][i].targetKey%>(<%-associationsArguments["to_one"][i].sourceKey-%>, <%- idAttribute-%>, benignErrorReporter, token, false); + <%}-%> + } + const db = await this.storageHandler + const collection = await db.collection("<%- model_name_in_storage -%>") + const updatedContent = {<%-associationsArguments["to_one"][i].sourceKey-%>: <% if (op === 'remove') + { -%>null<% } else { %><%-associationsArguments["to_one"][i].sourceKey-%><%}-%>} + const response = await collection.updateOne({<%- idAttribute -%>: <%- idAttribute -%><% if (op === 'remove') + {-%>,<%-associationsArguments["to_one"][i].sourceKey-%>: <%-associationsArguments["to_one"][i].sourceKey-%> <%}-%>}, {$set: updatedContent}); + if (response.result.ok !== 1){ + benignErrorReporter.push({ + message:`Record with ID = ${<%- idAttribute-%>} has not been updated` + }); + } + return response.modifiedCount; + } catch (error) { + benignErrorReporter.push({ + message:error + }); + } + } + <%}-%> + <%# /*** End of the the mongodb-adapter case */ -%> + + <%# + /** + * check the type of adapter and handle neo4j-adapter + */ + -%> + <%if(storageType === 'neo4j-adapter'){-%> + static async <%- op -%>_<%-associationsArguments["to_one"][i].sourceKey-%>(<%- idAttribute-%>, <%-associationsArguments["to_one"][i].sourceKey-%>, benignErrorReporter, token, handle_inverse = true) { + //handle inverse association + if(handle_inverse){ + <%if (associationsArguments["to_one"][i].type==="many_to_one") {-%> + await models.<%-associationsArguments["to_one"][i].target_lc-%>.<%- op %>_<%-associationsArguments["to_one"][i].targetKey%>(<%-associationsArguments["to_one"][i].sourceKey-%>, [`${<%- idAttribute-%>}`], benignErrorReporter, token, false); + <%} else if (associationsArguments["to_one"][i].type==="one_to_one") {-%> + await models.<%-associationsArguments["to_one"][i].target_lc-%>.<%- op %>_<%-associationsArguments["to_one"][i].targetKey%>(<%-associationsArguments["to_one"][i].sourceKey-%>, <%- idAttribute-%>, benignErrorReporter, token, false); + <%}-%> + } + const driver = await this.storageHandler; + const session = driver.session({ + database: config.database, + defaultAccessMode: neo4j.session.WRITE, + }); + let foreignKey = `MATCH (n:<%- model_name_in_storage -%>) WHERE n.<%-idAttribute%> = $id + SET n.<%-associationsArguments["to_one"][i].sourceKey-%> = $target RETURN count(n)` + const target_model = models.<%-associationsArguments["to_one"][i].target_lc-%>.definition.model_name_in_storage ?? "<%-associationsArguments["to_one"][i].target_lc_pl-%>"; + + <% if (op == 'remove') { %>let delete_relationships = `MATCH (a:<%- model_name_in_storage -%>)-[r:${"<%-associationsArguments["to_one"][i].name-%>".toUpperCase() + "_EDGE"}]-> (b:${target_model}) + WHERE a.<%-idAttribute%> = $id AND b.${models.<%-associationsArguments["to_one"][i].target-%>.idAttribute()} = $target + DELETE r`<% } + else { %>let create_relationships = `MATCH (a:<%- model_name_in_storage -%>), (b:${target_model}) + WHERE a.<%-idAttribute%> = $id AND b.${models.<%-associationsArguments["to_one"][i].target-%>.idAttribute()} = $target + CREATE (a)-[r:${"<%-associationsArguments["to_one"][i].name-%>".toUpperCase() + "_EDGE"}]->(b)`<%}-%> + + try{ + const result = await session.run(foreignKey, {id: <%-idAttribute%>, target: <% if (op == 'remove') { _%>null<% } else { %><%-associationsArguments["to_one"][i].sourceKey-%><%}-%>}); + await session.run(<% if (op == 'remove') { %>delete_relationships<% } else { %>create_relationships<%}-%>, + { + id: <%-idAttribute%>, + target: <%-associationsArguments["to_one"][i].sourceKey-%>, + }) + return result.records[0].get(0); + } catch (error) { + benignErrorReporter.push({ + message:error + }); + } finally { + await session.close(); + } + } + <%}-%> + <%# /*** End of the the neo4j-adapter case */ -%> + + <%# + /** + * check the type of adapter and handle cassandra-adapter + */ + -%> + <%if(storageType === 'cassandra-adapter'){-%> + static async <%- op -%>_<%-associationsArguments["to_one"][i].sourceKey-%>(<%- idAttribute-%>, <%-associationsArguments["to_one"][i].sourceKey-%>, benignErrorReporter, token, handle_inverse = true) { + try{ + //handle inverse association + if(handle_inverse){ + <%if (associationsArguments["to_one"][i].type==="many_to_one") {-%> + await models.<%-associationsArguments["to_one"][i].target_lc-%>.<%- op %>_<%-associationsArguments["to_one"][i].targetKey%>(<%-associationsArguments["to_one"][i].sourceKey-%>, [`${<%- idAttribute-%>}`], benignErrorReporter, token, false); + <%} else if (associationsArguments["to_one"][i].type==="one_to_one") {-%> + await models.<%-associationsArguments["to_one"][i].target_lc-%>.<%- op %>_<%-associationsArguments["to_one"][i].targetKey%>(<%-associationsArguments["to_one"][i].sourceKey-%>, <%- idAttribute-%>, benignErrorReporter, token, false); + <%}-%> + } + const mutationCql = `UPDATE <%- model_name_in_storage -%> SET <%-associationsArguments["to_one"][i].sourceKey-%> = ? WHERE <%- idAttribute -%> = ?`; + await this.storageHandler.execute(mutationCql, [<% if (op == 'remove') { -%>null<% } else { %><%-associationsArguments["to_one"][i].sourceKey-%><%}-%>, <%- idAttribute -%>], {prepare: true}); + const checkCql = `SELECT COUNT(*) FROM <%- model_name_in_storage -%> WHERE <%- idAttribute-%> = ?`; + let result = await this.storageHandler.execute(checkCql, [<%- idAttribute-%>]); + return parseInt(result.first()["count"]); + } catch (error) { + benignErrorReporter.push({ + message: error, + }); + } + } + <%}-%> + <%# /*** End of the the cassandra-adapter case */ -%> + <%} else if (associationsArguments["to_one"][i].holdsForeignKey) { -%> /** * <%- op %>_<%-associationsArguments["to_one"][i].targetKey-%> - field Mutation (adapter-layer) for to_one associationsArguments to <%- op %> * @@ -194,16 +410,24 @@ * * @param {Id} <%- idAttribute-%> IdAttribute of the root model to be updated * @param {Array} <%-associationsArguments["to_many"][i].sourceKey-%> Array foreign Key (stored in "Me") of the Association to be updated. + * @param {string} token The token used for authorization + <% if(storageType != 'zendro-webservice-adapter' || storageType != 'ddm-adapter'){-%> + * @param {boolean} handle_inverse Handle inverse association + <% } _%> */ <%if(storageType === 'sql-adapter'){-%> - static async <%- op -%>_<%-associationsArguments["to_many"][i].sourceKey-%>(<%- idAttribute-%>, <%-associationsArguments["to_many"][i].sourceKey-%>, benignErrorReporter, handle_inverse = true) { + static async <%- op -%>_<%-associationsArguments["to_many"][i].sourceKey-%>(<%- idAttribute-%>, <%-associationsArguments["to_many"][i].sourceKey-%>, benignErrorReporter, token, handle_inverse = true) { //handle inverse association if(handle_inverse){ let promises = []; <%-associationsArguments["to_many"][i].sourceKey-%>.forEach( idx =>{ - promises.push(models.<%-associationsArguments["to_many"][i].target_lc-%>.<%- op %>_<%-associationsArguments["to_many"][i].targetKey%>(idx, [`${<%- idAttribute-%>}`], benignErrorReporter, false) ); + <%if (associationsArguments["to_many"][i].type==="many_to_many") {-%> + promises.push(models.<%-associationsArguments["to_many"][i].target_lc-%>.<%- op %>_<%-associationsArguments["to_many"][i].targetKey%>(idx, [`${<%- idAttribute-%>}`], benignErrorReporter, token, false) ); + <%} else if (associationsArguments["to_many"][i].type==="one_to_many") {-%> + promises.push(models.<%-associationsArguments["to_many"][i].target_lc-%>.<%- op %>_<%-associationsArguments["to_many"][i].targetKey%>(idx, <%- idAttribute-%>, benignErrorReporter, token, false) ); + <%}-%> }); await Promise.all(promises); } @@ -218,13 +442,17 @@ <%}-%> <%# /*** End of the the sql-adapter case */ -%> <%if(storageType === 'mongodb-adapter'){-%> - static async <%- op -%>_<%-associationsArguments["to_many"][i].sourceKey-%>(<%- idAttribute-%>, <%-associationsArguments["to_many"][i].sourceKey-%>, benignErrorReporter, handle_inverse = true) { + static async <%- op -%>_<%-associationsArguments["to_many"][i].sourceKey-%>(<%- idAttribute-%>, <%-associationsArguments["to_many"][i].sourceKey-%>, benignErrorReporter, token, handle_inverse = true) { //handle inverse association if(handle_inverse){ let promises = []; <%-associationsArguments["to_many"][i].sourceKey-%>.forEach( idx =>{ - promises.push(models.<%-associationsArguments["to_many"][i].target_lc-%>.<%- op %>_<%-associationsArguments["to_many"][i].targetKey%>(idx, [`${<%- idAttribute-%>}`], benignErrorReporter, false) ); + <%if (associationsArguments["to_many"][i].type==="many_to_many") {-%> + promises.push(models.<%-associationsArguments["to_many"][i].target_lc-%>.<%- op %>_<%-associationsArguments["to_many"][i].targetKey%>(idx, [`${<%- idAttribute-%>}`], benignErrorReporter, token, false) ); + <%} else if (associationsArguments["to_many"][i].type==="one_to_many") {-%> + promises.push(models.<%-associationsArguments["to_many"][i].target_lc-%>.<%- op %>_<%-associationsArguments["to_many"][i].targetKey%>(idx, <%- idAttribute-%>, benignErrorReporter, token, false) ); + <%}-%> }); await Promise.all(promises); } @@ -244,12 +472,16 @@ <%}-%> <%# /*** End of the the mongodb-adapter case */ -%> <%if(storageType === 'neo4j-adapter'){-%> - static async <%- op -%>_<%-associationsArguments["to_many"][i].sourceKey-%>(<%- idAttribute-%>, <%-associationsArguments["to_many"][i].sourceKey-%>, benignErrorReporter, handle_inverse = true) { + static async <%- op -%>_<%-associationsArguments["to_many"][i].sourceKey-%>(<%- idAttribute-%>, <%-associationsArguments["to_many"][i].sourceKey-%>, benignErrorReporter, token, handle_inverse = true) { //handle inverse association if(handle_inverse){ let promises = []; <%-associationsArguments["to_many"][i].sourceKey-%>.forEach( idx =>{ - promises.push(models.<%-associationsArguments["to_many"][i].target_lc-%>.<%- op %>_<%-associationsArguments["to_many"][i].targetKey%>(idx, [`${<%- idAttribute-%>}`], benignErrorReporter, false) ); + <%if (associationsArguments["to_many"][i].type==="many_to_many") {-%> + promises.push(models.<%-associationsArguments["to_many"][i].target_lc-%>.<%- op %>_<%-associationsArguments["to_many"][i].targetKey%>(idx, [`${<%- idAttribute-%>}`], benignErrorReporter, token, false) ); + <%} else if (associationsArguments["to_many"][i].type==="one_to_many") {-%> + promises.push(models.<%-associationsArguments["to_many"][i].target_lc-%>.<%- op %>_<%-associationsArguments["to_many"][i].targetKey%>(idx, <%- idAttribute-%>, benignErrorReporter, token, false) ); + <%}-%> }); await Promise.all(promises); } @@ -350,12 +582,16 @@ */ -%> <%if(storageType === 'cassandra-adapter'){-%> - static async <%- op -%>_<%-associationsArguments["to_many"][i].sourceKey-%>(<%- idAttribute-%>, <%-associationsArguments["to_many"][i].sourceKey-%>, benignErrorReporter, handle_inverse = true) { + static async <%- op -%>_<%-associationsArguments["to_many"][i].sourceKey-%>(<%- idAttribute-%>, <%-associationsArguments["to_many"][i].sourceKey-%>, benignErrorReporter, token, handle_inverse = true) { //handle inverse association if(handle_inverse){ let promises = []; <%-associationsArguments["to_many"][i].sourceKey-%>.forEach( idx =>{ - promises.push(models.<%-associationsArguments["to_many"][i].target_lc-%>.<%- op %>_<%-associationsArguments["to_many"][i].targetKey%>(idx, [`${<%- idAttribute-%>}`], benignErrorReporter, false) ); + <%if (associationsArguments["to_many"][i].type==="many_to_many") {-%> + promises.push(models.<%-associationsArguments["to_many"][i].target_lc-%>.<%- op %>_<%-associationsArguments["to_many"][i].targetKey%>(idx, [`${<%- idAttribute-%>}`], benignErrorReporter, token, false) ); + <%} else if (associationsArguments["to_many"][i].type==="one_to_many") {-%> + promises.push(models.<%-associationsArguments["to_many"][i].target_lc-%>.<%- op %>_<%-associationsArguments["to_many"][i].targetKey%>(idx, <%- idAttribute-%>, benignErrorReporter, token, false) ); + <%}-%> }); await Promise.all(promises); } diff --git a/views/includes/create-ddm-models-fields-mutations.ejs b/views/includes/create-ddm-models-fields-mutations.ejs index 5a769013..df604607 100644 --- a/views/includes/create-ddm-models-fields-mutations.ejs +++ b/views/includes/create-ddm-models-fields-mutations.ejs @@ -1,5 +1,25 @@ <%for(let i=0; i < associationsArguments["to_one"].length; i++){-%> - <% if (associationsArguments["to_one"][i].holdsForeignKey) { -%> + <% if (associationsArguments["to_one"][i].assocThroughArray) { -%> + /** + * <%- op %>_<%-associationsArguments["to_one"][i].sourceKey-%> - field Mutation (model-layer) for to_one associationsArguments to <%- op %> + * + * @param {Id} <%- idAttribute-%> IdAttribute of the root model to be updated + * @param {Id} <%-associationsArguments["to_one"][i].sourceKey-%> Foreign Key (stored in "Me") of the Association to be updated. + * @param {BenignErrorReporter} benignErrorReporter Error Reporter used for reporting Errors from remote zendro services + * @param {string} token The token used for authorization + * @param {boolean} handle_inverse Handle inverse association + */ + static async <%- op -%>_<%-associationsArguments["to_one"][i].sourceKey-%>(<%- idAttribute-%>, <%-associationsArguments["to_one"][i].sourceKey-%>, benignErrorReporter, token, handle_inverse) { + try { + let responsibleAdapter = this.adapterForIri(<%- idAttribute-%>); + return await adapters[responsibleAdapter].<%- op -%>_<%-associationsArguments["to_one"][i].sourceKey-%>(<%- idAttribute-%>, <%-associationsArguments["to_one"][i].sourceKey-%>, benignErrorReporter, token, handle_inverse); + } catch (error) { + benignErrorReporter.push({ + message: error, + }); + } + } + <% } else if (associationsArguments["to_one"][i].holdsForeignKey) { -%> /** * <%- op %>_<%-associationsArguments["to_one"][i].targetKey-%> - field Mutation (model-layer) for to_one associationsArguments to <%- op %> * @@ -7,11 +27,12 @@ * @param {Id} <%-associationsArguments["to_one"][i].targetKey-%> Foreign Key (stored in "Me") of the Association to be updated. * @param {BenignErrorReporter} benignErrorReporter Error Reporter used for reporting Errors from remote zendro services * @param {string} token The token used for authorization + * @param {boolean} handle_inverse Handle inverse association */ - static async <%- op -%>_<%-associationsArguments["to_one"][i].targetKey-%>(<%- idAttribute-%>, <%-associationsArguments["to_one"][i].targetKey-%>, benignErrorReporter, token) { + static async <%- op -%>_<%-associationsArguments["to_one"][i].targetKey-%>(<%- idAttribute-%>, <%-associationsArguments["to_one"][i].targetKey-%>, benignErrorReporter, token, handle_inverse) { try { let responsibleAdapter = this.adapterForIri(<%- idAttribute-%>); - return await adapters[responsibleAdapter].<%- op -%>_<%-associationsArguments["to_one"][i].targetKey-%>(<%- idAttribute-%>, <%-associationsArguments["to_one"][i].targetKey-%>, benignErrorReporter, token); + return await adapters[responsibleAdapter].<%- op -%>_<%-associationsArguments["to_one"][i].targetKey-%>(<%- idAttribute-%>, <%-associationsArguments["to_one"][i].targetKey-%>, benignErrorReporter, token, handle_inverse); } catch (error) { benignErrorReporter.push({ message: error, @@ -30,10 +51,11 @@ * @param {Array} <%-associationsArguments["to_many"][i].sourceKey-%> Foreign Key (stored in "Me") of the Association to be updated. * @param {BenignErrorReporter} benignErrorReporter Error Reporter used for reporting Errors from remote zendro services * @param {string} token The token used for authorization + * @param {boolean} handle_inverse Handle inverse association */ - static async <%- op -%>_<%-associationsArguments["to_many"][i].sourceKey-%>(<%- idAttribute-%>, <%-associationsArguments["to_many"][i].sourceKey-%>, benignErrorReporter, handle_inverse = true, token) { + static async <%- op -%>_<%-associationsArguments["to_many"][i].sourceKey-%>(<%- idAttribute-%>, <%-associationsArguments["to_many"][i].sourceKey-%>, benignErrorReporter, token, handle_inverse) { let responsibleAdapter = this.adapterForIri(<%- idAttribute-%>); - return await adapters[responsibleAdapter].<%- op -%>_<%-associationsArguments["to_many"][i].sourceKey-%>(<%- idAttribute-%>, <%-associationsArguments["to_many"][i].sourceKey-%>, benignErrorReporter, handle_inverse, token); + return await adapters[responsibleAdapter].<%- op -%>_<%-associationsArguments["to_many"][i].sourceKey-%>(<%- idAttribute-%>, <%-associationsArguments["to_many"][i].sourceKey-%>, benignErrorReporter, token, handle_inverse); } <%}-%> <%}-%> diff --git a/views/includes/create-models-cassandra-fieldMutations.ejs b/views/includes/create-models-cassandra-fieldMutations.ejs index ab7d5236..4fb4c1c3 100644 --- a/views/includes/create-models-cassandra-fieldMutations.ejs +++ b/views/includes/create-models-cassandra-fieldMutations.ejs @@ -1,5 +1,36 @@ <%for(let i=0; i < associationsArguments["to_one"].length; i++){-%> - <% if (associationsArguments["to_one"][i].holdsForeignKey) { -%> + <% if (associationsArguments["to_one"][i].assocThroughArray) { -%> + /** + * <%- op %>_<%-associationsArguments["to_one"][i].sourceKey-%> - field Mutation (model-layer) for to_one associationsArguments to <%- op %> + * + * @param {Id} <%- idAttribute-%> IdAttribute of the root model to be updated + * @param {Id} <%-associationsArguments["to_one"][i].sourceKey-%> Foreign Key (stored in "Me") of the Association to be updated. + * @param {BenignErrorReporter} benignErrorReporter Error Reporter used for reporting Errors + * @param {string} token The token used for authorization + * @param {boolean} handle_inverse Handle inverse association + */ + static async <%- op -%>_<%-associationsArguments["to_one"][i].sourceKey-%>(<%- idAttribute-%>, <%-associationsArguments["to_one"][i].sourceKey-%>, benignErrorReporter, token, handle_inverse = true) { + try { + //handle inverse association + if(handle_inverse){ + <%if (associationsArguments["to_one"][i].type==="many_to_one") {-%> + await models.<%-associationsArguments["to_one"][i].target_lc-%>.<%- op %>_<%-associationsArguments["to_one"][i].targetKey%>(<%-associationsArguments["to_one"][i].sourceKey-%>, [`${<%- idAttribute-%>}`], benignErrorReporter, token, false); + <%} else if (associationsArguments["to_one"][i].type==="one_to_one") {-%> + await models.<%-associationsArguments["to_one"][i].target_lc-%>.<%- op %>_<%-associationsArguments["to_one"][i].targetKey%>(<%-associationsArguments["to_one"][i].sourceKey-%>, <%- idAttribute-%>, benignErrorReporter, token, false); + <%}-%> + } + const mutationCql = `UPDATE "<%- model_name_in_storage -%>" SET <%-associationsArguments["to_one"][i].sourceKey-%> = ? WHERE <%- idAttribute -%> = ?`; + await this.storageHandler.execute(mutationCql, [<% if (op == 'remove') { -%>null<% } else { %><%-associationsArguments["to_one"][i].sourceKey-%><%}-%>, <%- idAttribute -%>], {prepare: true}); + const checkCql = `SELECT COUNT(*) FROM "<%- model_name_in_storage -%>" WHERE <%- idAttribute-%> = ?`; + let result = await this.storageHandler.execute(checkCql, [<%- idAttribute-%>]); + return parseInt(result.first()["count"]); + } catch (error) { + benignErrorReporter.push({ + message: error, + }); + } + } + <% } else if (associationsArguments["to_one"][i].holdsForeignKey) { -%> /** * <%- op %>_<%-associationsArguments["to_one"][i].targetKey-%> - field Mutation (model-layer) for to_one associationsArguments to <%- op %> * @@ -30,13 +61,19 @@ * * @param {Id} <%- idAttribute-%> IdAttribute of the root model to be updated * @param {Array} <%-associationsArguments["to_many"][i].sourceKey-%> Array foreign Key (stored in "Me") of the Association to be updated. + * @param {string} token The token used for authorization + * @param {boolean} handle_inverse Handle inverse association */ - static async <%- op -%>_<%-associationsArguments["to_many"][i].sourceKey-%>(<%- idAttribute-%>, <%-associationsArguments["to_many"][i].sourceKey-%>, benignErrorReporter, handle_inverse = true) { + static async <%- op -%>_<%-associationsArguments["to_many"][i].sourceKey-%>(<%- idAttribute-%>, <%-associationsArguments["to_many"][i].sourceKey-%>, benignErrorReporter, token, handle_inverse = true) { //handle inverse association if(handle_inverse){ let promises = []; <%-associationsArguments["to_many"][i].sourceKey-%>.forEach( idx =>{ - promises.push(models.<%-associationsArguments["to_many"][i].target_lc-%>.<%- op %>_<%-associationsArguments["to_many"][i].targetKey%>(idx, [`${<%- idAttribute-%>}`], benignErrorReporter, false) ); + <%if (associationsArguments["to_many"][i].type==="many_to_many") {-%> + promises.push(models.<%-associationsArguments["to_many"][i].target_lc-%>.<%- op %>_<%-associationsArguments["to_many"][i].targetKey%>(idx, [`${<%- idAttribute-%>}`], benignErrorReporter, token, false) ); + <%} else if (associationsArguments["to_many"][i].type==="one_to_many") {-%> + promises.push(models.<%-associationsArguments["to_many"][i].target_lc-%>.<%- op %>_<%-associationsArguments["to_many"][i].targetKey%>(idx, <%- idAttribute-%>, benignErrorReporter, token, false) ); + <%}-%> }); await Promise.all(promises); } diff --git a/views/includes/create-models-fieldMutations-mongodb.ejs b/views/includes/create-models-fieldMutations-mongodb.ejs index f0468435..df3cccd5 100644 --- a/views/includes/create-models-fieldMutations-mongodb.ejs +++ b/views/includes/create-models-fieldMutations-mongodb.ejs @@ -1,5 +1,43 @@ <%for(let i=0; i < associationsArguments["to_one"].length; i++){-%> - <% if (associationsArguments["to_one"][i].holdsForeignKey) { -%> + <% if (associationsArguments["to_one"][i].assocThroughArray) { -%> + /** + * <%- op %>_<%-associationsArguments["to_one"][i].sourceKey-%> - field Mutation (model-layer) for to_one associationsArguments to <%- op %> + * + * @param {Id} <%- idAttribute-%> IdAttribute of the root model to be updated + * @param {Id} <%-associationsArguments["to_one"][i].sourceKey-%> Foreign Key (stored in "Me") of the Association to be updated. + * @param {BenignErrorReporter} benignErrorReporter Error Reporter used for reporting Errors + * @param {string} token The token used for authorization + * @param {boolean} handle_inverse Handle inverse association + */ + static async <%- op -%>_<%-associationsArguments["to_one"][i].sourceKey-%>(<%- idAttribute-%>, <%-associationsArguments["to_one"][i].sourceKey-%>, benignErrorReporter, token, handle_inverse = true) { + try { + //handle inverse association + if(handle_inverse){ + <%if (associationsArguments["to_one"][i].type==="many_to_one") {-%> + await models.<%-associationsArguments["to_one"][i].target_lc-%>.<%- op %>_<%-associationsArguments["to_one"][i].targetKey%>(<%-associationsArguments["to_one"][i].sourceKey-%>, [`${<%- idAttribute-%>}`], benignErrorReporter, token, false); + <%} else if (associationsArguments["to_one"][i].type==="one_to_one") {-%> + await models.<%-associationsArguments["to_one"][i].target_lc-%>.<%- op %>_<%-associationsArguments["to_one"][i].targetKey%>(<%-associationsArguments["to_one"][i].sourceKey-%>, <%- idAttribute-%>, benignErrorReporter, token, false); + <%}-%> + } + const db = await this.storageHandler; + const collection = await db.collection("<%- model_name_in_storage -%>"); + const updatedContent = {<%-associationsArguments["to_one"][i].sourceKey-%>: <% if (op === 'remove') + { -%>null<% } else { %><%-associationsArguments["to_one"][i].sourceKey-%><%}-%>} + const response = await collection.updateOne({<%- idAttribute -%>: <%- idAttribute -%><% if (op === 'remove') + {-%>,<%-associationsArguments["to_one"][i].sourceKey-%>: <%-associationsArguments["to_one"][i].sourceKey-%> <%}-%>}, {$set: updatedContent}); + if (response.result.ok !== 1){ + benignErrorReporter.push({ + message: `Record with ID = ${<%- idAttribute-%>} has not been updated` + }); + } + return response.modifiedCount; + } catch (error) { + benignErrorReporter.push({ + message:error + }); + } + } + <%} else if (associationsArguments["to_one"][i].holdsForeignKey) { -%> /** * <%- op %>_<%-associationsArguments["to_one"][i].targetKey-%> - field Mutation (model-layer) for to_one associationsArguments to <%- op %> * @@ -37,13 +75,19 @@ * * @param {Id} <%- idAttribute-%> IdAttribute of the root model to be updated * @param {Array} <%-associationsArguments["to_many"][i].sourceKey-%> Array foreign Key (stored in "Me") of the Association to be updated. + * @param {string} token The token used for authorization + * @param {boolean} handle_inverse Handle inverse association */ - static async <%- op -%>_<%-associationsArguments["to_many"][i].sourceKey-%>(<%- idAttribute-%>, <%-associationsArguments["to_many"][i].sourceKey-%>, benignErrorReporter, handle_inverse = true) { + static async <%- op -%>_<%-associationsArguments["to_many"][i].sourceKey-%>(<%- idAttribute-%>, <%-associationsArguments["to_many"][i].sourceKey-%>, benignErrorReporter, token, handle_inverse = true) { //handle inverse association if(handle_inverse){ let promises = []; <%-associationsArguments["to_many"][i].sourceKey-%>.forEach( idx =>{ - promises.push(models.<%-associationsArguments["to_many"][i].target_lc-%>.<%- op %>_<%-associationsArguments["to_many"][i].targetKey%>(idx, [`${<%- idAttribute-%>}`], benignErrorReporter, false) ); + <%if (associationsArguments["to_many"][i].type==="many_to_many") {-%> + promises.push(models.<%-associationsArguments["to_many"][i].target_lc-%>.<%- op %>_<%-associationsArguments["to_many"][i].targetKey%>(idx, [`${<%- idAttribute-%>}`], benignErrorReporter, token, false) ); + <%} else if (associationsArguments["to_many"][i].type==="one_to_many") {-%> + promises.push(models.<%-associationsArguments["to_many"][i].target_lc-%>.<%- op %>_<%-associationsArguments["to_many"][i].targetKey%>(idx, <%- idAttribute-%>, benignErrorReporter, token, false) ); + <%}-%> }); await Promise.all(promises); } diff --git a/views/includes/create-models-fieldMutations-neo4j.ejs b/views/includes/create-models-fieldMutations-neo4j.ejs index 2c3491c2..915a4be9 100644 --- a/views/includes/create-models-fieldMutations-neo4j.ejs +++ b/views/includes/create-models-fieldMutations-neo4j.ejs @@ -1,5 +1,56 @@ <%for(let i=0; i < associationsArguments["to_one"].length; i++){-%> - <% if (associationsArguments["to_one"][i].holdsForeignKey) { -%> + <% if (associationsArguments["to_one"][i].assocThroughArray) { -%> + /** + * <%- op %>_<%-associationsArguments["to_one"][i].sourceKey-%> - field Mutation (model-layer) for to_one associationsArguments to <%- op %> + * + * @param {Id} <%- idAttribute-%> IdAttribute of the root model to be updated + * @param {Id} <%-associationsArguments["to_one"][i].sourceKey-%> Foreign Key (stored in "Me") of the Association to be updated. + * @param {BenignErrorReporter} benignErrorReporter Error Reporter used for reporting Errors + * @param {string} token The token used for authorization + * @param {boolean} handle_inverse Handle inverse association + */ + static async <%- op -%>_<%-associationsArguments["to_one"][i].sourceKey-%>(<%- idAttribute-%>, <%-associationsArguments["to_one"][i].sourceKey-%>, benignErrorReporter, token, handle_inverse = true) { + //handle inverse association + if(handle_inverse){ + <%if (associationsArguments["to_one"][i].type==="many_to_one") {-%> + await models.<%-associationsArguments["to_one"][i].target_lc-%>.<%- op %>_<%-associationsArguments["to_one"][i].targetKey%>(<%-associationsArguments["to_one"][i].sourceKey-%>, [`${<%- idAttribute-%>}`], benignErrorReporter, token, false); + <%} else if (associationsArguments["to_one"][i].type==="one_to_one") {-%> + await models.<%-associationsArguments["to_one"][i].target_lc-%>.<%- op %>_<%-associationsArguments["to_one"][i].targetKey%>(<%-associationsArguments["to_one"][i].sourceKey-%>, <%- idAttribute-%>, benignErrorReporter, token, false); + <%}-%> + } + const driver = await this.storageHandler; + const session = driver.session({ + database: config.database, + defaultAccessMode: neo4j.session.WRITE, + }); + let foreignKey = `MATCH (n:<%- model_name_in_storage -%> ) WHERE n.<%-idAttribute%> = $id + SET n.<%-associationsArguments["to_one"][i].sourceKey-%> = $target RETURN count(n)`; + const target_model = models.<%-associationsArguments["to_one"][i].target_lc-%>.definition.model_name_in_storage ?? "<%-associationsArguments["to_one"][i].target_lc_pl-%>"; + + <% if (op == 'remove') { %>let delete_relationships = `MATCH (a:<%- model_name_in_storage -%>)-[r:${"<%-associationsArguments["to_one"][i].name-%>".toUpperCase() + "_EDGE"}]-> (b:${target_model}) + WHERE a.<%-idAttribute%> = $id AND b.${models.<%-associationsArguments["to_one"][i].target-%>.idAttribute()} = $target + DELETE r`<% } + else { %>let create_relationships = `MATCH (a:<%- model_name_in_storage -%>), (b:${target_model}) + WHERE a.<%-idAttribute%> = $id AND b.${models.<%-associationsArguments["to_one"][i].target-%>.idAttribute()} = $target + CREATE (a)-[r:${"<%-associationsArguments["to_one"][i].name-%>".toUpperCase() + "_EDGE"}]->(b)`<%}-%> + + try{ + const result = await session.run(foreignKey, {id: <%-idAttribute%>, target: <% if (op == 'remove') { _%>null<% } else { %><%-associationsArguments["to_one"][i].sourceKey-%><%}-%>}); + await session.run(<% if (op == 'remove') { %>delete_relationships<% } else { %>create_relationships<%}-%>, + { + id: <%-idAttribute%>, + target: <%-associationsArguments["to_one"][i].sourceKey-%>, + }) + return result.records[0].get(0); + } catch (error) { + benignErrorReporter.push({ + message:error + }); + } finally { + await session.close(); + } + } + <%} else if (associationsArguments["to_one"][i].holdsForeignKey) { -%> /** * <%- op %>_<%-associationsArguments["to_one"][i].targetKey-%> - field Mutation (model-layer) for to_one associationsArguments to <%- op %> * @@ -50,13 +101,19 @@ * * @param {Id} <%- idAttribute-%> IdAttribute of the root model to be updated * @param {Array} <%-associationsArguments["to_many"][i].sourceKey-%> Array foreign Key (stored in "Me") of the Association to be updated. + * @param {string} token The token used for authorization + * @param {boolean} handle_inverse Handle inverse association */ - static async <%- op -%>_<%-associationsArguments["to_many"][i].sourceKey-%>(<%- idAttribute-%>, <%-associationsArguments["to_many"][i].sourceKey-%>, benignErrorReporter, handle_inverse = true) { + static async <%- op -%>_<%-associationsArguments["to_many"][i].sourceKey-%>(<%- idAttribute-%>, <%-associationsArguments["to_many"][i].sourceKey-%>, benignErrorReporter, token, handle_inverse = true) { //handle inverse association if(handle_inverse){ let promises = []; <%-associationsArguments["to_many"][i].sourceKey-%>.forEach( idx =>{ - promises.push(models.<%-associationsArguments["to_many"][i].target_lc-%>.<%- op %>_<%-associationsArguments["to_many"][i].targetKey%>(idx, [`${<%- idAttribute-%>}`], benignErrorReporter, false) ); + <%if (associationsArguments["to_many"][i].type==="many_to_many") {-%> + promises.push(models.<%-associationsArguments["to_many"][i].target_lc-%>.<%- op %>_<%-associationsArguments["to_many"][i].targetKey%>(idx, [`${<%- idAttribute-%>}`], benignErrorReporter, token, false) ); + <%} else if (associationsArguments["to_many"][i].type==="one_to_many") {-%> + promises.push(models.<%-associationsArguments["to_many"][i].target_lc-%>.<%- op %>_<%-associationsArguments["to_many"][i].targetKey%>(idx, <%- idAttribute-%>, benignErrorReporter, token, false) ); + <%}-%> }); await Promise.all(promises); } diff --git a/views/includes/create-models-fieldMutations.ejs b/views/includes/create-models-fieldMutations.ejs index 7642a927..37b86e44 100644 --- a/views/includes/create-models-fieldMutations.ejs +++ b/views/includes/create-models-fieldMutations.ejs @@ -1,5 +1,33 @@ <%for(let i=0; i < associationsArguments["to_one"].length; i++){-%> - <% if (associationsArguments["to_one"][i].holdsForeignKey) { -%> + <% if (associationsArguments["to_one"][i].assocThroughArray) { -%> + /** + * <%- op %>_<%-associationsArguments["to_one"][i].sourceKey-%> - field Mutation (model-layer) for to_one associationsArguments to <%- op %> + * + * @param {Id} <%- idAttribute-%> IdAttribute of the root model to be updated + * @param {Id} <%-associationsArguments["to_one"][i].sourceKey-%> Foreign Key (stored in "Me") of the Association to be updated. + * @param {BenignErrorReporter} benignErrorReporter Error Reporter used for reporting Errors + * @param {string} token The token used for authorization + * @param {boolean} handle_inverse Handle inverse association + */ + static async <%- op -%>_<%-associationsArguments["to_one"][i].sourceKey-%>(<%- idAttribute-%>, <%-associationsArguments["to_one"][i].sourceKey-%>, benignErrorReporter, token, handle_inverse = true) { + try{ + //handle inverse association + if(handle_inverse){ + <%if (associationsArguments["to_one"][i].type==="many_to_one") {-%> + await models.<%-associationsArguments["to_one"][i].target_lc-%>.<%- op %>_<%-associationsArguments["to_one"][i].targetKey%>(<%-associationsArguments["to_one"][i].sourceKey-%>, [`${<%- idAttribute-%>}`], benignErrorReporter, token, false); + <%} else if (associationsArguments["to_one"][i].type==="one_to_one") {-%> + await models.<%-associationsArguments["to_one"][i].target_lc-%>.<%- op %>_<%-associationsArguments["to_one"][i].targetKey%>(<%-associationsArguments["to_one"][i].sourceKey-%>, <%- idAttribute-%>, benignErrorReporter, token, false); + <%}-%> + } + let updated = await <%- nameLc -%>.update({ <%-associationsArguments["to_one"][i].sourceKey-%>: <% if (op == 'remove') { -%>null<% } else { %><%-associationsArguments["to_one"][i].sourceKey-%><%}-%>},{where: {<%- idAttribute -%>: <%- idAttribute -%><% if (op == 'remove') { -%>,<%-associationsArguments["to_one"][i].sourceKey-%>: <%-associationsArguments["to_one"][i].sourceKey-%> <%}-%>}}); + return updated[0]; + } catch (error) { + benignErrorReporter.push({ + message:error + }); + } + } + <%} else if (associationsArguments["to_one"][i].holdsForeignKey) {-%> /** * <%- op %>_<%-associationsArguments["to_one"][i].targetKey-%> - field Mutation (model-layer) for to_one associationsArguments to <%- op %> * @@ -26,13 +54,19 @@ * * @param {Id} <%- idAttribute-%> IdAttribute of the root model to be updated * @param {Array} <%-associationsArguments["to_many"][i].sourceKey-%> Array foreign Key (stored in "Me") of the Association to be updated. + * @param {string} token The token used for authorization + * @param {boolean} handle_inverse Handle inverse association */ - static async <%- op -%>_<%-associationsArguments["to_many"][i].sourceKey-%>(<%- idAttribute-%>, <%-associationsArguments["to_many"][i].sourceKey-%>, benignErrorReporter, handle_inverse = true) { + static async <%- op -%>_<%-associationsArguments["to_many"][i].sourceKey-%>(<%- idAttribute-%>, <%-associationsArguments["to_many"][i].sourceKey-%>, benignErrorReporter, token, handle_inverse = true) { //handle inverse association if(handle_inverse){ let promises = []; <%-associationsArguments["to_many"][i].sourceKey-%>.forEach( idx =>{ - promises.push(models.<%-associationsArguments["to_many"][i].target_lc-%>.<%- op %>_<%-associationsArguments["to_many"][i].targetKey%>(idx, [`${<%- idAttribute-%>}`], benignErrorReporter, false) ); + <%if (associationsArguments["to_many"][i].type==="many_to_many") {-%> + promises.push(models.<%-associationsArguments["to_many"][i].target_lc-%>.<%- op %>_<%-associationsArguments["to_many"][i].targetKey%>(idx, [`${<%- idAttribute-%>}`], benignErrorReporter, token, false) ); + <%} else if (associationsArguments["to_many"][i].type==="one_to_many") {-%> + promises.push(models.<%-associationsArguments["to_many"][i].target_lc-%>.<%- op %>_<%-associationsArguments["to_many"][i].targetKey%>(idx, <%- idAttribute-%>, benignErrorReporter, token, false) ); + <%}-%> }); await Promise.all(promises); } diff --git a/views/includes/create-models-zendro-fieldMutations.ejs b/views/includes/create-models-zendro-fieldMutations.ejs index 4ee317fa..6f235cd7 100644 --- a/views/includes/create-models-zendro-fieldMutations.ejs +++ b/views/includes/create-models-zendro-fieldMutations.ejs @@ -1,5 +1,62 @@ <%for(let i=0; i < associationsArguments["to_one"].length; i++){-%> - <% if (associationsArguments["to_one"][i].holdsForeignKey) { -%> + <% if (associationsArguments["to_one"][i].assocThroughArray) { -%> + /** + * <%- op %>_<%-associationsArguments["to_one"][i].sourceKey-%> - field Mutation (adapter-layer) for to_one associationsArguments to <%- op %> + * + * @param {Id} <%- idAttribute-%> IdAttribute of the root model to be updated + * @param {Id} <%-associationsArguments["to_one"][i].sourceKey-%> Foreign Key (stored in "Me") of the Association to be updated. + * @param {BenignErrorReporter} benignErrorReporter Error Reporter used for reporting Errors from remote zendro services + * @param {string} token The token used for authorization + */ + static async <%- op -%>_<%-associationsArguments["to_one"][i].sourceKey-%>(<%- idAttribute-%>, <%-associationsArguments["to_one"][i].sourceKey-%>, benignErrorReporter, token) { + let query = ` + mutation + update<%- nameCp-%>{ + update<%- nameCp-%>( + <%- idAttribute -%>:"${<%- idAttribute -%>}" + <%- op %><%= associationsArguments["to_one"][i].name_cp%>:"${<%-associationsArguments["to_one"][i].sourceKey-%>}" + ){ + <%- idAttribute -%> + <%-associationsArguments["to_one"][i].sourceKey-%> + } + }` + + try { + // Send an HTTP request to the remote server + let opts = { + headers: { + "Content-Type": "application/json", + Accept: "application/graphql", + }, + }; + if (token) { + opts.headers["authorization"] = token; + } + let response = await axios.post( + remoteZendroURL, + { + query: query, + }, + opts + ); + //check if remote service returned benign Errors in the response and add them to the benignErrorReporter + if(helper.isNonEmptyArray(response.data.errors)) { + benignErrorReporter.push(errorHelper.handleRemoteErrors(response.data.errors, remoteZendroURL)); + } + // STATUS-CODE is 200 + // NO ERROR as such has been detected by the server (Express) + // check if data was send + if(response && response.data && response.data.data) { + return new <%- nameLc -%>(response.data.data.update<%- nameCp -%>); + } else { + throw new Error(`Remote zendro-server (${remoteZendroURL}) did not respond with data.`); + } + } catch(error){ + //handle caught errors + errorHelper.handleCaughtErrorAndBenignErrors(error, benignErrorReporter, remoteZendroURL); + } + } + <%} else if (associationsArguments["to_one"][i].holdsForeignKey) {-%> /** * <%- op %>_<%-associationsArguments["to_one"][i].targetKey-%> - field Mutation (adapter-layer) for to_one associationsArguments to <%- op %> * diff --git a/views/includes/create-resolvers-fieldMutations.ejs b/views/includes/create-resolvers-fieldMutations.ejs index 8e57c683..9cd15e58 100644 --- a/views/includes/create-resolvers-fieldMutations.ejs +++ b/views/includes/create-resolvers-fieldMutations.ejs @@ -40,7 +40,26 @@ * @param {string} token The token used for authorization */ <%- nameLc -%>.prototype.<%- op %>_<%-associationsArguments["to_one"][i].name_lc-%> = async function(input, benignErrorReporter, token) { - <% if (associationsArguments["to_one"][i].holdsForeignKey) { -%> + <% if (associationsArguments["to_one"][i].assocThroughArray) { -%> + <% if (op === 'remove') { -%> + if(input.<%- op %><%-associationsArguments["to_one"][i].name_cp-%> == this.<%-associationsArguments["to_one"][i].sourceKey%>) { + await <%- nameLc -%>.<%- op %>_<%-associationsArguments["to_one"][i].sourceKey-%>(this.getIdValue(), input.<%- op %><%-associationsArguments["to_one"][i].name_cp-%>, benignErrorReporter, token); + this.<%-associationsArguments["to_one"][i].sourceKey%> = null; + } + <%} else { -%> + <% if (associationsArguments["to_one"][i].type==="one_to_one" && op === 'add') { -%> + const associated = await models.<%=associationsArguments["to_one"][i].target_lc-%>.readById(input.<%- op %><%-associationsArguments["to_one"][i].name_cp-%>, benignErrorReporter, token); + if (associated.<%=associationsArguments["to_one"][i].targetKey%>) { + const removed = await <%- nameLc %>.remove_<%-associationsArguments["to_one"][i].sourceKey%>(associated.<%=associationsArguments["to_one"][i].targetKey%>, input.add<%-associationsArguments["to_one"][i].name_cp-%>, benignErrorReporter, token); + benignErrorReporter.push({ + message: `Hint: update ${removed} existing association!`, + }); + } + <%}-%> + await <%- nameLc -%>.<%- op %>_<%-associationsArguments["to_one"][i].sourceKey-%>(this.getIdValue(), input.<%- op %><%-associationsArguments["to_one"][i].name_cp-%>, benignErrorReporter, token); + this.<%-associationsArguments["to_one"][i].sourceKey%> = input.<%- op %><%-associationsArguments["to_one"][i].name_cp%>; + <%}-%> + <%} else if (associationsArguments["to_one"][i].holdsForeignKey) {-%> <% if (op === 'remove') { -%> if(input.<%- op %><%-associationsArguments["to_one"][i].name_cp-%> == this.<%-associationsArguments["to_one"][i].targetKey%>) { await <%- nameLc -%>.<%- op %>_<%-associationsArguments["to_one"][i].targetKey-%>(this.getIdValue(), input.<%- op %><%-associationsArguments["to_one"][i].name_cp-%>, benignErrorReporter, token); diff --git a/views/includes/create-resolvers-fieldResover_to_one.ejs b/views/includes/create-resolvers-fieldResover_to_one.ejs index 90a8e8a2..ec427761 100644 --- a/views/includes/create-resolvers-fieldResover_to_one.ejs +++ b/views/includes/create-resolvers-fieldResover_to_one.ejs @@ -9,8 +9,27 @@ * @return {type} Associated record */ <%- nameLc -%>.prototype.<%=associations_one[i].name%> = async function({search}, context){ - <% if (associations_one[i].holdsForeignKey) { %> - if(helper.isNotUndefinedAndNotNull(this.<%=associations_one[i].targetKey%>)){ + <% if (associationsArguments["to_one"][i].assocThroughArray) { -%> + if(helper.isNotUndefinedAndNotNull(this.<%=associations_one[i].sourceKey%>)){ + if (search === undefined || search === null) { + return resolvers.readOne<%=associations_one[i].target_cp%>({[models.<%=associations_one[i].target_lc-%>.idAttribute()]: this.<%=associations_one[i].sourceKey%>},context) + } else { + //build new search filter + let nsearch = helper.addSearchField({ + "search": search, + "field": models.<%=associations_one[i].target_lc-%>.idAttribute(), + "value": this.<%= associations_one[i].sourceKey -%>, + "operator": "eq" + }); + let found = await resolvers.<%=associations_one[i].target_lc_pl%>Connection({search: nsearch, pagination: {first:1}}, context); + if (found) { + return found[0] + } + return found; + } + } + <% } else if (associations_one[i].holdsForeignKey) { %> + if(helper.isNotUndefinedAndNotNull(this.<%=associations_one[i].targetKey%>)){ if (search === undefined || search === null) { return resolvers.readOne<%=associations_one[i].target_cp%>({[models.<%=associations_one[i].target_lc-%>.idAttribute()]: this.<%=associations_one[i].targetKey%>},context) } else { @@ -27,26 +46,26 @@ } return found; } - } + } <%}else{-%> - //build new search filter - let nsearch = helper.addSearchField({ - "search": search, - "field": "<%=associations_one[i].targetKey%>", - "value": this.getIdValue(), - "operator": "eq" - }); + //build new search filter + let nsearch = helper.addSearchField({ + "search": search, + "field": "<%=associations_one[i].targetKey%>", + "value": this.getIdValue(), + "operator": "eq" + }); - let found = (await resolvers.<%=associations_one[i].target_lc_pl%>Connection({search: nsearch, pagination: {first:2}}, context)).edges; - if(found.length > 0) { - if(found.length > 1){ - context.benignErrors.push(new Error( - `Not unique "to_one" association Error: Found > 1 <%=associations_one[i].target_lc_pl%> matching <%- nameLc -%> with <%- idAttribute-%> ${this.getIdValue()}. Consider making this a "to_many" association, or using unique constraints, or moving the foreign key into the <%- name -%> model. Returning first <%=associations_one[i].target-%>.` - )); - } - return found[0].node; + let found = (await resolvers.<%=associations_one[i].target_lc_pl%>Connection({search: nsearch, pagination: {first:2}}, context)).edges; + if(found.length > 0) { + if(found.length > 1){ + context.benignErrors.push(new Error( + `Not unique "to_one" association Error: Found > 1 <%=associations_one[i].target_lc_pl%> matching <%- nameLc -%> with <%- idAttribute-%> ${this.getIdValue()}. Consider making this a "to_many" association, or using unique constraints, or moving the foreign key into the <%- name -%> model. Returning first <%=associations_one[i].target-%>.` + )); } - return null; + return found[0].node; + } + return null; <%}-%> } <%}-%> \ No newline at end of file