Skip to content

Commit d30ace7

Browse files
authored
fix: cassandra field resolver (#188)
* fix: index creation camelCase not working * fix: add missing to_many cassandra adapter fieldMutation * fix: workaround for cassandra storageType association search
1 parent c52fdb2 commit d30ace7

7 files changed

+260
-32
lines changed

Diff for: test/mocha_integration_cassandra.test.js

+110-6
Original file line numberDiff line numberDiff line change
@@ -668,7 +668,23 @@ describe("Cassandra Local", function () {
668668
});
669669
});
670670

671-
it("17. Delete the associations", function () {
671+
it("17. Read one instant and search on associated incident primary key", function () {
672+
let res = itHelpers.request_graph_ql_post(
673+
`{
674+
readOneInstant(instant_id: "instant_1") {
675+
instant_id
676+
incident(search: {field: incident_id value:"incident_7" operator:eq}) {
677+
incident_id
678+
}
679+
}
680+
}`
681+
);
682+
let resBody = JSON.parse(res.body.toString("utf8"));
683+
expect(res.statusCode).to.equal(200);
684+
expect(resBody).to.deep.equal({"data":{"readOneInstant":{"instant_id":"instant_1","incident":{"incident_id":"incident_7"}}}})
685+
});
686+
687+
it("18. Delete the associations", function () {
672688
let res = itHelpers.request_graph_ql_post(
673689
`{instantsConnection(pagination:{first:20}, search:{field: incident_assoc_id, operator: eq, value:"incident_7"}) {edges {node{ instant_id}}}}`
674690
);
@@ -694,7 +710,7 @@ describe("Cassandra Local", function () {
694710
}
695711
});
696712

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

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

1276-
it("03. Update record and remove one association - cassandra", function () {
1292+
it("03. Query rivers and filter associated cities on city_id existent in the fkarray: simple search - cassandra", function () {
1293+
// Operator: eq
1294+
let res = itHelpers.request_graph_ql_post(
1295+
`{
1296+
riversConnection(pagination:{first:2}) {
1297+
rivers{
1298+
river_id
1299+
citiesConnection(
1300+
pagination: {first: 2}
1301+
search: {field: city_id, value: "cassandra_city_1", operator: eq}
1302+
){
1303+
edges {
1304+
node {
1305+
city_id
1306+
}
1307+
}
1308+
}
1309+
}
1310+
}
1311+
}
1312+
`
1313+
);
1314+
expect(res.statusCode).to.equal(200);
1315+
let resBody = JSON.parse(res.body.toString("utf8"));
1316+
expect(resBody.data).to.deep.equal({"riversConnection":{"rivers":[{"river_id":"fkA_river_1","citiesConnection":{"edges":[{"node":{"city_id":"cassandra_city_1"}}]}}]}});
1317+
1318+
1319+
});
1320+
1321+
it("04. Query rivers and filter associated cities on city_id existent in the fkarray: complex search - cassandra", function(){
1322+
//Operator: in
1323+
let res = itHelpers.request_graph_ql_post(
1324+
`{
1325+
riversConnection(pagination:{first:2}) {
1326+
rivers{
1327+
river_id
1328+
citiesConnection(
1329+
pagination: {first: 2}
1330+
search: { operator: and, search:[
1331+
{field: city_id, value: "cassandra_city_2", operator: eq},
1332+
{field: name, value: "duesseldorf", operator: eq}
1333+
]}
1334+
){
1335+
edges {
1336+
node {
1337+
city_id
1338+
}
1339+
}
1340+
}
1341+
}
1342+
}
1343+
}
1344+
`
1345+
);
1346+
expect(res.statusCode).to.equal(200);
1347+
let resBody = JSON.parse(res.body.toString("utf8"));
1348+
1349+
expect(resBody.data).to.deep.equal({"riversConnection":{"rivers":[{"river_id":"fkA_river_1","citiesConnection":{"edges":[{"node":{"city_id":"cassandra_city_2"}}]}}]}});
1350+
});
1351+
1352+
it("05. Query rivers and filter associated cities on city_id existent in the fkarray: IN search - cassandra", function(){
1353+
//Operator: in
1354+
let res = itHelpers.request_graph_ql_post(
1355+
`{
1356+
riversConnection(pagination:{first:2}) {
1357+
rivers{
1358+
river_id
1359+
citiesConnection(
1360+
pagination: {first: 2}
1361+
search: {field: city_id, value: "cassandra_city_2,cassandra_city_1,city_non_existent", operator: in, valueType:Array}
1362+
){
1363+
edges {
1364+
node {
1365+
city_id
1366+
}
1367+
}
1368+
}
1369+
}
1370+
}
1371+
}
1372+
`
1373+
);
1374+
expect(res.statusCode).to.equal(200);
1375+
let resBody = JSON.parse(res.body.toString("utf8"));
1376+
1377+
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"}}]}}]}})
1378+
});
1379+
1380+
it("06. Update record and remove one association - cassandra", function () {
12771381
let res = itHelpers.request_graph_ql_post(
12781382
'mutation{updateCity(city_id:"cassandra_city_1" removeRivers:["fkA_river_1"]){city_id river_ids}}'
12791383
);
@@ -1295,7 +1399,7 @@ describe("cassandra Foreign-key arrays", function () {
12951399
});
12961400
});
12971401

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

1333-
it("05. Update record and remove all association - cassandra", function () {
1437+
it("08. Update record and remove all association - cassandra", function () {
13341438
let res = itHelpers.request_graph_ql_post(
13351439
'mutation{updateRiver(river_id:"fkA_river_1" removeCities:["cassandra_city_1","cassandra_city_2"]){river_id city_ids}}'
13361440
);

Diff for: test/mocha_unit.test.js

+6
Original file line numberDiff line numberDiff line change
@@ -2722,6 +2722,12 @@ describe("Cassandra storagetype", function () {
27222722
testCompare(generated_resolver, data_test.cassandra_resolver_Count);
27232723
});
27242724

2725+
it("targetStorageType cassandra fieldResolver Workaround - citiesConnection", async function () {
2726+
let opts = funks.getOptions(models_cassandra.river);
2727+
let generated_resolver = await funks.generateJs("create-resolvers", opts);
2728+
testCompare(generated_resolver, data_test.river_many_to_many_cassandra_fieldResolver_Connection);
2729+
});
2730+
27252731
it("cassandra models - constructor", async function () {
27262732
let opts = funks.getOptions(models_cassandra.city);
27272733
let generated_model = await funks.generateJs(

Diff for: test/unit_test_misc/data_models_cassandra.js

+33
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,39 @@ module.exports.city = {
2727
"internalId": "city_id"
2828
}
2929

30+
module.exports.river = {
31+
"model": "river",
32+
"storageType": "SQL",
33+
"attributes": {
34+
"name": "String",
35+
"length": "Int",
36+
"river_id": "String",
37+
38+
"city_ids": "[String]"
39+
},
40+
"associations": {
41+
"countries": {
42+
"type": "many_to_many",
43+
"implementation": "sql_cross_table",
44+
"target": "country",
45+
"sourceKey": "river_id",
46+
"targetKey": "country_id",
47+
"keysIn": "country_to_river",
48+
"targetStorageType": "sql"
49+
},
50+
"cities": {
51+
"type": "many_to_many",
52+
"implementation": "foreignkeys",
53+
"target": "city",
54+
"targetStorageType": "cassandra",
55+
"sourceKey": "city_ids",
56+
"targetKey": "river_ids",
57+
"keysIn": "river"
58+
}
59+
},
60+
"internalId": "river_id"
61+
}
62+
3063
module.exports.incident = {
3164
"model": "Incident",
3265
"storageType": "cassandra",

Diff for: test/unit_test_misc/test-describe/cassandra-storagetype.js

+35
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,41 @@ countCities: async function({
161161
}
162162
`;
163163

164+
module.exports.river_many_to_many_cassandra_fieldResolver_Connection = `
165+
river.prototype.citiesConnection = function({
166+
search,
167+
order,
168+
pagination
169+
}, context) {
170+
//return an empty response if the foreignKey Array is empty, no need to query the database
171+
if (!Array.isArray(this.city_ids) || this.city_ids.length === 0) {
172+
return {
173+
edges: [],
174+
cities: [],
175+
pageInfo: {
176+
startCursor: null,
177+
endCursor: null,
178+
hasPreviousPage: false,
179+
hasNextPage: false
180+
}
181+
};
182+
}
183+
const hasIdSearch = helper.parseFieldResolverSearchArgForCassandra(search, this.city_ids, models.city.idAttribute());
184+
let nsearch = hasIdSearch ? search : helper.addSearchField({
185+
"search": search,
186+
"field": models.city.idAttribute(),
187+
"value": this.city_ids.join(','),
188+
"valueType": "Array",
189+
"operator": "in"
190+
});
191+
return resolvers.citiesConnection({
192+
search: nsearch,
193+
order: order,
194+
pagination: pagination
195+
}, context);
196+
}
197+
`;
198+
164199
module.exports.cassandra_model_constructor = `
165200
constructor(input) {
166201
for (let key of Object.keys(input)) {

Diff for: views/create-migrations-cassandra.ejs

+2-2
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ module.exports = {
2929
await cassandraClient.execute(createString);
3030

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

3434
await Promise.allSettled(indexCreationPromises);
3535

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

5252
};

Diff for: views/create-resolvers.ejs

+49-22
Original file line numberDiff line numberDiff line change
@@ -154,8 +154,17 @@ const associationArgsDef = {
154154
if (search === undefined || search === null) {
155155
return resolvers.readOne<%=associations_one[i].target_cp%>({[models.<%=associations_one[i].target_lc-%>.idAttribute()]: this.<%=associations_one[i].targetKey%>},context)
156156
} else {
157+
<%# WORKAROUND FOR Cassandra targetStorageType:
158+
In case of an association to a model within cassandra we need to do intersections
159+
of the search parameters with the foreignkey array if the search is on the idAttribute
160+
and with operator "eq" / "in", since cassandra doesn't support multiple restricions
161+
with an "eq" / "in" on the primary key field. %>
162+
<%if(associations_one[i].targetStorageType === 'cassandra'){%>
163+
//WORKAROUND for cassandra targetStorageType. Mainpulate search to intersect Equal searches on the primaryKey
164+
const hasIdSearch = helper.parseFieldResolverSearchArgForCassandra(search, this.<%=associations_one[i].targetKey%>, models.<%=associations_one[i].target_lc-%>.idAttribute());
165+
<%}-%>
157166
//build new search filter
158-
let nsearch = helper.addSearchField({
167+
let nsearch = <%if(associations_one[i].targetStorageType === 'cassandra'){%>hasIdSearch ? search : <%}-%>helper.addSearchField({
159168
"search": search,
160169
"field": models.<%=associations_one[i].target_lc-%>.idAttribute(),
161170
"value": this.<%= associations_one[i].targetKey -%>,
@@ -260,13 +269,23 @@ const associationArgsDef = {
260269
if (!Array.isArray(this.<%=associations_temp[i].sourceKey%>) || this.<%=associations_temp[i].sourceKey%>.length === 0 ) {
261270
return 0;
262271
}
263-
let nsearch = helper.addSearchField({
272+
<%# WORKAROUND FOR Cassandra targetStorageType:
273+
In case of an association to a model within cassandra we need to do intersections
274+
of the search parameters with the foreignkey array if the search is on the idAttribute
275+
and with operator "eq" / "in", since cassandra doesn't support multiple restricions
276+
with an "eq" / "in" on the primary key field. %>
277+
<%if(associations_temp[i].targetStorageType === 'cassandra'){%>
278+
//WORKAROUND for cassandra targetStorageType. Mainpulate search to intersect Equal searches on the primaryKey
279+
const hasIdSearch = helper.parseFieldResolverSearchArgForCassandra(search, this.<%=associations_temp[i].sourceKey%>, models.<%=associations_temp[i].target_lc-%>.idAttribute());
280+
<%}-%>
281+
let nsearch = <%if(associations_temp[i].targetStorageType === 'cassandra'){%>hasIdSearch ? search : <%}-%>helper.addSearchField({
264282
"search": search,
265283
"field": models.<%=associations_temp[i].target_lc-%>.idAttribute(),
266284
"value": this.<%=associations_temp[i].sourceKey%>.join(','),
267285
"valueType": "Array",
268286
"operator": "in"
269287
});
288+
270289
<%}else{-%>
271290
//build new search filter
272291
let nsearch = helper.addSearchField({
@@ -293,26 +312,34 @@ const associationArgsDef = {
293312
<%- nameLc -%>.prototype.<%=associations_temp[i].name%>Connection = function({search,order,pagination}, context){
294313
295314
<%if(associations_temp[i].assocThroughArray){%>
296-
//return an empty response if the foreignKey Array is empty, no need to query the database
297-
if (!Array.isArray(this.<%=associations_temp[i].sourceKey%>) || this.<%=associations_temp[i].sourceKey%>.length === 0 ) {
298-
return {
299-
edges: [],
300-
<%=associations_temp[i].target_lc_pl%>: [],
301-
pageInfo: {
302-
startCursor: null,
303-
endCursor: null,
304-
hasPreviousPage: false,
305-
hasNextPage: false
306-
}
307-
};
308-
}
309-
let nsearch = helper.addSearchField({
310-
"search": search,
311-
"field": models.<%=associations_temp[i].target_lc-%>.idAttribute(),
312-
"value": this.<%=associations_temp[i].sourceKey%>.join(','),
313-
"valueType": "Array",
314-
"operator": "in"
315-
});
315+
//return an empty response if the foreignKey Array is empty, no need to query the database
316+
if (!Array.isArray(this.<%=associations_temp[i].sourceKey%>) || this.<%=associations_temp[i].sourceKey%>.length === 0 ) {
317+
return {
318+
edges: [],
319+
<%=associations_temp[i].target_lc_pl%>: [],
320+
pageInfo: {
321+
startCursor: null,
322+
endCursor: null,
323+
hasPreviousPage: false,
324+
hasNextPage: false
325+
}
326+
};
327+
}
328+
<%# WORKAROUND FOR Cassandra targetStorageType:
329+
In case of an association to a model within cassandra we need to do intersections
330+
of the search parameters with the foreignkey array if the search is on the idAttribute
331+
and with operator "eq" / "in", since cassandra doesn't support multiple restricions
332+
with an "eq" / "in" on the primary key field. %>
333+
<%if(associations_temp[i].targetStorageType === 'cassandra'){%>
334+
const hasIdSearch = helper.parseFieldResolverSearchArgForCassandra(search, this.<%=associations_temp[i].sourceKey%>, models.<%=associations_temp[i].target_lc-%>.idAttribute());
335+
<%}-%>
336+
let nsearch = <%if(associations_temp[i].targetStorageType === 'cassandra'){%>hasIdSearch ? search : <%}-%>helper.addSearchField({
337+
"search": search,
338+
"field": models.<%=associations_temp[i].target_lc-%>.idAttribute(),
339+
"value": this.<%=associations_temp[i].sourceKey%>.join(','),
340+
"valueType": "Array",
341+
"operator": "in"
342+
});
316343
<%}else{-%>
317344
318345
//build new search filter

0 commit comments

Comments
 (0)