Skip to content

Commit

Permalink
fix: cassandra field resolver (#188)
Browse files Browse the repository at this point in the history
* fix: index creation camelCase not working

* fix: add missing to_many cassandra adapter fieldMutation

* fix: workaround for cassandra storageType association search
  • Loading branch information
coeit authored Jun 22, 2021
1 parent c52fdb2 commit d30ace7
Show file tree
Hide file tree
Showing 7 changed files with 260 additions and 32 deletions.
116 changes: 110 additions & 6 deletions test/mocha_integration_cassandra.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -668,7 +668,23 @@ describe("Cassandra Local", function () {
});
});

it("17. Delete the associations", function () {
it("17. Read one instant and search on associated incident primary key", function () {
let res = itHelpers.request_graph_ql_post(
`{
readOneInstant(instant_id: "instant_1") {
instant_id
incident(search: {field: incident_id value:"incident_7" operator:eq}) {
incident_id
}
}
}`
);
let resBody = JSON.parse(res.body.toString("utf8"));
expect(res.statusCode).to.equal(200);
expect(resBody).to.deep.equal({"data":{"readOneInstant":{"instant_id":"instant_1","incident":{"incident_id":"incident_7"}}}})
});

it("18. Delete the associations", function () {
let res = itHelpers.request_graph_ql_post(
`{instantsConnection(pagination:{first:20}, search:{field: incident_assoc_id, operator: eq, value:"incident_7"}) {edges {node{ instant_id}}}}`
);
Expand All @@ -694,7 +710,7 @@ describe("Cassandra Local", function () {
}
});

it("18. Get the table template", function () {
it("19. Get the table template", function () {
let res = itHelpers.request_graph_ql_post(`{csvTableTemplateIncident}`);
let resBody = JSON.parse(res.body.toString("utf8"));
expect(res.statusCode).to.equal(200);
Expand All @@ -708,7 +724,7 @@ describe("Cassandra Local", function () {
});
});

it("19. Associate cassandra to sql model", function () {
it("20. Associate cassandra to sql model", function () {
// create sql-capital
let res = itHelpers.request_graph_ql_post(
`mutation { addCapital(capital_id: "cass_assoc_capital_1", name: "London") {capital_id}}`
Expand Down Expand Up @@ -1273,7 +1289,95 @@ describe("cassandra Foreign-key arrays", function () {
});
});

it("03. Update record and remove one association - cassandra", function () {
it("03. Query rivers and filter associated cities on city_id existent in the fkarray: simple search - cassandra", function () {
// Operator: eq
let res = itHelpers.request_graph_ql_post(
`{
riversConnection(pagination:{first:2}) {
rivers{
river_id
citiesConnection(
pagination: {first: 2}
search: {field: city_id, value: "cassandra_city_1", operator: eq}
){
edges {
node {
city_id
}
}
}
}
}
}
`
);
expect(res.statusCode).to.equal(200);
let resBody = JSON.parse(res.body.toString("utf8"));
expect(resBody.data).to.deep.equal({"riversConnection":{"rivers":[{"river_id":"fkA_river_1","citiesConnection":{"edges":[{"node":{"city_id":"cassandra_city_1"}}]}}]}});


});

it("04. Query rivers and filter associated cities on city_id existent in the fkarray: complex search - cassandra", function(){
//Operator: in
let res = itHelpers.request_graph_ql_post(
`{
riversConnection(pagination:{first:2}) {
rivers{
river_id
citiesConnection(
pagination: {first: 2}
search: { operator: and, search:[
{field: city_id, value: "cassandra_city_2", operator: eq},
{field: name, value: "duesseldorf", operator: eq}
]}
){
edges {
node {
city_id
}
}
}
}
}
}
`
);
expect(res.statusCode).to.equal(200);
let resBody = JSON.parse(res.body.toString("utf8"));

expect(resBody.data).to.deep.equal({"riversConnection":{"rivers":[{"river_id":"fkA_river_1","citiesConnection":{"edges":[{"node":{"city_id":"cassandra_city_2"}}]}}]}});
});

it("05. Query rivers and filter associated cities on city_id existent in the fkarray: IN search - cassandra", function(){
//Operator: in
let res = itHelpers.request_graph_ql_post(
`{
riversConnection(pagination:{first:2}) {
rivers{
river_id
citiesConnection(
pagination: {first: 2}
search: {field: city_id, value: "cassandra_city_2,cassandra_city_1,city_non_existent", operator: in, valueType:Array}
){
edges {
node {
city_id
}
}
}
}
}
}
`
);
expect(res.statusCode).to.equal(200);
let resBody = JSON.parse(res.body.toString("utf8"));

expect(resBody.data).to.deep.equal({"riversConnection":{"rivers":[{"river_id":"fkA_river_1","citiesConnection":{"edges":[{"node":{"city_id":"cassandra_city_1"}},{"node":{"city_id":"cassandra_city_2"}}]}}]}})
});

it("06. Update record and remove one association - cassandra", function () {
let res = itHelpers.request_graph_ql_post(
'mutation{updateCity(city_id:"cassandra_city_1" removeRivers:["fkA_river_1"]){city_id river_ids}}'
);
Expand All @@ -1295,7 +1399,7 @@ describe("cassandra Foreign-key arrays", function () {
});
});

it("04. Update record and add one association - cassandra", function () {
it("07. Update record and add one association - cassandra", function () {
let res = itHelpers.request_graph_ql_post(
'mutation{updateRiver(river_id:"fkA_river_1" addCities:["cassandra_city_1"]){river_id city_ids}}'
);
Expand Down Expand Up @@ -1330,7 +1434,7 @@ describe("cassandra Foreign-key arrays", function () {
});
});

it("05. Update record and remove all association - cassandra", function () {
it("08. Update record and remove all association - cassandra", function () {
let res = itHelpers.request_graph_ql_post(
'mutation{updateRiver(river_id:"fkA_river_1" removeCities:["cassandra_city_1","cassandra_city_2"]){river_id city_ids}}'
);
Expand Down
6 changes: 6 additions & 0 deletions test/mocha_unit.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2722,6 +2722,12 @@ describe("Cassandra storagetype", function () {
testCompare(generated_resolver, data_test.cassandra_resolver_Count);
});

it("targetStorageType cassandra fieldResolver Workaround - citiesConnection", async function () {
let opts = funks.getOptions(models_cassandra.river);
let generated_resolver = await funks.generateJs("create-resolvers", opts);
testCompare(generated_resolver, data_test.river_many_to_many_cassandra_fieldResolver_Connection);
});

it("cassandra models - constructor", async function () {
let opts = funks.getOptions(models_cassandra.city);
let generated_model = await funks.generateJs(
Expand Down
33 changes: 33 additions & 0 deletions test/unit_test_misc/data_models_cassandra.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,39 @@ module.exports.city = {
"internalId": "city_id"
}

module.exports.river = {
"model": "river",
"storageType": "SQL",
"attributes": {
"name": "String",
"length": "Int",
"river_id": "String",

"city_ids": "[String]"
},
"associations": {
"countries": {
"type": "many_to_many",
"implementation": "sql_cross_table",
"target": "country",
"sourceKey": "river_id",
"targetKey": "country_id",
"keysIn": "country_to_river",
"targetStorageType": "sql"
},
"cities": {
"type": "many_to_many",
"implementation": "foreignkeys",
"target": "city",
"targetStorageType": "cassandra",
"sourceKey": "city_ids",
"targetKey": "river_ids",
"keysIn": "river"
}
},
"internalId": "river_id"
}

module.exports.incident = {
"model": "Incident",
"storageType": "cassandra",
Expand Down
35 changes: 35 additions & 0 deletions test/unit_test_misc/test-describe/cassandra-storagetype.js
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,41 @@ countCities: async function({
}
`;

module.exports.river_many_to_many_cassandra_fieldResolver_Connection = `
river.prototype.citiesConnection = function({
search,
order,
pagination
}, context) {
//return an empty response if the foreignKey Array is empty, no need to query the database
if (!Array.isArray(this.city_ids) || this.city_ids.length === 0) {
return {
edges: [],
cities: [],
pageInfo: {
startCursor: null,
endCursor: null,
hasPreviousPage: false,
hasNextPage: false
}
};
}
const hasIdSearch = helper.parseFieldResolverSearchArgForCassandra(search, this.city_ids, models.city.idAttribute());
let nsearch = hasIdSearch ? search : helper.addSearchField({
"search": search,
"field": models.city.idAttribute(),
"value": this.city_ids.join(','),
"valueType": "Array",
"operator": "in"
});
return resolvers.citiesConnection({
search: nsearch,
order: order,
pagination: pagination
}, context);
}
`;

module.exports.cassandra_model_constructor = `
constructor(input) {
for (let key of Object.keys(input)) {
Expand Down
4 changes: 2 additions & 2 deletions views/create-migrations-cassandra.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ module.exports = {
await cassandraClient.execute(createString);

let indexCreationPromises = indexCreationStrings.map(async i =>
await cassandraClient.execute('CREATE INDEX IF NOT EXISTS <%-namePl-%>_' + i + '_index ON <%-namePl-%> (' + i + ');'));
await cassandraClient.execute('CREATE INDEX IF NOT EXISTS <%-namePl-%>_' + i + '_index ON "<%-namePl-%>" ("' + i + '");'));

await Promise.allSettled(indexCreationPromises);

Expand All @@ -46,7 +46,7 @@ module.exports = {
// get the default cassandra client
const connectionInstances = await getConnectionInstances();
const cassandraClient = connectionInstances.get("default-cassandra").connection;
await cassandraClient.execute('DROP TABLE IF EXISTS <%-namePl-%>');
await cassandraClient.execute('DROP TABLE IF EXISTS "<%-namePl-%>"');
}

};
71 changes: 49 additions & 22 deletions views/create-resolvers.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -154,8 +154,17 @@ const associationArgsDef = {
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 {
<%# 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].targetKey%>, models.<%=associations_one[i].target_lc-%>.idAttribute());
<%}-%>
//build new search filter
let nsearch = helper.addSearchField({
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].targetKey -%>,
Expand Down Expand Up @@ -260,13 +269,23 @@ const associationArgsDef = {
if (!Array.isArray(this.<%=associations_temp[i].sourceKey%>) || this.<%=associations_temp[i].sourceKey%>.length === 0 ) {
return 0;
}
let nsearch = helper.addSearchField({
<%# 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_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());
<%}-%>
let nsearch = <%if(associations_temp[i].targetStorageType === 'cassandra'){%>hasIdSearch ? search : <%}-%>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({
Expand All @@ -293,26 +312,34 @@ const associationArgsDef = {
<%- 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"
});
//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
}
};
}
<%# 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_temp[i].targetStorageType === 'cassandra'){%>
const hasIdSearch = helper.parseFieldResolverSearchArgForCassandra(search, this.<%=associations_temp[i].sourceKey%>, models.<%=associations_temp[i].target_lc-%>.idAttribute());
<%}-%>
let nsearch = <%if(associations_temp[i].targetStorageType === 'cassandra'){%>hasIdSearch ? search : <%}-%>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
Expand Down
Loading

0 comments on commit d30ace7

Please sign in to comment.