diff --git a/funks.js b/funks.js index 9856d233..69c059c0 100644 --- a/funks.js +++ b/funks.js @@ -976,6 +976,7 @@ generateSections = async function (sections, opts, dir_write) { case "models-cassandra": case "models-mongodb": case "models-amazonS3": + case "models-trino": //adapters case "sql-adapter": case "zendro-adapters": @@ -983,6 +984,7 @@ generateSections = async function (sections, opts, dir_write) { case "cassandra-adapter": case "mongodb-adapter": case "amazonS3-adapter": + case "trino-adapter": file_name = dir_write + "/" + section.dir + "/" + section.fileName + ".js"; break; @@ -1086,6 +1088,8 @@ getStorageType = function (dataModel) { case "cassandra": case "mongodb": case "amazon-s3": + case "trino": + case "presto": //adapters case "sql-adapter": case "ddm-adapter": @@ -1094,6 +1098,8 @@ getStorageType = function (dataModel) { case "cassandra-adapter": case "mongodb-adapter": case "amazon-s3-adapter": + case "trino-adapter": + case "presto-adapter": //ok break; @@ -1102,9 +1108,12 @@ getStorageType = function (dataModel) { valid = false; console.error( colors.red( - `ERROR: The attribute 'storageType' has an invalid value. \nOne of the following types is expected: [sql, distributed-data-model, zendro-server, generic, sql-adapter, ddm-adapter, zendro-webservice-adapter, generic-adapter]. But '${ - dataModel.storageType - }' was obtained on ${ + `ERROR: The attribute 'storageType' has an invalid value. \n + One of the following types is expected: [sql, distributed-data-model, + zendro-server, generic, sql-adapter, ddm-adapter, zendro-webservice-adapter, generic-adapter, + cassandra, mongodb, amazon-s3, trino, presto, cassandra-adapter, mongodb-adapter, + amazon-s3-adapter, trino-adapter, presto-adapter]. + But '${dataModel.storageType}' was obtained on ${ dataModel.adapterName !== undefined ? "adapter" : "model" } '${ dataModel.adapterName !== undefined @@ -1153,6 +1162,8 @@ module.exports.generateCode = async function (json_dir, dir_write, options) { "models/cassandra", "models/mongodb", "models/amazonS3", + "models/trino", + "models/presto", ]; let models = []; let adapters = []; @@ -1457,6 +1468,42 @@ module.exports.generateCode = async function (json_dir, dir_write, options) { ]; break; + case "trino": + sections = [ + { dir: "schemas", template: "schemas", fileName: opts.nameLc }, + { dir: "resolvers", template: "resolvers", fileName: opts.nameLc }, + { + dir: "models/trino", + template: "models-trino", + fileName: opts.nameLc, + }, + { + dir: "validations", + template: "validations", + fileName: opts.nameLc, + }, + { dir: "patches", template: "patches", fileName: opts.nameLc }, + ]; + break; + + case "presto": + sections = [ + { dir: "schemas", template: "schemas", fileName: opts.nameLc }, + { dir: "resolvers", template: "resolvers", fileName: opts.nameLc }, + { + dir: "models/presto", + template: "models-trino", + fileName: opts.nameLc, + }, + { + dir: "validations", + template: "validations", + fileName: opts.nameLc, + }, + { dir: "patches", template: "patches", fileName: opts.nameLc }, + ]; + break; + case "zendro-webservice-adapter": sections = [ { @@ -1539,6 +1586,28 @@ module.exports.generateCode = async function (json_dir, dir_write, options) { { dir: "patches", template: "patches", fileName: opts.adapterName }, ]; break; + + case "trino-adapter": + sections = [ + { + dir: "models/adapters", + template: "trino-adapter", + fileName: opts.adapterName, + }, + { dir: "patches", template: "patches", fileName: opts.adapterName }, + ]; + break; + + case "presto-adapter": + sections = [ + { + dir: "models/adapters", + template: "trino-adapter", + fileName: opts.adapterName, + }, + { dir: "patches", template: "patches", fileName: opts.adapterName }, + ]; + break; default: break; } @@ -1568,6 +1637,8 @@ module.exports.generateCode = async function (json_dir, dir_write, options) { "mongodb-adapter", "cassandra-adapter", "amazon-s3-adapter", + "trino-adapter", + "presto-adapter", ].includes(opts.storageType) ) { adapters.push(opts.adapterName); diff --git a/lib/generators-aux.js b/lib/generators-aux.js index 3962a9ac..65553bac 100644 --- a/lib/generators-aux.js +++ b/lib/generators-aux.js @@ -12,6 +12,12 @@ exports.getModelDatabase = function (dataModel) { "cassandra-adapter": "default-cassandra", mongodb: "default-mongodb", "mongodb-adapter": "default-mongodb", + "amazon-s3": "default-amazonS3", + "amazon-s3-adapter": "default-amazonS3", + trino: "default-trino", + "trino-adapter": "default-trino", + presto: "default-presto", + "presto-adapter": "default-presto", }; const storageType = dataModel.storageType.toLowerCase(); diff --git a/test/integration_test_misc/Dockerfile.postgres2 b/test/integration_test_misc/Dockerfile.postgres2 new file mode 100644 index 00000000..c1e2f6aa --- /dev/null +++ b/test/integration_test_misc/Dockerfile.postgres2 @@ -0,0 +1,4 @@ +FROM postgres:11.1-alpine + +COPY initUserDb2.sh /docker-entrypoint-initdb.d/initUserDb.sh +RUN chmod 755 /docker-entrypoint-initdb.d/initUserDb.sh diff --git a/test/integration_test_misc/data_models_storage_config2.json b/test/integration_test_misc/data_models_storage_config2.json index 2a7027fa..23a752b6 100644 --- a/test/integration_test_misc/data_models_storage_config2.json +++ b/test/integration_test_misc/data_models_storage_config2.json @@ -6,5 +6,19 @@ "database": "sciencedb_development", "host": "gql_postgres2", "dialect": "postgres" + }, + "default-trino": { + "storageType": "trino", + "catalog":"postgresql", + "schema":"public", + "trino_host": "gql_trino", + "trino_port": "8080" + }, + "default-presto": { + "storageType": "presto", + "catalog":"postgresql", + "schema":"public", + "presto_host": "gql_presto", + "presto_port": "8080" } } \ No newline at end of file diff --git a/test/integration_test_misc/docker-compose-test.yml b/test/integration_test_misc/docker-compose-test.yml index cc53e273..1c7809ce 100644 --- a/test/integration_test_misc/docker-compose-test.yml +++ b/test/integration_test_misc/docker-compose-test.yml @@ -109,9 +109,33 @@ services: container_name: postgres2 build: context: . - dockerfile: Dockerfile.postgres + dockerfile: Dockerfile.postgres2 + ports: + - 1235:5432 + networks: + - instance2 + + gql_presto: + image: ahanaio/prestodb-sandbox + container_name: presto1 + depends_on: + - gql_postgres2 + volumes: + - ./postgresql.properties:/opt/presto-server/etc/catalog/postgresql.properties + ports: + - 8081:8080 + networks: + - instance2 + + gql_trino: + image: trinodb/trino + container_name: trino1 + depends_on: + - gql_postgres2 + volumes: + - ./postgresql.properties:/etc/trino/catalog/postgresql.properties ports: - - 1235:5431 + - 8080:8080 networks: - instance2 diff --git a/test/integration_test_misc/initUserDb2.sh b/test/integration_test_misc/initUserDb2.sh new file mode 100644 index 00000000..01d78fff --- /dev/null +++ b/test/integration_test_misc/initUserDb2.sh @@ -0,0 +1,72 @@ +#!/bin/bash +psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" <<-EOSQL + CREATE USER sciencedb WITH SUPERUSER PASSWORD 'sciencedb'; + CREATE DATABASE sciencedb_development OWNER sciencedb; + CREATE DATABASE sciencedb_test OWNER sciencedb; + CREATE DATABASE sciencedb_production OWNER sciencedb; +EOSQL +psql -U sciencedb -d sciencedb_development <<-EOSQL +CREATE TABLE trino_doctors ( + doctor_id varchar(255) PRIMARY KEY, + birthday timestamp, + experience integer, + rating float, + on_holiday boolean, + speciality text, + telephone text +); +INSERT INTO trino_doctors (doctor_id, birthday, experience, rating, on_holiday, speciality, telephone) VALUES + ('d1', '1989-12-03T10:15:30.000Z', 3, 4.9, false, '["Tinnitus","Allergology"]', '[152234,137584]'), + ('d2', '1977-12-03T10:15:30.000Z', 15, 5.0, false, '["Cardiology","Cardiothoracic Surgery"]', '[142234,127584]'), + ('d3', '1987-12-03T10:15:30.000Z', 5, 4.8, true, '["Dermatology","Allergology"]', '[162234,177584]'), + ('d4', '1988-12-03T10:15:30.000Z', 4, 4.9, false, '["Child Psychiatry","Adolescent Psychiatry"]', '[192234,197584]'), + ('d5', '1986-12-03T10:15:30.000Z', 6, 4.7, true, '["Neurology"]', '[122234,187584]'); + +CREATE TABLE presto_doctors ( + doctor_id varchar(255) PRIMARY KEY, + birthday timestamp, + experience integer, + rating float, + on_holiday boolean, + speciality text, + telephone text +); +INSERT INTO presto_doctors (doctor_id, birthday, experience, rating, on_holiday, speciality, telephone) VALUES + ('d1', '1989-12-03T10:15:30.000Z', 3, 4.9, false, '["Tinnitus","Allergology"]', '[152234,137584]'), + ('d2', '1977-12-03T10:15:30.000Z', 15, 5.0, false, '["Cardiology","Cardiothoracic Surgery"]', '[142234,127584]'), + ('d3', '1987-12-03T10:15:30.000Z', 5, 4.8, true, '["Dermatology","Allergology"]', '[162234,177584]'), + ('d4', '1988-12-03T10:15:30.000Z', 4, 4.9, false, '["Child Psychiatry","Adolescent Psychiatry"]', '[192234,197584]'), + ('d5', '1986-12-03T10:15:30.000Z', 6, 4.7, true, '["Neurology"]', '[122234,187584]'); + +CREATE TABLE dist_trino_doctors ( + doctor_id varchar(255) PRIMARY KEY, + birthday timestamp, + experience integer, + rating float, + on_holiday boolean, + speciality text, + telephone text +); +INSERT INTO dist_trino_doctors (doctor_id, birthday, experience, rating, on_holiday, speciality, telephone) VALUES + ('instance1-d1', '1989-12-03T10:15:30.000Z', 3, 4.9, false, '["Tinnitus","Allergology"]', '[152234,137584]'), + ('instance1-d2', '1977-12-03T10:15:30.000Z', 15, 5.0, false, '["Cardiology","Cardiothoracic Surgery"]', '[142234,127584]'), + ('instance1-d3', '1987-12-03T10:15:30.000Z', 5, 4.8, true, '["Dermatology","Allergology"]', '[162234,177584]'), + ('instance1-d4', '1988-12-03T10:15:30.000Z', 4, 4.9, false, '["Child Psychiatry","Adolescent Psychiatry"]', '[192234,197584]'), + ('instance1-d5', '1986-12-03T10:15:30.000Z', 6, 4.7, true, '["Neurology"]', '[122234,187584]'); + +CREATE TABLE dist_presto_doctors ( + doctor_id varchar(255) PRIMARY KEY, + birthday timestamp, + experience integer, + rating float, + on_holiday boolean, + speciality text, + telephone text +); +INSERT INTO dist_presto_doctors (doctor_id, birthday, experience, rating, on_holiday, speciality, telephone) VALUES + ('instance1-d1', '1989-12-03T10:15:30.000Z', 3, 4.9, false, '["Tinnitus","Allergology"]', '[152234,137584]'), + ('instance1-d2', '1977-12-03T10:15:30.000Z', 15, 5.0, false, '["Cardiology","Cardiothoracic Surgery"]', '[142234,127584]'), + ('instance1-d3', '1987-12-03T10:15:30.000Z', 5, 4.8, true, '["Dermatology","Allergology"]', '[162234,177584]'), + ('instance1-d4', '1988-12-03T10:15:30.000Z', 4, 4.9, false, '["Child Psychiatry","Adolescent Psychiatry"]', '[192234,197584]'), + ('instance1-d5', '1986-12-03T10:15:30.000Z', 6, 4.7, true, '["Neurology"]', '[122234,187584]'); +EOSQL \ No newline at end of file diff --git a/test/integration_test_misc/integration_test_models_instance2/dist_presto_doctor.json b/test/integration_test_misc/integration_test_models_instance2/dist_presto_doctor.json new file mode 100644 index 00000000..116062c8 --- /dev/null +++ b/test/integration_test_misc/integration_test_models_instance2/dist_presto_doctor.json @@ -0,0 +1,16 @@ +{ + "model": "dist_presto_doctor", + "storageType" : "distributed-data-model", + "registry": ["dist_presto_doctor_instance1"], + "attributes": { + "doctor_id": "String", + "birthday": "DateTime", + "experience": "Int", + "rating": "Float", + "on_holiday": "Boolean", + "speciality": "[String]", + "telephone": "[Int]" + }, + + "internalId" : "doctor_id" +} \ No newline at end of file diff --git a/test/integration_test_misc/integration_test_models_instance2/dist_presto_doctor_instance1.json b/test/integration_test_misc/integration_test_models_instance2/dist_presto_doctor_instance1.json new file mode 100644 index 00000000..43f5c383 --- /dev/null +++ b/test/integration_test_misc/integration_test_models_instance2/dist_presto_doctor_instance1.json @@ -0,0 +1,17 @@ +{ + "model": "dist_presto_doctor", + "storageType": "presto-adapter", + "adapterName": "dist_presto_doctor_instance1", + "regex": "instance1", + "attributes": { + "doctor_id": "String", + "birthday": "DateTime", + "experience": "Int", + "rating": "Float", + "on_holiday": "Boolean", + "speciality": "[String]", + "telephone": "[Int]" + }, + + "internalId": "doctor_id" +} \ No newline at end of file diff --git a/test/integration_test_misc/integration_test_models_instance2/dist_trino_doctor.json b/test/integration_test_misc/integration_test_models_instance2/dist_trino_doctor.json new file mode 100644 index 00000000..3d4815aa --- /dev/null +++ b/test/integration_test_misc/integration_test_models_instance2/dist_trino_doctor.json @@ -0,0 +1,16 @@ +{ + "model": "dist_trino_doctor", + "storageType" : "distributed-data-model", + "registry": ["dist_trino_doctor_instance1"], + "attributes": { + "doctor_id": "String", + "birthday": "DateTime", + "experience": "Int", + "rating": "Float", + "on_holiday": "Boolean", + "speciality": "[String]", + "telephone": "[Int]" + }, + + "internalId" : "doctor_id" +} \ No newline at end of file diff --git a/test/integration_test_misc/integration_test_models_instance2/dist_trino_doctor_instance1.json b/test/integration_test_misc/integration_test_models_instance2/dist_trino_doctor_instance1.json new file mode 100644 index 00000000..52426d81 --- /dev/null +++ b/test/integration_test_misc/integration_test_models_instance2/dist_trino_doctor_instance1.json @@ -0,0 +1,17 @@ +{ + "model": "dist_trino_doctor", + "storageType": "trino-adapter", + "adapterName": "dist_trino_doctor_instance1", + "regex": "instance1", + "attributes": { + "doctor_id": "String", + "birthday": "DateTime", + "experience": "Int", + "rating": "Float", + "on_holiday": "Boolean", + "speciality": "[String]", + "telephone": "[Int]" + }, + + "internalId": "doctor_id" +} \ No newline at end of file diff --git a/test/integration_test_misc/integration_test_models_instance2/presto_doctor.json b/test/integration_test_misc/integration_test_models_instance2/presto_doctor.json new file mode 100644 index 00000000..b331ec04 --- /dev/null +++ b/test/integration_test_misc/integration_test_models_instance2/presto_doctor.json @@ -0,0 +1,18 @@ +{ + "model": "presto_doctor", + "storageType": "presto", + "attributes": { + "doctor_id": "String", + "birthday": "DateTime", + "experience": "Int", + "rating": "Float", + "on_holiday": "Boolean", + "speciality": "[String]", + "telephone": "[Int]" + }, + "internalId": "doctor_id", + "id": { + "name": "doctor_id", + "type": "String" + } +} \ No newline at end of file diff --git a/test/integration_test_misc/integration_test_models_instance2/trino_doctor.json b/test/integration_test_misc/integration_test_models_instance2/trino_doctor.json new file mode 100644 index 00000000..594b6a44 --- /dev/null +++ b/test/integration_test_misc/integration_test_models_instance2/trino_doctor.json @@ -0,0 +1,18 @@ +{ + "model": "trino_doctor", + "storageType": "trino", + "attributes": { + "doctor_id": "String", + "birthday": "DateTime", + "experience": "Int", + "rating": "Float", + "on_holiday": "Boolean", + "speciality": "[String]", + "telephone": "[Int]" + }, + "internalId": "doctor_id", + "id": { + "name": "doctor_id", + "type": "String" + } +} \ No newline at end of file diff --git a/test/integration_test_misc/postgresql.properties b/test/integration_test_misc/postgresql.properties new file mode 100644 index 00000000..d2093675 --- /dev/null +++ b/test/integration_test_misc/postgresql.properties @@ -0,0 +1,4 @@ +connector.name=postgresql +connection-url=jdbc:postgresql://gql_postgres2:5432/sciencedb_development +connection-user=sciencedb +connection-password=sciencedb diff --git a/test/mocha_integration_amazon_s3.test.js b/test/mocha_integration_amazon_s3.test.js index dee69b6c..6dbb88f4 100644 --- a/test/mocha_integration_amazon_s3.test.js +++ b/test/mocha_integration_amazon_s3.test.js @@ -304,6 +304,28 @@ describe("Amazon S3/ Minio - Upload/Read Operations", () => { expect(res.statusCode).to.equal(200); expect(resBody.data.readersConnection.edges.length).equal(1); + + res = itHelpers.request_graph_ql_post(` + { + readersConnection( + search:{ + operator:eq, + field:history, + value:"Critique of Pure Reason,The World as Will and Representation", + valueType:Array + }, + pagination:{first:5}) { + edges{ + node{ + reader_name + } + } + } + }`); + resBody = JSON.parse(res.body.toString("utf8")); + + expect(res.statusCode).to.equal(200); + expect(resBody.data.readersConnection.edges.length).equal(1); }); it("13. Reader: search with ne operator for array field", () => { @@ -466,7 +488,7 @@ describe("Amazon S3/ Minio - Distributed Data Models", () => { }); }); - it("02. Reader DDM: search", () => { + it("03. Reader DDM: search", () => { res = itHelpers.request_graph_ql_post( `{ dist_readersConnection( @@ -512,7 +534,7 @@ describe("Amazon S3/ Minio - Distributed Data Models", () => { }); }); - it("03. Reader DDM: paginate", () => { + it("04. Reader DDM: paginate", () => { res = itHelpers.request_graph_ql_post( `{ dist_readersConnection(pagination: { @@ -576,7 +598,7 @@ describe("Amazon S3/ Minio - Distributed Data Models", () => { }); }); - it("04. Reader DDM: count readers", () => { + it("05. Reader DDM: count readers", () => { res = itHelpers.request_graph_ql_post(`{countDist_readers}`); resBody = JSON.parse(res.body.toString("utf8")); expect(res.statusCode).to.equal(200); diff --git a/test/mocha_integration_presto.test.js b/test/mocha_integration_presto.test.js new file mode 100644 index 00000000..5dcc1d56 --- /dev/null +++ b/test/mocha_integration_presto.test.js @@ -0,0 +1,538 @@ +const { expect } = require("chai"); +const itHelpers = require("./integration_test_misc/integration_test_helpers"); + +describe("Presto - Read Access", () => { + it("01. presto_doctor: read one record", () => { + let res = itHelpers.request_graph_ql_post_instance2(` + { + readOnePresto_doctor(doctor_id:"d1"){ + doctor_id + birthday + experience + rating + on_holiday + speciality + telephone + } + }`); + let resBody = JSON.parse(res.body.toString("utf8")); + + expect(res.statusCode).to.equal(200); + expect(resBody).to.deep.equal({ + data: { + readOnePresto_doctor: { + doctor_id: "d1", + birthday: "1989-12-03T10:15:30.000Z", + experience: 3, + + rating: 4.9, + on_holiday: false, + speciality: ["Tinnitus", "Allergology"], + telephone: [152234, 137584], + }, + }, + }); + }); + + it("02. presto_doctor: count presto_doctors with like operator", () => { + let res = itHelpers.request_graph_ql_post_instance2( + `{countPresto_doctors(search:{operator: like, field: doctor_id, + value: "d%"})}` + ); + let resBody = JSON.parse(res.body.toString("utf8")); + + expect(res.statusCode).to.equal(200); + expect(resBody.data.countPresto_doctors).equal(5); + }); + + it("03. presto_doctor: search with and operator", () => { + let res = itHelpers.request_graph_ql_post_instance2(` + { + presto_doctors( + search: { + operator: and, + search: [ + {operator: eq, field: doctor_id, value: "d1"}, + {operator: eq, field: on_holiday, value: "false"} + ] + }, + pagination:{limit:5}) { + doctor_id + on_holiday + } + }`); + let resBody = JSON.parse(res.body.toString("utf8")); + + expect(res.statusCode).to.equal(200); + expect(resBody).to.deep.equal({ + data: { + presto_doctors: [ + { + doctor_id: "d1", + on_holiday: false, + }, + ], + }, + }); + }); + + it("04. presto_doctor: search with or operator", () => { + let res = itHelpers.request_graph_ql_post_instance2(` + { + presto_doctors( + search: { + operator: or, + search: [ + {operator: eq, field: doctor_id, value: "d1"}, + {operator: eq, field: doctor_id, value: "d2"} + ] + }, + pagination:{limit:5}) { + doctor_id + } + }`); + let resBody = JSON.parse(res.body.toString("utf8")); + + expect(res.statusCode).to.equal(200); + expect(resBody).to.deep.equal({ + data: { + presto_doctors: [ + { + doctor_id: "d1", + }, + { + doctor_id: "d2", + }, + ], + }, + }); + }); + + it("05. presto_doctor: search with in operator for primitive field", () => { + let res = itHelpers.request_graph_ql_post_instance2(` + { + presto_doctors( + search: { + operator: in, + field: doctor_id, + value: "d1,d2,d3", + valueType: Array + }, + pagination:{limit:5}) { + doctor_id + } + }`); + let resBody = JSON.parse(res.body.toString("utf8")); + + expect(res.statusCode).to.equal(200); + expect(resBody.data.presto_doctors.length).equal(3); + }); + + it("06. presto_doctor: search with not & in operators for primitive field", () => { + let res = itHelpers.request_graph_ql_post_instance2(` + { + presto_doctors( + search:{ + operator: not, + search: { + operator: in, + field: doctor_id, + value: "d1,d2,d3", + valueType: Array + } + }, + pagination:{limit:5}) { + doctor_id + } + }`); + let resBody = JSON.parse(res.body.toString("utf8")); + + expect(res.statusCode).to.equal(200); + expect(resBody.data.presto_doctors.length).equal(2); + }); + + it("07. presto_doctor: search with in operator for array field", () => { + let res = itHelpers.request_graph_ql_post_instance2(` + { + presto_doctors( + search:{operator:in, field:telephone, value:"152234"}, + pagination:{limit:5}) { + doctor_id + } + }`); + let resBody = JSON.parse(res.body.toString("utf8")); + + expect(res.statusCode).to.equal(200); + expect(resBody.data.presto_doctors.length).equal(1); + + res = itHelpers.request_graph_ql_post_instance2(` + { + presto_doctors( + search:{operator:in, field:speciality, value:"Tinnitus"}, + pagination:{limit:5}) { + doctor_id + } + }`); + resBody = JSON.parse(res.body.toString("utf8")); + + expect(res.statusCode).to.equal(200); + expect(resBody.data.presto_doctors.length).equal(1); + }); + + it("08. presto_doctor: search with not & in operators for array field", () => { + let res = itHelpers.request_graph_ql_post_instance2(` + { + presto_doctors( + search:{ + operator:not, + search:{operator:in, field:telephone, value:"152234"} + }, + pagination:{limit:5}) { + doctor_id + } + }`); + let resBody = JSON.parse(res.body.toString("utf8")); + + expect(res.statusCode).to.equal(200); + expect(resBody.data.presto_doctors.length).equal(4); + }); + + it("09. presto_doctor: search with eq operator for array field", () => { + let res = itHelpers.request_graph_ql_post_instance2(` + { + presto_doctors( + search:{operator:eq, field:telephone, value:"[152234,137584]"}, + pagination:{limit:5}) { + doctor_id + } + }`); + let resBody = JSON.parse(res.body.toString("utf8")); + + expect(res.statusCode).to.equal(200); + expect(resBody.data.presto_doctors.length).equal(1); + + res = itHelpers.request_graph_ql_post_instance2(` + { + presto_doctors( + search:{operator:eq, field:telephone, value:"152234,137584", valueType:Array}, + pagination:{limit:5}) { + doctor_id + } + }`); + resBody = JSON.parse(res.body.toString("utf8")); + + expect(res.statusCode).to.equal(200); + expect(resBody.data.presto_doctors.length).equal(1); + + res = itHelpers.request_graph_ql_post_instance2(` + { + presto_doctors( + search:{operator:eq, field:speciality, value:"Tinnitus,Allergology", valueType:Array}, + pagination:{limit:5}) { + doctor_id + } + }`); + resBody = JSON.parse(res.body.toString("utf8")); + + expect(res.statusCode).to.equal(200); + expect(resBody.data.presto_doctors.length).equal(1); + }); + + it("10. presto_doctor: search with ne operator for array field", () => { + let res = itHelpers.request_graph_ql_post_instance2(` + { + presto_doctors( + search:{operator:ne, field:speciality, value:"Tinnitus,Allergology", valueType:Array}, + pagination:{limit:5}) { + doctor_id + } + }`); + let resBody = JSON.parse(res.body.toString("utf8")); + + expect(res.statusCode).to.equal(200); + expect(resBody.data.presto_doctors.length).equal(4); + }); + + it("11. presto_doctor: sort", () => { + let res = itHelpers.request_graph_ql_post_instance2( + `{ + presto_doctors(pagination: {limit:5}, order: [{field: doctor_id, order: DESC}]) { + doctor_id + } + }` + ); + let resBody = JSON.parse(res.body.toString("utf8")); + + expect(res.statusCode).to.equal(200); + + expect(resBody).to.deep.equal({ + data: { + presto_doctors: [ + { doctor_id: "d5" }, + { doctor_id: "d4" }, + { doctor_id: "d3" }, + { doctor_id: "d2" }, + { doctor_id: "d1" }, + ], + }, + }); + }); + + it("12. presto_doctor: paginate", () => { + let res = itHelpers.request_graph_ql_post_instance2( + `{ + presto_doctorsConnection(pagination:{first:10}) { + edges{ + cursor + node{ + doctor_id + } + } + } + }` + ); + let resBody = JSON.parse(res.body.toString("utf8")); + let edges = resBody.data.presto_doctorsConnection.edges; + let idArray = edges.map((edge) => edge.node.doctor_id); + let cursorArray = edges.map((edge) => edge.cursor); + res = itHelpers.request_graph_ql_post_instance2( + `{ + presto_doctorsConnection(pagination:{first: 2, after: "${cursorArray[1]}"}) { + edges{ + cursor + node{ + doctor_id + } + } + presto_doctors{ + doctor_id + } + pageInfo{ + startCursor + endCursor + hasNextPage + hasPreviousPage + } + } + }` + ); + resBody = JSON.parse(res.body.toString("utf8")); + expect(res.statusCode).to.equal(200); + + expect(resBody).to.deep.equal({ + data: { + presto_doctorsConnection: { + edges: [ + { + cursor: cursorArray[2], + node: { + doctor_id: idArray[2], + }, + }, + { + cursor: cursorArray[3], + node: { + doctor_id: idArray[3], + }, + }, + ], + presto_doctors: [ + { + doctor_id: "d3", + }, + { + doctor_id: "d4", + }, + ], + pageInfo: { + startCursor: cursorArray[2], + endCursor: cursorArray[3], + hasNextPage: true, + hasPreviousPage: true, + }, + }, + }, + }); + + res = itHelpers.request_graph_ql_post_instance2( + `{ + presto_doctorsConnection(pagination: {last: 4, before:"${cursorArray[4]}"}) { + pageInfo { + startCursor + endCursor + hasNextPage + hasPreviousPage + } + } + }` + ); + resBody = JSON.parse(res.body.toString("utf8")); + expect(res.statusCode).to.equal(200); + + expect(resBody).to.deep.equal({ + data: { + presto_doctorsConnection: { + pageInfo: { + startCursor: cursorArray[0], + endCursor: cursorArray[3], + hasNextPage: true, + hasPreviousPage: false, + }, + }, + }, + }); + }); + + it("13. presto_doctor: get the table template", () => { + let res = itHelpers.request_graph_ql_post_instance2( + `{csvTableTemplatePresto_doctor}` + ); + let resBody = JSON.parse(res.body.toString("utf8")); + expect(res.statusCode).to.equal(200); + expect(resBody).to.deep.equal({ + data: { + csvTableTemplatePresto_doctor: [ + "doctor_id,birthday,experience,rating,on_holiday,speciality,telephone", + "String,DateTime,Int,Float,Boolean,[String],[Int]", + ], + }, + }); + }); +}); + +describe("Presto - Distributed Data Models", () => { + it("01. presto_doctor: read one presto_doctor", () => { + let res = itHelpers.request_graph_ql_post_instance2(` + { + readOneDist_presto_doctor(doctor_id:"instance1-d1"){ + doctor_id + birthday + experience + rating + on_holiday + speciality + telephone + } + }`); + let resBody = JSON.parse(res.body.toString("utf8")); + + expect(res.statusCode).to.equal(200); + expect(resBody).to.deep.equal({ + data: { + readOneDist_presto_doctor: { + doctor_id: "instance1-d1", + birthday: "1989-12-03T10:15:30.000Z", + experience: 3, + rating: 4.9, + on_holiday: false, + speciality: ["Tinnitus", "Allergology"], + telephone: [152234, 137584], + }, + }, + }); + }); + + it("02. presto_doctor DDM: search & sort", () => { + res = itHelpers.request_graph_ql_post_instance2( + `{ + dist_presto_doctorsConnection( + search: {field: doctor_id, value: "instance1%", operator: like}, + order: [{field: doctor_id, order: DESC}] + pagination: {first: 5}) { + dist_presto_doctors{ + doctor_id + } + } + } + ` + ); + resBody = JSON.parse(res.body.toString("utf8")); + expect(res.statusCode).to.equal(200); + expect(resBody).to.deep.equal({ + data: { + dist_presto_doctorsConnection: { + dist_presto_doctors: [ + { doctor_id: "instance1-d5" }, + { doctor_id: "instance1-d4" }, + { doctor_id: "instance1-d3" }, + { doctor_id: "instance1-d2" }, + { doctor_id: "instance1-d1" }, + ], + }, + }, + }); + }); + + it("03. presto_doctor DDM: paginate", () => { + res = itHelpers.request_graph_ql_post_instance2( + `{ + dist_presto_doctorsConnection(pagination: { + first: 2, + after: "eyJkb2N0b3JfaWQiOiJpbnN0YW5jZTEtZDEiLCJiaXJ0aGRheSI6IjE5ODktMTItMDNUMTA6MTU6MzAuMDAwWiIsImV4cGVyaWVuY2UiOjMsInJhdGluZyI6NC45LCJvbl9ob2xpZGF5IjpmYWxzZSwic3BlY2lhbGl0eSI6WyJUaW5uaXR1cyIsIkFsbGVyZ29sb2d5Il0sInRlbGVwaG9uZSI6WzE1MjIzNCwxMzc1ODRdfQ==" + }){ + edges { + node { + doctor_id + } + cursor + } + dist_presto_doctors{ + doctor_id + } + pageInfo { + startCursor + endCursor + hasNextPage + hasPreviousPage + } + } + } + ` + ); + resBody = JSON.parse(res.body.toString("utf8")); + expect(res.statusCode).to.equal(200); + expect(resBody).to.deep.equal({ + data: { + dist_presto_doctorsConnection: { + edges: [ + { + cursor: + "eyJkb2N0b3JfaWQiOiJpbnN0YW5jZTEtZDIiLCJiaXJ0aGRheSI6IjE5NzctMTItMDNUMTA6MTU6MzAuMDAwWiIsImV4cGVyaWVuY2UiOjE1LCJyYXRpbmciOjUsIm9uX2hvbGlkYXkiOmZhbHNlLCJzcGVjaWFsaXR5IjpbIkNhcmRpb2xvZ3kiLCJDYXJkaW90aG9yYWNpYyBTdXJnZXJ5Il0sInRlbGVwaG9uZSI6WzE0MjIzNCwxMjc1ODRdfQ==", + node: { + doctor_id: "instance1-d2", + }, + }, + { + cursor: + "eyJkb2N0b3JfaWQiOiJpbnN0YW5jZTEtZDMiLCJiaXJ0aGRheSI6IjE5ODctMTItMDNUMTA6MTU6MzAuMDAwWiIsImV4cGVyaWVuY2UiOjUsInJhdGluZyI6NC44LCJvbl9ob2xpZGF5Ijp0cnVlLCJzcGVjaWFsaXR5IjpbIkRlcm1hdG9sb2d5IiwiQWxsZXJnb2xvZ3kiXSwidGVsZXBob25lIjpbMTYyMjM0LDE3NzU4NF19", + node: { + doctor_id: "instance1-d3", + }, + }, + ], + dist_presto_doctors: [ + { doctor_id: "instance1-d2" }, + { doctor_id: "instance1-d3" }, + ], + pageInfo: { + startCursor: + "eyJkb2N0b3JfaWQiOiJpbnN0YW5jZTEtZDIiLCJiaXJ0aGRheSI6IjE5NzctMTItMDNUMTA6MTU6MzAuMDAwWiIsImV4cGVyaWVuY2UiOjE1LCJyYXRpbmciOjUsIm9uX2hvbGlkYXkiOmZhbHNlLCJzcGVjaWFsaXR5IjpbIkNhcmRpb2xvZ3kiLCJDYXJkaW90aG9yYWNpYyBTdXJnZXJ5Il0sInRlbGVwaG9uZSI6WzE0MjIzNCwxMjc1ODRdfQ==", + endCursor: + "eyJkb2N0b3JfaWQiOiJpbnN0YW5jZTEtZDMiLCJiaXJ0aGRheSI6IjE5ODctMTItMDNUMTA6MTU6MzAuMDAwWiIsImV4cGVyaWVuY2UiOjUsInJhdGluZyI6NC44LCJvbl9ob2xpZGF5Ijp0cnVlLCJzcGVjaWFsaXR5IjpbIkRlcm1hdG9sb2d5IiwiQWxsZXJnb2xvZ3kiXSwidGVsZXBob25lIjpbMTYyMjM0LDE3NzU4NF19", + hasNextPage: true, + hasPreviousPage: false, + }, + }, + }, + }); + }); + + it("04. presto_doctor DDM: count presto_doctors", () => { + res = itHelpers.request_graph_ql_post_instance2( + `{countDist_presto_doctors}` + ); + resBody = JSON.parse(res.body.toString("utf8")); + expect(res.statusCode).to.equal(200); + expect(resBody.data.countDist_presto_doctors).equal(5); + }); +}); diff --git a/test/mocha_integration_trino.test.js b/test/mocha_integration_trino.test.js new file mode 100644 index 00000000..d446f7d8 --- /dev/null +++ b/test/mocha_integration_trino.test.js @@ -0,0 +1,538 @@ +const { expect } = require("chai"); +const itHelpers = require("./integration_test_misc/integration_test_helpers"); + +describe("Trino - Read Access", () => { + it("01. trino_doctor: read one record", () => { + let res = itHelpers.request_graph_ql_post_instance2(` + { + readOneTrino_doctor(doctor_id:"d1"){ + doctor_id + birthday + experience + rating + on_holiday + speciality + telephone + } + }`); + let resBody = JSON.parse(res.body.toString("utf8")); + + expect(res.statusCode).to.equal(200); + expect(resBody).to.deep.equal({ + data: { + readOneTrino_doctor: { + doctor_id: "d1", + birthday: "1989-12-03T10:15:30.000Z", + experience: 3, + + rating: 4.9, + on_holiday: false, + speciality: ["Tinnitus", "Allergology"], + telephone: [152234, 137584], + }, + }, + }); + }); + + it("02. trino_doctor: count trino_doctors with like operator", () => { + let res = itHelpers.request_graph_ql_post_instance2( + `{countTrino_doctors(search:{operator: like, field: doctor_id, + value: "d%"})}` + ); + let resBody = JSON.parse(res.body.toString("utf8")); + + expect(res.statusCode).to.equal(200); + expect(resBody.data.countTrino_doctors).equal(5); + }); + + it("03. trino_doctor: search with and operator", () => { + let res = itHelpers.request_graph_ql_post_instance2(` + { + trino_doctors( + search: { + operator: and, + search: [ + {operator: eq, field: doctor_id, value: "d1"}, + {operator: eq, field: on_holiday, value: "false"} + ] + }, + pagination:{limit:5}) { + doctor_id + on_holiday + } + }`); + let resBody = JSON.parse(res.body.toString("utf8")); + + expect(res.statusCode).to.equal(200); + expect(resBody).to.deep.equal({ + data: { + trino_doctors: [ + { + doctor_id: "d1", + on_holiday: false, + }, + ], + }, + }); + }); + + it("04. trino_doctor: search with or operator", () => { + let res = itHelpers.request_graph_ql_post_instance2(` + { + trino_doctors( + search: { + operator: or, + search: [ + {operator: eq, field: doctor_id, value: "d1"}, + {operator: eq, field: doctor_id, value: "d2"} + ] + }, + pagination:{limit:5}) { + doctor_id + } + }`); + let resBody = JSON.parse(res.body.toString("utf8")); + + expect(res.statusCode).to.equal(200); + expect(resBody).to.deep.equal({ + data: { + trino_doctors: [ + { + doctor_id: "d1", + }, + { + doctor_id: "d2", + }, + ], + }, + }); + }); + + it("05. trino_doctor: search with in operator for primitive field", () => { + let res = itHelpers.request_graph_ql_post_instance2(` + { + trino_doctors( + search: { + operator: in, + field: doctor_id, + value: "d1,d2,d3", + valueType: Array + }, + pagination:{limit:5}) { + doctor_id + } + }`); + let resBody = JSON.parse(res.body.toString("utf8")); + + expect(res.statusCode).to.equal(200); + expect(resBody.data.trino_doctors.length).equal(3); + }); + + it("06. trino_doctor: search with not & in operators for primitive field", () => { + let res = itHelpers.request_graph_ql_post_instance2(` + { + trino_doctors( + search:{ + operator: not, + search: { + operator: in, + field: doctor_id, + value: "d1,d2,d3", + valueType: Array + } + }, + pagination:{limit:5}) { + doctor_id + } + }`); + let resBody = JSON.parse(res.body.toString("utf8")); + + expect(res.statusCode).to.equal(200); + expect(resBody.data.trino_doctors.length).equal(2); + }); + + it("07. trino_doctor: search with in operator for array field", () => { + let res = itHelpers.request_graph_ql_post_instance2(` + { + trino_doctors( + search:{operator:in, field:telephone, value:"152234"}, + pagination:{limit:5}) { + doctor_id + } + }`); + let resBody = JSON.parse(res.body.toString("utf8")); + + expect(res.statusCode).to.equal(200); + expect(resBody.data.trino_doctors.length).equal(1); + + res = itHelpers.request_graph_ql_post_instance2(` + { + trino_doctors( + search:{operator:in, field:speciality, value:"Tinnitus"}, + pagination:{limit:5}) { + doctor_id + } + }`); + resBody = JSON.parse(res.body.toString("utf8")); + + expect(res.statusCode).to.equal(200); + expect(resBody.data.trino_doctors.length).equal(1); + }); + + it("08. trino_doctor: search with not & in operators for array field", () => { + let res = itHelpers.request_graph_ql_post_instance2(` + { + trino_doctors( + search:{ + operator:not, + search:{operator:in, field:telephone, value:"152234"} + }, + pagination:{limit:5}) { + doctor_id + } + }`); + let resBody = JSON.parse(res.body.toString("utf8")); + + expect(res.statusCode).to.equal(200); + expect(resBody.data.trino_doctors.length).equal(4); + }); + + it("09. trino_doctor: search with eq operator for array field", () => { + let res = itHelpers.request_graph_ql_post_instance2(` + { + trino_doctors( + search:{operator:eq, field:telephone, value:"[152234,137584]"}, + pagination:{limit:5}) { + doctor_id + } + }`); + let resBody = JSON.parse(res.body.toString("utf8")); + + expect(res.statusCode).to.equal(200); + expect(resBody.data.trino_doctors.length).equal(1); + + res = itHelpers.request_graph_ql_post_instance2(` + { + trino_doctors( + search:{operator:eq, field:telephone, value:"152234,137584", valueType:Array}, + pagination:{limit:5}) { + doctor_id + } + }`); + resBody = JSON.parse(res.body.toString("utf8")); + + expect(res.statusCode).to.equal(200); + expect(resBody.data.trino_doctors.length).equal(1); + + res = itHelpers.request_graph_ql_post_instance2(` + { + trino_doctors( + search:{operator:eq, field:speciality, value:"Tinnitus,Allergology", valueType:Array}, + pagination:{limit:5}) { + doctor_id + } + }`); + resBody = JSON.parse(res.body.toString("utf8")); + + expect(res.statusCode).to.equal(200); + expect(resBody.data.trino_doctors.length).equal(1); + }); + + it("10. trino_doctor: search with ne operator for array field", () => { + let res = itHelpers.request_graph_ql_post_instance2(` + { + trino_doctors( + search:{operator:ne, field:speciality, value:"Tinnitus,Allergology", valueType:Array}, + pagination:{limit:5}) { + doctor_id + } + }`); + let resBody = JSON.parse(res.body.toString("utf8")); + + expect(res.statusCode).to.equal(200); + expect(resBody.data.trino_doctors.length).equal(4); + }); + + it("11. trino_doctor: sort", () => { + let res = itHelpers.request_graph_ql_post_instance2( + `{ + trino_doctors(pagination: {limit:5}, order: [{field: doctor_id, order: DESC}]) { + doctor_id + } + }` + ); + let resBody = JSON.parse(res.body.toString("utf8")); + + expect(res.statusCode).to.equal(200); + + expect(resBody).to.deep.equal({ + data: { + trino_doctors: [ + { doctor_id: "d5" }, + { doctor_id: "d4" }, + { doctor_id: "d3" }, + { doctor_id: "d2" }, + { doctor_id: "d1" }, + ], + }, + }); + }); + + it("12. trino_doctor: paginate", () => { + let res = itHelpers.request_graph_ql_post_instance2( + `{ + trino_doctorsConnection(pagination:{first:10}) { + edges{ + cursor + node{ + doctor_id + } + } + } + }` + ); + let resBody = JSON.parse(res.body.toString("utf8")); + let edges = resBody.data.trino_doctorsConnection.edges; + let idArray = edges.map((edge) => edge.node.doctor_id); + let cursorArray = edges.map((edge) => edge.cursor); + res = itHelpers.request_graph_ql_post_instance2( + `{ + trino_doctorsConnection(pagination:{first: 2, after: "${cursorArray[1]}"}) { + edges{ + cursor + node{ + doctor_id + } + } + trino_doctors{ + doctor_id + } + pageInfo{ + startCursor + endCursor + hasNextPage + hasPreviousPage + } + } + }` + ); + resBody = JSON.parse(res.body.toString("utf8")); + expect(res.statusCode).to.equal(200); + + expect(resBody).to.deep.equal({ + data: { + trino_doctorsConnection: { + edges: [ + { + cursor: cursorArray[2], + node: { + doctor_id: idArray[2], + }, + }, + { + cursor: cursorArray[3], + node: { + doctor_id: idArray[3], + }, + }, + ], + trino_doctors: [ + { + doctor_id: "d3", + }, + { + doctor_id: "d4", + }, + ], + pageInfo: { + startCursor: cursorArray[2], + endCursor: cursorArray[3], + hasNextPage: true, + hasPreviousPage: true, + }, + }, + }, + }); + + res = itHelpers.request_graph_ql_post_instance2( + `{ + trino_doctorsConnection(pagination: {last: 4, before:"${cursorArray[4]}"}) { + pageInfo { + startCursor + endCursor + hasNextPage + hasPreviousPage + } + } + }` + ); + resBody = JSON.parse(res.body.toString("utf8")); + expect(res.statusCode).to.equal(200); + + expect(resBody).to.deep.equal({ + data: { + trino_doctorsConnection: { + pageInfo: { + startCursor: cursorArray[0], + endCursor: cursorArray[3], + hasNextPage: true, + hasPreviousPage: false, + }, + }, + }, + }); + }); + + it("13. trino_doctor: get the table template", () => { + let res = itHelpers.request_graph_ql_post_instance2( + `{csvTableTemplateTrino_doctor}` + ); + let resBody = JSON.parse(res.body.toString("utf8")); + expect(res.statusCode).to.equal(200); + expect(resBody).to.deep.equal({ + data: { + csvTableTemplateTrino_doctor: [ + "doctor_id,birthday,experience,rating,on_holiday,speciality,telephone", + "String,DateTime,Int,Float,Boolean,[String],[Int]", + ], + }, + }); + }); +}); + +describe("Trino - Distributed Data Models", () => { + it("01. trino_doctor: read one trino_doctor", () => { + let res = itHelpers.request_graph_ql_post_instance2(` + { + readOneDist_trino_doctor(doctor_id:"instance1-d1"){ + doctor_id + birthday + experience + rating + on_holiday + speciality + telephone + } + }`); + let resBody = JSON.parse(res.body.toString("utf8")); + + expect(res.statusCode).to.equal(200); + expect(resBody).to.deep.equal({ + data: { + readOneDist_trino_doctor: { + doctor_id: "instance1-d1", + birthday: "1989-12-03T10:15:30.000Z", + experience: 3, + rating: 4.9, + on_holiday: false, + speciality: ["Tinnitus", "Allergology"], + telephone: [152234, 137584], + }, + }, + }); + }); + + it("02. trino_doctor DDM: search & sort", () => { + res = itHelpers.request_graph_ql_post_instance2( + `{ + dist_trino_doctorsConnection( + search: {field: doctor_id, value: "instance1%", operator: like}, + order: [{field: doctor_id, order: DESC}] + pagination: {first: 5}) { + dist_trino_doctors{ + doctor_id + } + } + } + ` + ); + resBody = JSON.parse(res.body.toString("utf8")); + expect(res.statusCode).to.equal(200); + expect(resBody).to.deep.equal({ + data: { + dist_trino_doctorsConnection: { + dist_trino_doctors: [ + { doctor_id: "instance1-d5" }, + { doctor_id: "instance1-d4" }, + { doctor_id: "instance1-d3" }, + { doctor_id: "instance1-d2" }, + { doctor_id: "instance1-d1" }, + ], + }, + }, + }); + }); + + it("03. trino_doctor DDM: paginate", () => { + res = itHelpers.request_graph_ql_post_instance2( + `{ + dist_trino_doctorsConnection(pagination: { + first: 2, + after: "eyJkb2N0b3JfaWQiOiJpbnN0YW5jZTEtZDEiLCJiaXJ0aGRheSI6IjE5ODktMTItMDNUMTA6MTU6MzAuMDAwWiIsImV4cGVyaWVuY2UiOjMsInJhdGluZyI6NC45LCJvbl9ob2xpZGF5IjpmYWxzZSwic3BlY2lhbGl0eSI6WyJUaW5uaXR1cyIsIkFsbGVyZ29sb2d5Il0sInRlbGVwaG9uZSI6WzE1MjIzNCwxMzc1ODRdfQ==" + }){ + edges { + node { + doctor_id + } + cursor + } + dist_trino_doctors{ + doctor_id + } + pageInfo { + startCursor + endCursor + hasNextPage + hasPreviousPage + } + } + } + ` + ); + resBody = JSON.parse(res.body.toString("utf8")); + expect(res.statusCode).to.equal(200); + expect(resBody).to.deep.equal({ + data: { + dist_trino_doctorsConnection: { + edges: [ + { + cursor: + "eyJkb2N0b3JfaWQiOiJpbnN0YW5jZTEtZDIiLCJiaXJ0aGRheSI6IjE5NzctMTItMDNUMTA6MTU6MzAuMDAwWiIsImV4cGVyaWVuY2UiOjE1LCJyYXRpbmciOjUsIm9uX2hvbGlkYXkiOmZhbHNlLCJzcGVjaWFsaXR5IjpbIkNhcmRpb2xvZ3kiLCJDYXJkaW90aG9yYWNpYyBTdXJnZXJ5Il0sInRlbGVwaG9uZSI6WzE0MjIzNCwxMjc1ODRdfQ==", + node: { + doctor_id: "instance1-d2", + }, + }, + { + cursor: + "eyJkb2N0b3JfaWQiOiJpbnN0YW5jZTEtZDMiLCJiaXJ0aGRheSI6IjE5ODctMTItMDNUMTA6MTU6MzAuMDAwWiIsImV4cGVyaWVuY2UiOjUsInJhdGluZyI6NC44LCJvbl9ob2xpZGF5Ijp0cnVlLCJzcGVjaWFsaXR5IjpbIkRlcm1hdG9sb2d5IiwiQWxsZXJnb2xvZ3kiXSwidGVsZXBob25lIjpbMTYyMjM0LDE3NzU4NF19", + node: { + doctor_id: "instance1-d3", + }, + }, + ], + dist_trino_doctors: [ + { doctor_id: "instance1-d2" }, + { doctor_id: "instance1-d3" }, + ], + pageInfo: { + startCursor: + "eyJkb2N0b3JfaWQiOiJpbnN0YW5jZTEtZDIiLCJiaXJ0aGRheSI6IjE5NzctMTItMDNUMTA6MTU6MzAuMDAwWiIsImV4cGVyaWVuY2UiOjE1LCJyYXRpbmciOjUsIm9uX2hvbGlkYXkiOmZhbHNlLCJzcGVjaWFsaXR5IjpbIkNhcmRpb2xvZ3kiLCJDYXJkaW90aG9yYWNpYyBTdXJnZXJ5Il0sInRlbGVwaG9uZSI6WzE0MjIzNCwxMjc1ODRdfQ==", + endCursor: + "eyJkb2N0b3JfaWQiOiJpbnN0YW5jZTEtZDMiLCJiaXJ0aGRheSI6IjE5ODctMTItMDNUMTA6MTU6MzAuMDAwWiIsImV4cGVyaWVuY2UiOjUsInJhdGluZyI6NC44LCJvbl9ob2xpZGF5Ijp0cnVlLCJzcGVjaWFsaXR5IjpbIkRlcm1hdG9sb2d5IiwiQWxsZXJnb2xvZ3kiXSwidGVsZXBob25lIjpbMTYyMjM0LDE3NzU4NF19", + hasNextPage: true, + hasPreviousPage: false, + }, + }, + }, + }); + }); + + it("04. trino_doctor DDM: count trino_doctors", () => { + res = itHelpers.request_graph_ql_post_instance2( + `{countDist_trino_doctors}` + ); + resBody = JSON.parse(res.body.toString("utf8")); + expect(res.statusCode).to.equal(200); + expect(resBody.data.countDist_trino_doctors).equal(5); + }); +}); diff --git a/test/mocha_unit.test.js b/test/mocha_unit.test.js index 40033ac2..1a97f822 100644 --- a/test/mocha_unit.test.js +++ b/test/mocha_unit.test.js @@ -11,6 +11,7 @@ const models_generic = require("./unit_test_misc/data_models_generic"); const models_cassandra = require("./unit_test_misc/data_models_cassandra"); const models_mongodb = require("./unit_test_misc/data_models_mongodb"); const models_amazonS3 = require("./unit_test_misc/data_models_amazonS3"); +const models_trino = require("./unit_test_misc/data_models_trino"); const requireFromString = require("require-from-string"); const helpers = require("./unit_test_misc/helpers/reporting_helpers"); const { test } = require("mocha"); @@ -3016,3 +3017,43 @@ describe("Amazon S3/ Minio Unit Test", function () { testCompare(generated_model, data_test.amazonS3_adapter_readById); }); }); + +describe("Trino/Presto Unit Test", () => { + let data_test = require("./unit_test_misc/test-describe/trino-unittest.js"); + + it("Trino model - doctor constructor", async () => { + let opts = funks.getOptions(models_trino.doctor); + let generated_model = await funks.generateJs("create-models-trino", opts); + testCompare(generated_model, data_test.doctor_constructor); + }); + + it("Trino model - doctor readById", async () => { + let opts = funks.getOptions(models_trino.doctor); + let generated_model = await funks.generateJs("create-models-trino", opts); + testCompare(generated_model, data_test.doctor_readById); + }); + + it("Trino model - doctor countRecords", async () => { + let opts = funks.getOptions(models_trino.doctor); + let generated_model = await funks.generateJs("create-models-trino", opts); + testCompare(generated_model, data_test.doctor_countRecords); + }); + + it("Trino model - doctor readAll", async () => { + let opts = funks.getOptions(models_trino.doctor); + let generated_model = await funks.generateJs("create-models-trino", opts); + testCompare(generated_model, data_test.doctor_readAll); + }); + + it("Trino model - doctor readAllCursor", async () => { + let opts = funks.getOptions(models_trino.doctor); + let generated_model = await funks.generateJs("create-models-trino", opts); + testCompare(generated_model, data_test.doctor_readAllCursor); + }); + + it("Trino adapter - dist_doctor_instance1 readById ", async () => { + let opts = funks.getOptions(models_trino.dist_doctor_instance1); + let generated_model = await funks.generateJs("create-trino-adapter", opts); + testCompare(generated_model, data_test.trino_adapter_readById); + }); +}); diff --git a/test/testenv_cli.sh b/test/testenv_cli.sh index cb47a65b..5fba6eee 100644 --- a/test/testenv_cli.sh +++ b/test/testenv_cli.sh @@ -112,6 +112,8 @@ if [[ $OPT_RUN_TESTS == "true" ]]; then mocha "${TEST_DIR}/mocha_integration_mongodb.test.js" mocha "${TEST_DIR}/mocha_integration_cassandra.test.js" mocha "${TEST_DIR}/mocha_integration_amazon_s3.test.js" + mocha --timeout 10000 "${TEST_DIR}/mocha_integration_trino.test.js" + mocha --timeout 10000 "${TEST_DIR}/mocha_integration_presto.test.js" # 1. Remove docker containers, images, and volumes # 2. Remove the testing environment @@ -138,6 +140,8 @@ if [[ $OPT_GENCODE_RUNTESTS == "true" ]]; then mocha "${TEST_DIR}/mocha_integration_mongodb.test.js" mocha "${TEST_DIR}/mocha_integration_cassandra.test.js" mocha "${TEST_DIR}/mocha_integration_amazon_s3.test.js" + mocha --timeout 10000 "${TEST_DIR}/mocha_integration_trino.test.js" + mocha --timeout 10000 "${TEST_DIR}/mocha_integration_presto.test.js" # 1. Remove docker containers, images, and volumes # 2. Remove the testing environment @@ -166,6 +170,8 @@ if [[ $DEFAULT_RUN == "true" ]]; then mocha "${TEST_DIR}/mocha_integration_mongodb.test.js" mocha "${TEST_DIR}/mocha_integration_cassandra.test.js" mocha "${TEST_DIR}/mocha_integration_amazon_s3.test.js" + mocha --timeout 10000 "${TEST_DIR}/mocha_integration_trino.test.js" + mocha --timeout 10000 "${TEST_DIR}/mocha_integration_presto.test.js" # 1. Remove docker containers, images, and volumes # 2. Remove the testing environment diff --git a/test/unit_test_misc/data_models_trino.js b/test/unit_test_misc/data_models_trino.js new file mode 100644 index 00000000..441c49e5 --- /dev/null +++ b/test/unit_test_misc/data_models_trino.js @@ -0,0 +1,35 @@ +module.exports.doctor = { + model: "doctor", + storageType: "trino", + attributes: { + doctor_id: "String", + birthday: "DateTime", + experience: "Int", + rating: "Float", + on_holiday: "Boolean", + speciality: "[String]", + telephone: "[Int]", + }, + internalId: "doctor_id", + id: { + name: "doctor_id", + type: "String", + }, +}; + +module.exports.dist_doctor_instance1 = { + model: "dist_doctor", + storageType: "trino-adapter", + adapterName: "dist_doctor_instance1", + regex: "instance1", + attributes: { + doctor_id: "String", + birthday: "DateTime", + experience: "Int", + rating: "Float", + on_holiday: "Boolean", + speciality: "[String]", + telephone: "[Int]", + }, + internalId: "doctor_id", +}; diff --git a/test/unit_test_misc/test-describe/cassandra-storagetype.js b/test/unit_test_misc/test-describe/cassandra-storagetype.js index 645c2637..0189285b 100644 --- a/test/unit_test_misc/test-describe/cassandra-storagetype.js +++ b/test/unit_test_misc/test-describe/cassandra-storagetype.js @@ -456,6 +456,8 @@ static readAllCursor(search, order, pagination, authorizedAdapters, benignErrorR case 'sql-adapter': case 'mongodb-adapter': case 'amazon-s3-adapter': + case 'trino-adapter': + case 'presto-adapter': case 'zendro-webservice-adapter': return adapter.readAllCursor(search, order, pagination, benignErrorReporter); case 'cassandra-adapter': diff --git a/test/unit_test_misc/test-describe/distributed-models.js b/test/unit_test_misc/test-describe/distributed-models.js index bc9e77bd..2670362c 100644 --- a/test/unit_test_misc/test-describe/distributed-models.js +++ b/test/unit_test_misc/test-describe/distributed-models.js @@ -167,6 +167,8 @@ static countRecords(search, authorizedAdapters, benignErrorReporter, searchAutho case 'sql-adapter': case 'mongodb-adapter': case 'amazon-s3-adapter': + case 'trino-adapter': + case 'presto-adapter': case 'zendro-webservice-adapter': return adapter.countRecords(search, benignErrorReporter); case 'cassandra-adapter': @@ -243,6 +245,8 @@ static readAllCursor(search, order, pagination, authorizedAdapters, benignErrorR case 'sql-adapter': case 'mongodb-adapter': case 'amazon-s3-adapter': + case 'trino-adapter': + case 'presto-adapter': case 'zendro-webservice-adapter': return adapter.readAllCursor(search, order, pagination, benignErrorReporter); case 'cassandra-adapter': diff --git a/test/unit_test_misc/test-describe/handle-error-ddm.js b/test/unit_test_misc/test-describe/handle-error-ddm.js index d446c1f0..ba0620bc 100644 --- a/test/unit_test_misc/test-describe/handle-error-ddm.js +++ b/test/unit_test_misc/test-describe/handle-error-ddm.js @@ -47,6 +47,8 @@ static countRecords(search, authorizedAdapters, benignErrorReporter, searchAutho case 'sql-adapter': case 'mongodb-adapter': case 'amazon-s3-adapter': + case 'trino-adapter': + case 'presto-adapter': case 'zendro-webservice-adapter': return adapter.countRecords(search, benignErrorReporter); case 'cassandra-adapter': @@ -124,6 +126,8 @@ static readAllCursor(search, order, pagination, authorizedAdapters, benignErrorR case 'sql-adapter': case 'mongodb-adapter': case 'amazon-s3-adapter': + case 'trino-adapter': + case 'presto-adapter': case 'zendro-webservice-adapter': return adapter.readAllCursor(search, order, pagination, benignErrorReporter); case 'cassandra-adapter': diff --git a/test/unit_test_misc/test-describe/trino-unittest.js b/test/unit_test_misc/test-describe/trino-unittest.js new file mode 100644 index 00000000..ade313b7 --- /dev/null +++ b/test/unit_test_misc/test-describe/trino-unittest.js @@ -0,0 +1,220 @@ +module.exports.doctor_constructor = ` +constructor(input) { + for (let key of Object.keys(input)) { + this[key] = input[key]; + } +} +`; +module.exports.doctor_readById = ` +static async readById(id) { + const query = \`SELECT * FROM doctors WHERE \${this.idAttribute()} = '\${id}'\`; + let item = null; + try { + const client = await this.storageHandler; + item = await prestoHelper.queryData(query, client); + + if (!item) { + throw new Error(\`Record with ID = "\${id}" does not exist\`); + } + } catch (e) { + throw new Error(e); + } + item = doctor.postReadCast(item)[0]; + return validatorUtil.validateData("validateAfterRead", this, item); +} +`; +module.exports.doctor_countRecords = ` +static async countRecords(search, benignErrorReporter) { + const whereOptions = prestoHelper.searchConditionsToTrino( + search, + definition + ); + const query = \`SELECT COUNT(*) AS num FROM doctors \${whereOptions}\`; + let num = null; + try { + const client = await this.storageHandler; + const result = await prestoHelper.queryData(query, client); + num = result[1][0][0]; + } catch (e) { + throw new Error(e); + } + return num; +} +`; +module.exports.doctor_readAll = ` +static async readAll(search, order, pagination, benignErrorReporter) { + //use default BenignErrorReporter if no BenignErrorReporter defined + benignErrorReporter = errorHelper.getDefaultBenignErrorReporterIfUndef( + benignErrorReporter + ); + // build the whereOptions for limit-offset-based pagination + const whereOptions = prestoHelper.searchConditionsToTrino( + search, + definition + ); + const orderOptions = prestoHelper.orderConditionsToTrino( + order, + this.idAttribute(), + true + ); + + const limit = pagination.limit; + const offset = pagination.offset ? pagination.offset : 0; + + let query = \`SELECT * FROM (SELECT row_number() over() AS rn, * FROM trino_doctors) \`; + query += + whereOptions !== "" + ? \`\${whereOptions} AND (rn BETWEEN \${offset + 1} AND \${offset + limit})\` + : \`WHERE rn BETWEEN \${offset + 1} AND \${offset + limit}\`; + query += \` \${orderOptions}\`; + + let result = null; + try { + const client = await this.storageHandler; + result = await prestoHelper.queryData(query, client); + } catch (e) { + throw new Error(e); + } + result = doctor.postReadCast(result); + + return validatorUtil.bulkValidateData( + "validateAfterRead", + this, + result, + benignErrorReporter + ); +} +`; + +module.exports.doctor_readAllCursor = ` +static async readAllCursor(search, order, pagination, benignErrorReporter) { + //use default BenignErrorReporter if no BenignErrorReporter defined + benignErrorReporter = errorHelper.getDefaultBenignErrorReporterIfUndef( + benignErrorReporter + ); + let isForwardPagination = helper.isForwardPagination(pagination); + // build the whereOptions. + let filter = prestoHelper.searchConditionsToTrino(search, definition); + let newOrder = isForwardPagination + ? order + : helper.reverseOrderConditions(order); + // depending on the direction build the order object + let sort = prestoHelper.orderConditionsToTrino( + newOrder, + this.idAttribute(), + isForwardPagination + ); + let orderFields = newOrder ? newOrder.map((x) => x.field) : []; + // extend the filter for the given order and cursor + filter = prestoHelper.cursorPaginationArgumentsToTrino( + pagination, + sort, + filter, + orderFields, + this.idAttribute(), + definition.attributes + ); + + // add +1 to the LIMIT to get information about following pages. + let limit = helper.isNotUndefinedAndNotNull(pagination.first) + ? pagination.first + 1 + : helper.isNotUndefinedAndNotNull(pagination.last) + ? pagination.last + 1 + : undefined; + + let query = \`SELECT * FROM doctors + \${filter} + \${sort} + LIMIT \${limit}\`; + let result = null; + + const client = await this.storageHandler; + result = await prestoHelper.queryData(query, client); + + result = doctor.postReadCast(result); + // validationCheck after read + result = await validatorUtil.bulkValidateData( + "validateAfterRead", + this, + result, + benignErrorReporter + ); + // get the first record (if exists) in the opposite direction to determine pageInfo. + // if no cursor was given there is no need for an extra query as the results will start at the first (or last) page. + let oppResult = []; + if (pagination && (pagination.after || pagination.before)) { + // reverse the pagination Arguement. after -> before; set first/last to 0, so LIMIT 1 is executed in the reverse Search + let oppPagination = helper.reversePaginationArgument({ + ...pagination, + includeCursor: false, + }); + let oppForwardPagination = helper.isForwardPagination(oppPagination); + // build the filter object. + let oppFilter = prestoHelper.searchConditionsToTrino(search, definition); + + let oppOrder = oppForwardPagination + ? order + : helper.reverseOrderConditions(order); + // depending on the direction build the order object + let oppSort = prestoHelper.orderConditionsToTrino( + oppOrder, + this.idAttribute(), + oppForwardPagination + ); + let oppOrderFields = oppOrder ? oppOrder.map((x) => x.field) : []; + // extend the filter for the given order and cursor + oppFilter = prestoHelper.cursorPaginationArgumentsToTrino( + oppPagination, + oppSort, + oppFilter, + oppOrderFields, + this.idAttribute(), + definition.attributes + ); + // add +1 to the LIMIT to get information about following pages. + let oppLimit = helper.isNotUndefinedAndNotNull(oppPagination.first) + ? oppPagination.first + 1 + : helper.isNotUndefinedAndNotNull(oppPagination.last) + ? oppPagination.last + 1 + : undefined; + query = \`SELECT * FROM doctors + \${oppFilter} + \${oppSort} + LIMIT \${oppLimit}\`; + oppResult = await prestoHelper.queryData(query, client); + oppResult = doctor.postReadCast(oppResult); + } + + // build the graphql Connection Object + result = result.map((res) => { + return new doctor(res); + }); + let edges = result.map((res) => { + return { + node: res, + cursor: res.base64Enconde(), + }; + }); + const pageInfo = helper.buildPageInfo(edges, oppResult, pagination); + return { edges, pageInfo, doctors: edges.map((edge) => edge.node) }; +} +`; + +module.exports.trino_adapter_readById = ` +static async readById(id){ + const query = \`SELECT * FROM dist_doctors WHERE \${this.idAttribute()} = '\${id}'\`; + let item = null; + try { + const client = await this.storageHandler; + item = await prestoHelper.queryData(query, client); + + if (!item) { + throw new Error(\`Record with ID = "\${id}" does not exist\`); + } + } catch (e) { + throw new Error(e); + } + item = dist_doctor_instance1.postReadCast(item)[0]; + return validatorUtil.validateData("validateAfterRead", this, item); +} +`; diff --git a/views/create-distributed-model.ejs b/views/create-distributed-model.ejs index afa28b5e..37432e94 100644 --- a/views/create-distributed-model.ejs +++ b/views/create-distributed-model.ejs @@ -135,6 +135,8 @@ module.exports = class <%- nameLc -%>{ case 'sql-adapter': case 'mongodb-adapter': case 'amazon-s3-adapter': + case 'trino-adapter': + case 'presto-adapter': case 'zendro-webservice-adapter': return adapter.countRecords(search, benignErrorReporter); case 'cassandra-adapter': @@ -209,6 +211,8 @@ module.exports = class <%- nameLc -%>{ case 'sql-adapter': case 'mongodb-adapter': case 'amazon-s3-adapter': + case 'trino-adapter': + case 'presto-adapter': case 'zendro-webservice-adapter': return adapter.readAllCursor(search, order, pagination, benignErrorReporter); case 'cassandra-adapter': diff --git a/views/create-models-amazonS3.ejs b/views/create-models-amazonS3.ejs index 24e8b33f..c3b18fdf 100644 --- a/views/create-models-amazonS3.ejs +++ b/views/create-models-amazonS3.ejs @@ -5,9 +5,7 @@ const validatorUtil = require("../../utils/validatorUtil"); const helper = require("../../utils/helper"); const errorHelper = require("../../utils/errors"); const fs = require("fs"); -const config = require("../../config/data_models_storage_config.json")[ - "default-amazonS3" -]; +const config = require("../../config/data_models_storage_config.json")["<%- database -%>"]; const amazonS3Helper = require("../../utils/amazonS3_helper"); const path = require("path"); const uuidv4 = require("uuidv4").uuid; diff --git a/views/create-models-trino.ejs b/views/create-models-trino.ejs new file mode 100644 index 00000000..c0657d89 --- /dev/null +++ b/views/create-models-trino.ejs @@ -0,0 +1,426 @@ +"use strict"; + +const _ = require("lodash"); +const validatorUtil = require("../../utils/validatorUtil"); +const helper = require("../../utils/helper"); +const errorHelper = require("../../utils/errors"); +const fs = require("fs"); +const prestoHelper = require("../../utils/presto_helper"); +const path = require("path"); +const uuidv4 = require("uuidv4").uuid; +const os = require("os"); + +// An exact copy of the the model definition that comes from the .json file +const definition = <%- definition -%>; +/** + * module - Creates a class to administer model + */ +module.exports = class <%- nameLc -%> { + constructor(input) { + for (let key of Object.keys(input)) { + this[key] = input[key]; + } + } + + get storageHandler() { + return <%- nameLc -%>.storageHandler; + } + + /** + * name - Getter for the name attribute + * + * This attribute is needed by the models' index + * @return {string} The name of the model + */ + static get name() { + return "<%- nameLc -%>"; + } + + /** + * Cast JSON string to array for the validation. + * @param {object} record Record with JSON string if necessary. + * @return {object} Parsed data record. + */ + static postReadCast(record) { + if (!record) { + return []; + } + const column_index = {}; + record[0].map((obj, index) => { + column_index[obj.name] = index; + }); + + let result = []; + for (const item of record[1]) { + let record = {}; + for (const attr in definition.attributes) { + const type = definition.attributes[attr].replace(/\s+/g, ""); + if ( + type[0] === "[" && + item[column_index[attr]] !== undefined && + item[column_index[attr]] !== null + ) { + record[attr] = JSON.parse(item[column_index[attr]]); + } else if (type === "DateTime") { + record[attr] = new Date(item[column_index[attr]]).toISOString(); + } else { + record[attr] = item[column_index[attr]]; + } + } + result.push(record); + } + return result; + } + + /** + * readById - The model implementation for reading a single record given by its ID + * + * This method is the implementation for reading a single record for the trino storage type, based on SQL. + * @param {string} id - The ID of the requested record + * @return {object} The requested record as an object with the type <%- nameLc -%>, or an error object if the validation after reading fails + * @throws {Error} If the requested record does not exist + */ + static async readById(id) { + const query = `SELECT * FROM <%- namePl -%> WHERE ${this.idAttribute()} = '${id}'`; + let item = null; + try { + const client = await this.storageHandler; + item = await prestoHelper.queryData(query, client); + + if (!item) { + throw new Error(`Record with ID = "${id}" does not exist`); + } + } catch (e) { + throw new Error(e); + } + item = <%- nameLc -%>.postReadCast(item)[0]; + return validatorUtil.validateData("validateAfterRead", this, item); + } + + /** + * countRecords - The model implementation for counting the number of records, possibly restricted by a search term + * + * This method is the implementation for counting the number of records that fulfill a given condition, or for all records in the table, + * for the trino storage type, based on SQL. + * @param {object} search - The search term that restricts the set of records to be counted - if undefined, all records in the table + * @param {BenignErrorReporter} benignErrorReporter can be used to generate the standard + * @return {number} The number of records that fulfill the condition, or of all records in the table + */ + static async countRecords(search, benignErrorReporter) { + const whereOptions = prestoHelper.searchConditionsToTrino( + search, + definition + ); + const query = `SELECT COUNT(*) AS num FROM <%- namePl -%> ${whereOptions}`; + let num = null; + try { + const client = await this.storageHandler; + const result = await prestoHelper.queryData(query, client); + num = result[1][0][0]; + } catch (e) { + throw new Error(e); + } + return num; + } + + /** + * readAll - Limit-offset based pagination is not offered by Trino, and this method is left here only as information + * to the user / developer. Use *readAllCursor* instead, which relies on cursor based pagination. + * @throw {Error} If this method is used at all, an Error is thrown + */ + static async readAll(search, order, pagination, benignErrorReporter) { + //use default BenignErrorReporter if no BenignErrorReporter defined + benignErrorReporter = errorHelper.getDefaultBenignErrorReporterIfUndef( + benignErrorReporter + ); + // build the whereOptions for limit-offset-based pagination + const whereOptions = prestoHelper.searchConditionsToTrino( + search, + definition + ); + const orderOptions = prestoHelper.orderConditionsToTrino( + order, + this.idAttribute(), + true + ); + + const limit = pagination.limit; + const offset = pagination.offset ? pagination.offset : 0; + + let query = `SELECT * FROM (SELECT row_number() over() AS rn, * FROM trino_doctors) `; + query += + whereOptions !== "" + ? `${whereOptions} AND (rn BETWEEN ${offset + 1} AND ${offset + limit})` + : `WHERE rn BETWEEN ${offset + 1} AND ${offset + limit}`; + query += ` ${orderOptions}`; + let result = null; + try { + const client = await this.storageHandler; + result = await prestoHelper.queryData(query, client); + } catch (e) { + throw new Error(e); + } + result = <%- nameLc -%>.postReadCast(result); + + return validatorUtil.bulkValidateData( + "validateAfterRead", + this, + result, + benignErrorReporter + ); + } + + /** + * readAllCursor - The model implementation for searching for records in Trino. This method uses cursor based pagination. + * + * @param {object} search - The search condition for which records shall be fetched + * @param {object} pagination - The parameters for pagination, which can be used to get a subset of the requested record set. + * @param {BenignErrorReporter} benignErrorReporter can be used to generate the standard + * @return {object} The set of records, possibly constrained by pagination, with full cursor information for all records + */ + static async readAllCursor(search, order, pagination, benignErrorReporter) { + //use default BenignErrorReporter if no BenignErrorReporter defined + benignErrorReporter = errorHelper.getDefaultBenignErrorReporterIfUndef( + benignErrorReporter + ); + let isForwardPagination = helper.isForwardPagination(pagination); + // build the whereOptions. + let filter = prestoHelper.searchConditionsToTrino(search, definition); + let newOrder = isForwardPagination + ? order + : helper.reverseOrderConditions(order); + // depending on the direction build the order object + let sort = prestoHelper.orderConditionsToTrino( + newOrder, + this.idAttribute(), + isForwardPagination + ); + let orderFields = newOrder ? newOrder.map((x) => x.field) : []; + // extend the filter for the given order and cursor + filter = prestoHelper.cursorPaginationArgumentsToTrino( + pagination, + sort, + filter, + orderFields, + this.idAttribute(), + definition.attributes + ); + + // add +1 to the LIMIT to get information about following pages. + let limit = helper.isNotUndefinedAndNotNull(pagination.first) + ? pagination.first + 1 + : helper.isNotUndefinedAndNotNull(pagination.last) + ? pagination.last + 1 + : undefined; + + let query = `SELECT * FROM <%- namePl -%> + ${filter} + ${sort} + LIMIT ${limit}`; + let result = null; + + const client = await this.storageHandler; + result = await prestoHelper.queryData(query, client); + + result = <%- nameLc -%>.postReadCast(result); + // validationCheck after read + result = await validatorUtil.bulkValidateData( + "validateAfterRead", + this, + result, + benignErrorReporter + ); + // get the first record (if exists) in the opposite direction to determine pageInfo. + // if no cursor was given there is no need for an extra query as the results will start at the first (or last) page. + let oppResult = []; + if (pagination && (pagination.after || pagination.before)) { + // reverse the pagination Arguement. after -> before; set first/last to 0, so LIMIT 1 is executed in the reverse Search + let oppPagination = helper.reversePaginationArgument({ + ...pagination, + includeCursor: false, + }); + let oppForwardPagination = helper.isForwardPagination(oppPagination); + // build the filter object. + let oppFilter = prestoHelper.searchConditionsToTrino(search, definition); + + let oppOrder = oppForwardPagination + ? order + : helper.reverseOrderConditions(order); + // depending on the direction build the order object + let oppSort = prestoHelper.orderConditionsToTrino( + oppOrder, + this.idAttribute(), + oppForwardPagination + ); + let oppOrderFields = oppOrder ? oppOrder.map((x) => x.field) : []; + // extend the filter for the given order and cursor + oppFilter = prestoHelper.cursorPaginationArgumentsToTrino( + oppPagination, + oppSort, + oppFilter, + oppOrderFields, + this.idAttribute(), + definition.attributes + ); + // add +1 to the LIMIT to get information about following pages. + let oppLimit = helper.isNotUndefinedAndNotNull(oppPagination.first) + ? oppPagination.first + 1 + : helper.isNotUndefinedAndNotNull(oppPagination.last) + ? oppPagination.last + 1 + : undefined; + query = `SELECT * FROM <%- namePl -%> + ${oppFilter} + ${oppSort} + LIMIT ${oppLimit}`; + oppResult = await prestoHelper.queryData(query, client); + oppResult = <%- nameLc -%>.postReadCast(oppResult); + } + + // build the graphql Connection Object + result = result.map((res) => { + return new <%- nameLc -%>(res); + }); + let edges = result.map((res) => { + return { + node: res, + cursor: res.base64Enconde(), + }; + }); + const pageInfo = helper.buildPageInfo(edges, oppResult, pagination); + return { edges, pageInfo, <%- namePl -%>: edges.map((edge) => edge.node) }; + } + + /** + * addOne - Not implemented for Trino. + */ + static async addOne(input) { + throw new Error("Not supported by Trino"); + } + + /** + * deleteOne - Delete the whole file. + */ + static async deleteOne(id) { + throw new Error("Not supported by Trino"); + } + + /** + * updateOne - Not implemented for Trino. + */ + static async updateOne(input) { + throw new Error("Not supported by Trino"); + } + + /** + * bulkAddCsv - Add records from csv file + * + * @param {object} context - contextual information, e.g. csv file, record delimiter and column names. + */ + static async bulkAddCsv(context) { + throw new Error("Not supported by Trino"); + } + /** + * csvTableTemplate - Allows the user to download a template in CSV format with the + * properties and types of this model. + * + * @param {BenignErrorReporter} benignErrorReporter can be used to generate the standard + * GraphQL output {error: ..., data: ...}. If the function reportError of the benignErrorReporter + * is invoked, the server will include any so reported errors in the final response, i.e. the + * GraphQL response will have a non empty errors property. + */ + static async csvTableTemplate(benignErrorReporter) { + return helper.csvTableTemplate(definition); + } + + /** + * idAttribute - Check whether an attribute "internalId" is given in the JSON model. If not the standard "id" is used instead. + * + * @return {type} Name of the attribute that functions as an internalId + */ + + static idAttribute() { + return <%- nameLc -%>.definition.id.name; + } + + /** + * idAttributeType - Return the Type of the internalId. + * + * @return {type} Type given in the JSON model + */ + + static idAttributeType() { + return <%- nameLc -%>.definition.id.type; + } + + /** + * getIdValue - Get the value of the idAttribute ("id", or "internalId") for an instance of <%- nameLc -%>. + * + * @return {type} id value + */ + + getIdValue() { + return this[<%- nameLc -%>.idAttribute()]; + } + + /** + * definition - Getter for the attribute 'definition' + * @return {string} the definition string + */ + static get definition() { + return definition; + } + + /** + * base64Decode - Decode a base 64 String to UTF-8. + * @param {string} cursor - The cursor to be decoded into the record, given in base 64 + * @return {string} The stringified object in UTF-8 format + */ + static base64Decode(cursor) { + return Buffer.from(cursor, "base64").toString("utf-8"); + } + + /** + * base64Enconde - Encode <%- nameLc -%> to a base 64 String + * + * @return {string} The <%- nameLc -%> object, encoded in a base 64 String + */ + base64Enconde() { + return Buffer.from(JSON.stringify(this.stripAssociations())).toString( + "base64" + ); + } + + /** + * stripAssociations - Instant method for getting all attributes of <%- nameLc -%>. + * + * @return {object} The attributes of <%- nameLc -%> in object form + */ + stripAssociations() { + let attributes = Object.keys(<%- nameLc -%>.definition.attributes); + let data_values = _.pick(this, attributes); + return data_values; + } + + /** + * externalIdsArray - Get all attributes of <%- nameLc -%> that are marked as external IDs. + * + * @return {Array} An array of all attributes of <%- nameLc -%> that are marked as external IDs + */ + static externalIdsArray() { + let externalIds = []; + if (definition.externalIds) { + externalIds = definition.externalIds; + } + + return externalIds; + } + + /** + * externalIdsObject - Get all external IDs of <%- nameLc -%>. + * + * @return {object} An object that has the names of the external IDs as keys and their types as values + */ + static externalIdsObject() { + return {}; + } +}; diff --git a/views/create-trino-adapter.ejs b/views/create-trino-adapter.ejs new file mode 100644 index 00000000..fe10fe86 --- /dev/null +++ b/views/create-trino-adapter.ejs @@ -0,0 +1,392 @@ +'use strict'; + +const _ = require("lodash"); +const validatorUtil = require("../../utils/validatorUtil"); +const helper = require("../../utils/helper"); +const errorHelper = require("../../utils/errors"); +const fs = require("fs"); +const prestoHelper = require("../../utils/presto_helper"); +const path = require("path"); +const uuidv4 = require("uuidv4").uuid; +const os = require("os"); + +const iriRegex = new RegExp('<%- regex -%>'); + +// An exact copy of the the model definition that comes from the .json file +const definition = <%- definition -%>; +/** + * module - Creates a class to administer model + */ +module.exports = class <%- adapterName -%> { + constructor(input) { + for (let key of Object.keys(input)) { + this[key] = input[key]; + } + } + + get storageHandler() { + return <%- adapterName-%>.storageHandler + } + + /** + * adapterName - Getter for the name attribute + * + * This attribute is needed by the models' index + * @return {string} The adapterName of the model + */ + static get adapterName(){ + return "<%- adapterName -%>"; + } + + static get adapterType(){ + return '<%- storageType -%>'; + } + + static recognizeId(iri){ + return iriRegex.test(iri); + } + + /** + * Cast JSON string to array for the validation. + * @param {object} record Record with JSON string if necessary. + * @return {object} Parsed data record. + */ + static postReadCast(record) { + if (!record) { + return []; + } + const column_index = {}; + record[0].map((obj, index) => { + column_index[obj.name] = index; + }); + + let result = []; + for (const item of record[1]) { + let record = {}; + for (const attr in definition.attributes) { + const type = definition.attributes[attr].replace(/\s+/g, ""); + if ( + type[0] === "[" && + item[column_index[attr]] !== undefined && + item[column_index[attr]] !== null + ) { + record[attr] = JSON.parse(item[column_index[attr]]); + } else if (type === "DateTime") { + record[attr] = new Date(item[column_index[attr]]).toISOString(); + } else { + record[attr] = item[column_index[attr]]; + } + } + result.push(record); + } + return result; + } + + /** + * readById - The model implementation for reading a single record given by its ID + * + * This method is the implementation for reading a single record for the trino storage type, based on SQL. + * @param {string} id - The ID of the requested record + * @return {object} The requested record as an object with the type <%- nameLc -%>, or an error object if the validation after reading fails + * @throws {Error} If the requested record does not exist + */ + static async readById(id){ + const query = `SELECT * FROM <%- namePl -%> WHERE ${this.idAttribute()} = '${id}'`; + let item = null; + try { + const client = await this.storageHandler; + item = await prestoHelper.queryData(query, client); + + if (!item) { + throw new Error(`Record with ID = "${id}" does not exist`); + } + } catch (e) { + throw new Error(e); + } + item = <%- adapterName -%>.postReadCast(item)[0]; + return validatorUtil.validateData("validateAfterRead", this, item); + } + + /** + * countRecords - The model implementation for counting the number of records, possibly restricted by a search term + * + * This method is the implementation for counting the number of records that fulfill a given condition, or for all records in the table, + * for the trino storage type, based on SQL. + * @param {object} search - The search term that restricts the set of records to be counted - if undefined, all records in the table + * @param {BenignErrorReporter} benignErrorReporter can be used to generate the standard + * @return {number} The number of records that fulfill the condition, or of all records in the table + */ + static async countRecords(search, benignErrorReporter){ + const whereOptions = prestoHelper.searchConditionsToTrino( + search, + definition + ); + const query = `SELECT COUNT(*) AS num FROM <%- namePl -%> ${whereOptions}`; + let num = null; + try { + const client = await this.storageHandler; + const result = await prestoHelper.queryData(query, client); + num = result[1][0][0]; + } catch (e) { + throw new Error(e); + } + return num; + } + + /** + * readAllCursor - The model implementation for searching for records in Trino. This method uses cursor based pagination. + * + * @param {object} search - The search condition for which records shall be fetched + * @param {object} pagination - The parameters for pagination, which can be used to get a subset of the requested record set. + * @param {BenignErrorReporter} benignErrorReporter can be used to generate the standard + * @return {object} The set of records, possibly constrained by pagination, with full cursor information for all records + */ + static async readAllCursor(search, order, pagination, benignErrorReporter){ + //use default BenignErrorReporter if no BenignErrorReporter defined + benignErrorReporter = errorHelper.getDefaultBenignErrorReporterIfUndef( + benignErrorReporter + ); + let isForwardPagination = helper.isForwardPagination(pagination); + // build the whereOptions. + let filter = prestoHelper.searchConditionsToTrino(search, definition); + let newOrder = isForwardPagination + ? order + : helper.reverseOrderConditions(order); + // depending on the direction build the order object + let sort = prestoHelper.orderConditionsToTrino( + newOrder, + this.idAttribute(), + isForwardPagination + ); + let orderFields = newOrder ? newOrder.map((x) => x.field) : []; + // extend the filter for the given order and cursor + filter = prestoHelper.cursorPaginationArgumentsToTrino( + pagination, + sort, + filter, + orderFields, + this.idAttribute(), + definition.attributes + ); + + // add +1 to the LIMIT to get information about following pages. + let limit = helper.isNotUndefinedAndNotNull(pagination.first) + ? pagination.first + 1 + : helper.isNotUndefinedAndNotNull(pagination.last) + ? pagination.last + 1 + : undefined; + + let query = `SELECT * FROM <%- namePl -%> + ${filter} + ${sort} + LIMIT ${limit}`; + let result = null; + + const client = await this.storageHandler; + result = await prestoHelper.queryData(query, client); + + result = <%- adapterName -%>.postReadCast(result); + // validationCheck after read + result = await validatorUtil.bulkValidateData( + "validateAfterRead", + this, + result, + benignErrorReporter + ); + // get the first record (if exists) in the opposite direction to determine pageInfo. + // if no cursor was given there is no need for an extra query as the results will start at the first (or last) page. + let oppResult = []; + if (pagination && (pagination.after || pagination.before)) { + // reverse the pagination Arguement. after -> before; set first/last to 0, so LIMIT 1 is executed in the reverse Search + let oppPagination = helper.reversePaginationArgument({ + ...pagination, + includeCursor: false, + }); + let oppForwardPagination = helper.isForwardPagination(oppPagination); + // build the filter object. + let oppFilter = prestoHelper.searchConditionsToTrino(search, definition); + + let oppOrder = oppForwardPagination + ? order + : helper.reverseOrderConditions(order); + // depending on the direction build the order object + let oppSort = prestoHelper.orderConditionsToTrino( + oppOrder, + this.idAttribute(), + oppForwardPagination + ); + let oppOrderFields = oppOrder ? oppOrder.map((x) => x.field) : []; + // extend the filter for the given order and cursor + oppFilter = prestoHelper.cursorPaginationArgumentsToTrino( + oppPagination, + oppSort, + oppFilter, + oppOrderFields, + this.idAttribute(), + definition.attributes + ); + // add +1 to the LIMIT to get information about following pages. + let oppLimit = helper.isNotUndefinedAndNotNull(oppPagination.first) + ? oppPagination.first + 1 + : helper.isNotUndefinedAndNotNull(oppPagination.last) + ? oppPagination.last + 1 + : undefined; + query = `SELECT * FROM <%- namePl -%> + ${oppFilter} + ${oppSort} + LIMIT ${oppLimit}`; + oppResult = await prestoHelper.queryData(query, client); + oppResult = <%- adapterName -%>.postReadCast(oppResult); + } + + // build the graphql Connection Object + result = result.map((res) => { + return new <%- adapterName -%>(res); + }); + let edges = result.map((res) => { + return { + node: res, + cursor: res.base64Enconde(), + }; + }); + const pageInfo = helper.buildPageInfo(edges, oppResult, pagination); + return { edges, pageInfo, <%- namePl -%>: edges.map((edge) => edge.node) }; + } + + /** + * addOne - Not implemented for Trino. + */ + static async addOne(input){ + throw new Error('Not supported by Trino'); + } + + /** + * deleteOne - Delete the whole file. + */ + static async deleteOne(id){ + throw new Error('Not supported by Trino'); + } + + /** + * updateOne - Not implemented for Trino. + */ + static async updateOne(input){ + throw new Error('Not supported by Trino'); + } + + /** + * bulkAddCsv - Add records from csv file + * + * @param {object} context - contextual information, e.g. csv file, record delimiter and column names. + */ + static async bulkAddCsv(context) { + throw new Error("Not supported by Trino"); + } + + /** + * csvTableTemplate - Allows the user to download a template in CSV format with the + * properties and types of this model. + * + * @param {BenignErrorReporter} benignErrorReporter can be used to generate the standard + * GraphQL output {error: ..., data: ...}. If the function reportError of the benignErrorReporter + * is invoked, the server will include any so reported errors in the final response, i.e. the + * GraphQL response will have a non empty errors property. + */ + static async csvTableTemplate(benignErrorReporter){ + return helper.csvTableTemplate(definition); + } + + + /** + * idAttribute - Check whether an attribute "internalId" is given in the JSON model. If not the standard "id" is used instead. + * + * @return {type} Name of the attribute that functions as an internalId + */ + + static idAttribute() { + return <%- adapterName -%>.definition.id.name; + } + + /** + * idAttributeType - Return the Type of the internalId. + * + * @return {type} Type given in the JSON model + */ + + static idAttributeType() { + return <%- adapterName -%>.definition.id.type; + } + + /** + * getIdValue - Get the value of the idAttribute ("id", or "internalId") for an instance of <%- adapterName -%>. + * + * @return {type} id value + */ + + getIdValue() { + return this[<%- adapterName -%>.idAttribute()] + } + + /** + * definition - Getter for the attribute 'definition' + * @return {string} the definition string + */ + static get definition(){ + return definition; + } + + /** + * base64Decode - Decode a base 64 String to UTF-8. + * @param {string} cursor - The cursor to be decoded into the record, given in base 64 + * @return {string} The stringified object in UTF-8 format + */ + static base64Decode(cursor){ + return Buffer.from(cursor, 'base64').toString('utf-8'); + } + + /** + * base64Enconde - Encode <%- adapterName -%> to a base 64 String + * + * @return {string} The <%- adapterName -%> object, encoded in a base 64 String + */ + base64Enconde(){ + return Buffer.from(JSON.stringify(this.stripAssociations())).toString('base64'); + } + + /** + * stripAssociations - Instant method for getting all attributes of <%- adapterName -%>. + * + * @return {object} The attributes of <%- adapterName -%> in object form + */ + stripAssociations(){ + let attributes = Object.keys(<%- adapterName -%>.definition.attributes); + <%if( defaultId ){-%>attributes.push('<%- idAttribute -%>'); <%}-%> + let data_values = _.pick(this, attributes); + return data_values; + } + + /** + * externalIdsArray - Get all attributes of <%- adapterName -%> that are marked as external IDs. + * + * @return {Array} An array of all attributes of <%- adapterName -%> that are marked as external IDs + */ + static externalIdsArray(){ + let externalIds = []; + if(definition.externalIds){ + externalIds = definition.externalIds; + } + + return externalIds; + } + + /** + * externalIdsObject - Get all external IDs of <%- adapterName -%>. + * + * @return {object} An object that has the names of the external IDs as keys and their types as values + */ + static externalIdsObject(){ + return { + <%for(let i=0; i < externalIds.length; i++){-%> <%=externalIds[i]-%>: '<%=attributes[ externalIds[i] ]-%>' <%if(i !== (externalIds.length -1) ){ -%>,<%}-%><%}-%> + }; + } +} \ No newline at end of file