Implementing the new match query

This commit is contained in:
luca.frosini 2023-11-17 17:21:45 +01:00
parent 46cb4f0975
commit d2b797c19a
13 changed files with 551 additions and 12 deletions

View File

@ -103,6 +103,13 @@ public class JsonQuery {
return entryPoint.createQuery(new StringBuffer());
}
public StringBuffer createMatchQuery() throws SchemaException, InvalidQueryException, ResourceRegistryException {
entryPoint = getJsonQueryERElement(jsonQuery);
entryPoint.setEntryPoint(true);
return entryPoint.createMatchQuery(new StringBuffer());
}
public String query() throws InvalidQueryException, ResourceRegistryException {
ODatabaseDocument current = ContextUtility.getCurrentODatabaseDocumentFromThreadLocal();
oDatabaseDocument = null;

View File

@ -1,7 +1,9 @@
package org.gcube.informationsystem.resourceregistry.queries.json.base;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.gcube.com.fasterxml.jackson.databind.JsonNode;
@ -44,6 +46,51 @@ public abstract class JsonQueryERElement {
protected Direction direction;
protected boolean entryPoint;
/* Start of variables used to create in MATCH queries */
/**
* Instruct the JSON query analyzer if it is a projection
*/
protected boolean projection;
/**
* The caller of this instance analyzer.
* It is null if this instance is an entry point.
* (e.g. a JsonQueryResource instance for a JsonQueryConsistsOf instance)
*
*/
protected JsonQueryERElement caller;
/**
* The chain of callers of this instance analyzer.
* breadcrumb.get(breadcrumb.size-1) == caller
* breadcrumb is empty if this instance is an entry point
*/
protected List<JsonQueryERElement> breadcrumb;
/**
* The breadcrumb.size() provide the level of nesting.
* The position which element number at the same level.
* E.g. the index of a ConsistsOf when the caller is a Resource.
*
* It is used in conjunction with breadcrumb.size() to attach a number to the alias.
* This allows to generate a predictive not clashing alias instead of using random string/number.
* This is useful for testing purposes.
*/
protected int position;
/**
* Contains the alias if needed by the class
*/
protected String alias;
/**
* This field is used by entry point only.
* Any class at any level can retrieve the entry point using
* breadcrumb.get(0);
*/
protected List<String> fieldsToEmit;
/* Start of variables used to create in MATCH queries */
/**
* it indicates the number of properties in this.jsonNode
* This number is manipulated while analyzing the jsonNode
@ -62,6 +109,12 @@ public abstract class JsonQueryERElement {
this.entryPoint = false;
this.traverseBack = true;
this.projection = false;
this.caller = null;
this.breadcrumb = new ArrayList<>();
this.position = 0;
this.fieldNamesToRemove = new HashSet<>();
this.fieldNamesToRemove.add(Element.TYPE_PROPERTY);
this.fieldNamesToRemove.add(ModelElement.SUPERTYPES_PROPERTY);
@ -91,6 +144,76 @@ public abstract class JsonQueryERElement {
this.traverseBack = !entryPoint;
}
public boolean isProjection() {
return projection;
}
public void setProjection(boolean projection) {
this.projection = projection;
if(projection && !entryPoint) {
breadcrumb.get(0).setEntryPoint(projection);
}
}
public JsonQueryERElement getCaller() {
return caller;
}
public void setCaller(JsonQueryERElement caller) {
this.caller = caller;
}
public List<JsonQueryERElement> getBreadcrumb() {
return breadcrumb;
}
public void setBreadcrumb(List<JsonQueryERElement> breadcrumb) {
this.breadcrumb = breadcrumb;
}
public int getPosition() {
return position;
}
public void setPosition(int position) {
this.position = position;
}
public String getAlias() {
return alias;
}
public String getAlias(boolean generateifNull) {
if(alias==null && generateifNull) {
alias = generateAlias().toString();
}
return alias;
}
protected StringBuffer generateAlias() {
StringBuffer sb = new StringBuffer();
sb.append(type.toLowerCase());
// The entry point will have 00 as suffix
sb.append(breadcrumb.size());
sb.append(position);
return sb;
}
/**
* Add a field to emit only if this instance is the entry point
* @param fieldToEmit
*/
protected void addFieldToEmit(String fieldToEmit) {
if(entryPoint) {
fieldsToEmit.add(fieldToEmit);
logger.trace("The field to emit ({}) has been added to entry point, i.e. {} with alias {}", fieldToEmit, this.type, this.alias);
}else {
logger.trace("The field to emit ({}) will be added to entry point", fieldToEmit);
breadcrumb.get(0).addFieldToEmit(fieldToEmit);
}
}
public boolean isTraverseBack() {
return traverseBack;
}
@ -103,9 +226,7 @@ public abstract class JsonQueryERElement {
return createTraversalQuery(stringBuffer);
}
protected abstract StringBuffer createTraversalQuery(StringBuffer stringBuffer) throws SchemaNotFoundException, InvalidQueryException, SchemaException, ResourceRegistryException;
protected abstract StringBuffer createMatchQuery(StringBuffer stringBuffer) throws SchemaNotFoundException, InvalidQueryException, SchemaException, ResourceRegistryException;
public abstract StringBuffer createTraversalQuery(StringBuffer stringBuffer) throws SchemaNotFoundException, InvalidQueryException, SchemaException, ResourceRegistryException;
protected StringBuffer addConstraints(JsonNode jsonNode, LogicalOperator queryLogicalOperator, String fieldNamePrefix) throws InvalidQueryException {
StringBuffer stringBuffer = new StringBuffer();
@ -240,4 +361,82 @@ public abstract class JsonQueryERElement {
}
return stringBuffer;
}
protected List<JsonQueryERElement> getChildrenBreadcrumb() {
List<JsonQueryERElement> childrenBreadcrumb = new ArrayList<>(this.breadcrumb);
childrenBreadcrumb.add(this);
return childrenBreadcrumb;
}
protected StringBuffer wrapMatchQuery(StringBuffer buffer) throws InvalidQueryException {
if(entryPoint) {
alias = getAlias(true);
StringBuffer entryBuffer = new StringBuffer();
entryBuffer.append("MATCH\n");
entryBuffer.append("\t{class: "); // The { has to be closed
entryBuffer.append(type);
entryBuffer.append(", as: ");
entryBuffer.append(alias);
entryBuffer.append(", where: ");
entryBuffer.append("(($currentMatch['@class'] INSTANCEOF '"); // The first and the second ( has to be closed
entryBuffer.append(type);
entryBuffer.append("')"); // close the second (
if(size>1) {
entryBuffer.append(" AND (");
entryBuffer.append(addConstraints(jsonNode, null, null));
entryBuffer.append(")");
}
entryBuffer.append(")"); // close the first (
entryBuffer.append("}");
entryBuffer.append(buffer);
entryBuffer.append("\nRETURN\n");
if(!projection) {
entryBuffer.append("\tDISTINCT(");
entryBuffer.append(alias);
entryBuffer.append(") as ret");
StringBuffer wrap = new StringBuffer();
wrap.append("SELECT EXPAND(ret) FROM (\n");
wrap.append(entryBuffer);
wrap.append("\n)");
entryBuffer = wrap;
}else {
int size = fieldsToEmit.size();
for(int i=0; i<size; i++) {
entryBuffer.append("\t");
entryBuffer.append(fieldsToEmit.get(i));
if(i<(size-1)) {
entryBuffer.append(",\n");
}
}
}
return entryBuffer;
}else {
return buffer;
}
}
protected abstract StringBuffer getSpecificMatchQuery(List<JsonQueryERElement> childrenBreadcrumb) throws SchemaException, ResourceRegistryException;
public StringBuffer createMatchQuery(StringBuffer stringBuffer) throws SchemaException, ResourceRegistryException {
List<JsonQueryERElement> childrenBreadcrumb = getChildrenBreadcrumb();
if(entryPoint) {
alias = getAlias();
}
StringBuffer buffer = getSpecificMatchQuery(childrenBreadcrumb);
buffer = wrapMatchQuery(buffer);
stringBuffer.append(buffer);
return stringBuffer;
}
}

View File

@ -1,11 +1,14 @@
package org.gcube.informationsystem.resourceregistry.queries.json.base.entities;
import java.util.List;
import org.gcube.com.fasterxml.jackson.databind.JsonNode;
import org.gcube.informationsystem.base.reference.AccessType;
import org.gcube.informationsystem.base.reference.Direction;
import org.gcube.informationsystem.resourceregistry.api.exceptions.ResourceRegistryException;
import org.gcube.informationsystem.resourceregistry.api.exceptions.queries.InvalidQueryException;
import org.gcube.informationsystem.resourceregistry.api.exceptions.types.SchemaException;
import org.gcube.informationsystem.resourceregistry.queries.json.base.JsonQueryERElement;
import org.gcube.informationsystem.resourceregistry.queries.json.base.relations.JsonQueryConsistsOf;
import org.gcube.informationsystem.resourceregistry.utils.OrientDBUtility;
@ -87,9 +90,11 @@ public class JsonQueryFacet extends JsonQueryEntity {
return newBuffer;
}
@Override
public StringBuffer createMatchQuery(StringBuffer stringBuffer) throws SchemaException, ResourceRegistryException {
protected StringBuffer getSpecificMatchQuery(List<JsonQueryERElement> childrenBreadcrumb)
throws SchemaException, ResourceRegistryException {
// TODO Auto-generated method stub
return null;
}

View File

@ -1,5 +1,7 @@
package org.gcube.informationsystem.resourceregistry.queries.json.base.entities;
import java.util.List;
import org.gcube.com.fasterxml.jackson.databind.JsonNode;
import org.gcube.com.fasterxml.jackson.databind.node.ArrayNode;
import org.gcube.informationsystem.base.reference.AccessType;
@ -7,6 +9,7 @@ import org.gcube.informationsystem.base.reference.Direction;
import org.gcube.informationsystem.model.reference.entities.Resource;
import org.gcube.informationsystem.resourceregistry.api.exceptions.ResourceRegistryException;
import org.gcube.informationsystem.resourceregistry.api.exceptions.types.SchemaException;
import org.gcube.informationsystem.resourceregistry.queries.json.base.JsonQueryERElement;
import org.gcube.informationsystem.resourceregistry.queries.json.base.relations.JsonQueryConsistsOf;
import org.gcube.informationsystem.resourceregistry.queries.json.base.relations.JsonQueryIsRelatedTo;
import org.gcube.informationsystem.resourceregistry.utils.OrientDBUtility;
@ -115,8 +118,89 @@ public class JsonQueryResource extends JsonQueryEntity {
}
@Override
public StringBuffer createMatchQuery(StringBuffer stringBuffer) throws SchemaException, ResourceRegistryException {
return null;
protected StringBuffer getSpecificMatchQuery(List<JsonQueryERElement> childrenBreadcrumb) throws SchemaException, ResourceRegistryException {
StringBuffer buffer = new StringBuffer();
int isRelatedToSize = 0;
ArrayNode consistsOfArray = (ArrayNode) jsonNode.get(Resource.CONSISTS_OF_PROPERTY);
int consistsOfSize = 0;
if(consistsOfArray!=null) {
consistsOfSize = consistsOfArray.size();
}
ArrayNode isRelatedToArray = (ArrayNode) jsonNode.get(Resource.IS_RELATED_TO_PROPERTY);
if(isRelatedToArray!=null && isRelatedToArray.size()>0) {
--size;
isRelatedToSize = isRelatedToArray.size();
for(int i=0; i<isRelatedToSize; i++) {
JsonNode isRelatedToJsonNode = isRelatedToArray.get(i);
JsonQueryIsRelatedTo jsonQueryIsRelatedTo = new JsonQueryIsRelatedTo(isRelatedToJsonNode);
jsonQueryIsRelatedTo.setRequestedResourceType(type);
jsonQueryIsRelatedTo.setDirectionByJson();
jsonQueryIsRelatedTo.setCaller(this);
jsonQueryIsRelatedTo.setBreadcrumb(childrenBreadcrumb);
jsonQueryIsRelatedTo.setPosition(i);
boolean traverseBack = false;
if(i<(isRelatedToSize-1) || consistsOfSize>0) {
traverseBack = true;
}
jsonQueryIsRelatedTo.setTraverseBack(traverseBack);
buffer = jsonQueryIsRelatedTo.createMatchQuery(buffer);
if(traverseBack) {
buffer.append(jsonQueryIsRelatedTo.getDirection().opposite().name().toLowerCase());
buffer.append("V('");
buffer.append(type);
buffer.append("') ");
if(alias!=null || entryPoint) {
buffer.append("{ where: ($matched.");
buffer.append(alias);
buffer.append(" == $currentMatch)}\n");
}
}
}
}
if(consistsOfSize>0) {
--size;
for(int i=0; i<consistsOfArray.size(); i++) {
JsonNode consistsOfJsonNode = consistsOfArray.get(i);
JsonQueryConsistsOf jsonQueryConsistsOf = new JsonQueryConsistsOf(consistsOfJsonNode);
jsonQueryConsistsOf.setRequestedResourceType(type);
jsonQueryConsistsOf.setDirection(Direction.IN);
jsonQueryConsistsOf.setCaller(this);
jsonQueryConsistsOf.setBreadcrumb(childrenBreadcrumb);
jsonQueryConsistsOf.setPosition(isRelatedToSize+i);
boolean traverseBack = false;
if(i<(consistsOfSize-1)) {
traverseBack = true;
}
jsonQueryConsistsOf.setTraverseBack(traverseBack);
buffer = jsonQueryConsistsOf.createMatchQuery(buffer);
if(traverseBack) {
buffer.append(jsonQueryConsistsOf.getDirection().opposite().name().toLowerCase());
buffer.append("V('");
buffer.append(type);
buffer.append("') ");
if(alias!=null || entryPoint) {
buffer.append("{ where: ($matched.");
buffer.append(alias);
buffer.append(" == $currentMatch)}\n");
}
}
}
}
return buffer;
}
}

View File

@ -1,11 +1,14 @@
package org.gcube.informationsystem.resourceregistry.queries.json.base.relations;
import java.util.List;
import org.gcube.com.fasterxml.jackson.databind.JsonNode;
import org.gcube.informationsystem.base.reference.AccessType;
import org.gcube.informationsystem.base.reference.Direction;
import org.gcube.informationsystem.model.reference.relations.ConsistsOf;
import org.gcube.informationsystem.resourceregistry.api.exceptions.ResourceRegistryException;
import org.gcube.informationsystem.resourceregistry.api.exceptions.types.SchemaException;
import org.gcube.informationsystem.resourceregistry.queries.json.base.JsonQueryERElement;
import org.gcube.informationsystem.resourceregistry.queries.json.base.entities.JsonQueryFacet;
import org.gcube.informationsystem.resourceregistry.queries.json.base.entities.JsonQueryResource;
import org.gcube.informationsystem.resourceregistry.utils.OrientDBUtility;
@ -134,9 +137,11 @@ public class JsonQueryConsistsOf extends JsonQueryRelation {
return stringBuffer;
}
@Override
public StringBuffer createMatchQuery(StringBuffer stringBuffer) throws SchemaException, ResourceRegistryException {
protected StringBuffer getSpecificMatchQuery(List<JsonQueryERElement> childrenBreadcrumb)
throws SchemaException, ResourceRegistryException {
// TODO Auto-generated method stub
return null;
}

View File

@ -1,5 +1,7 @@
package org.gcube.informationsystem.resourceregistry.queries.json.base.relations;
import java.util.List;
import javax.ws.rs.InternalServerErrorException;
import org.gcube.com.fasterxml.jackson.databind.JsonNode;
@ -9,6 +11,7 @@ import org.gcube.informationsystem.model.reference.relations.IsRelatedTo;
import org.gcube.informationsystem.resourceregistry.api.exceptions.ResourceRegistryException;
import org.gcube.informationsystem.resourceregistry.api.exceptions.queries.InvalidQueryException;
import org.gcube.informationsystem.resourceregistry.api.exceptions.types.SchemaException;
import org.gcube.informationsystem.resourceregistry.queries.json.base.JsonQueryERElement;
import org.gcube.informationsystem.resourceregistry.queries.json.base.entities.JsonQueryResource;
import org.gcube.informationsystem.resourceregistry.utils.OrientDBUtility;
@ -192,10 +195,20 @@ public class JsonQueryIsRelatedTo extends JsonQueryRelation {
return stringBuffer;
}
@Override
public StringBuffer createMatchQuery(StringBuffer stringBuffer) throws SchemaException, ResourceRegistryException {
return null;
protected StringBuffer getSpecificMatchQuery(List<JsonQueryERElement> childrenBreadcrumb) {
if(!entryPoint && direction==null) {
throw new InternalServerErrorException("Caller Resource must invoke setDirectionByJson() first. This is a server bug. Please contact the administator. ");
}
StringBuffer buffer = new StringBuffer();
return buffer;
}
}

View File

@ -188,4 +188,34 @@ public class JsonQueryTest extends ContextTest {
logger.info("No limit listing: Got {} with UUID {}", Entity.NAME, uuid);
}
}
@Test
public void testCreateMatchQuery() throws Exception {
File queriesDirectory = getQueriesDirectory();
File jsonQueryFile = new File(queriesDirectory, "query6.json");
ObjectMapper objectMapper = new ObjectMapper();
JsonNode jsonNode = objectMapper.readTree(jsonQueryFile);
logger.info("Going to test the following JSON query {}", jsonNode.toString());
JsonQuery jsonQuery = new JsonQuery();
jsonQuery.setJsonQuery(jsonNode);
StringBuffer createdStringBuffer = jsonQuery.createMatchQuery();
logger.info("Created Query from JSON:\n{}", createdStringBuffer.toString());
// StringBuffer expectedStringBuffer = new StringBuffer();
// File expectedQueryFile = new File(queriesDirectory, jsonQueryFile.getName().replace("json", "match.oquery"));
// try(BufferedReader br = new BufferedReader(new FileReader(expectedQueryFile))) {
// for(String line; (line = br.readLine()) != null; ) {
// expectedStringBuffer.append(line);
// }
// }
//
// logger.info("Expected Query from JSON: {}", expectedStringBuffer.toString());
//
// Assert.assertTrue(createdStringBuffer.toString().compareTo(expectedStringBuffer.toString())==0);
//
// String result = jsonQuery.query();
// logger.info("Result : {}", result);
}
}

View File

@ -0,0 +1,10 @@
[
{
"Id" : "",
"Group": "",
"Name" : "",
"Version" : "",
"Status" : "",
"Host" ""
}
]

View File

@ -0,0 +1,49 @@
{
"_projection" : {
"type": "EService",
"_emit" : {
"id" : "id"
},
"consistsOf": [
{
"type": "IsIdentifiedBy",
"target": {
"type": "SoftwareFacet",
"_emit" : {
"group" : "Group",
"name": "Name",
"version" : "Version"
}
}
},
{
"type": "ConsistsOf",
"target": {
"type": "StateFacet",
"_emit" : {
"value" : "Status"
}
}
}
],
"isRelatedTo" : [
{
"type": "Activates",
"source": {
"type": "HostingNode",
"consistsOf": [
{
"type": "IsIdentifiedBy",
"target": {
"type": "NetworkingFacet",
"_emit" : {
"hostName" : "Host"
}
}
}
]
}
}
]
}
}

View File

@ -0,0 +1,16 @@
MATCH
{class: EService, as: eservice, where: ($currentMatch['@class'] INSTANCEOF 'EService')}
.outE('IsIdentifiedBy').inV('SoftwareFacet') {as: softwarefacet, where: ($currentMatch['@class'] INSTANCEOF 'SoftwareFacet')}
.inE('IsIdentifiedBy')
.outV('Eservice') {where: ($matched.eservice == $currentMatch)}
.outE('ConsistsOf').inV("StateFacet") {as: statefacet, where: (($currentMatch['@class'] INSTANCEOF 'StateFacet'))}
.inE('ConsistsOf')
.outV('Eservice') {where: ($matched.eservice == $currentMatch)}
.inE('Activates').outV('HostingNode').outE('IsIdentifiedBy').inV('NetworkingFacet') {as: networkingfacet, where: ($currentMatch['@class'] INSTANCEOF 'NetworkingFacet')}
RETURN
eservice.id AS ID,
softwarefacet.group AS Group,
softwarefacet.name AS Name,
softwarefacet.version AS Version,
statefacet.value AS Status,
networkingfacet.hostName AS Host

View File

@ -0,0 +1,37 @@
SELECT id,
outE("IsIdentifiedBy").inV("SoftwareFacet").name[0] AS `Name`,
outE("IsIdentifiedBy").inV("SoftwareFacet").group[0] AS `Group`,
outE("IsIdentifiedBy").inV("SoftwareFacet").version[0] AS `Version`,
outE("ConsistsOf").inV("StateFacet").value AS `Status`,
inE("Activates").outV("HostingNode").outE("IsIdentifiedBy").inV("NetworkingFacet").hostName[0] AS `Host`
FROM (
TRAVERSE outV("EService") FROM (
TRAVERSE inE("ConsistsOf") FROM (
SELECT FROM (
TRAVERSE inV("StateFacet") FROM (
TRAVERSE outE("ConsistsOf") FROM (
TRAVERSE outV("EService") FROM (
TRAVERSE inE("IsIdentifiedBy") FROM (
SELECT FROM (
TRAVERSE inV("SoftwareFacet") FROM (
TRAVERSE outE("IsIdentifiedBy") FROM (
TRAVERSE inV("EService") FROM (
TRAVERSE outE("Activates") FROM (
TRAVERSE outV("HostingNode") FROM (
TRAVERSE inE("IsIdentifiedBy") FROM (
SELECT FROM NetworkingFacet
)
)
)
)
)
)
)
)
)
)
)
)
)
)
) WHERE @class INSTANCEOF "EService"

View File

@ -0,0 +1,18 @@
[
{
"id" : "",
"Last Update Time": "",
"HostName" : "",
"Status" : "",
"HD Space Left" : "34 Gb",
"Mem. Available." "1,23 Gb"
},
{
"id" : "",
"Last Update Time": "",
"HostName" : "",
"Status" : "",
"HD Space Left" : "42 Gb",
"Mem. Available." "2,54 Gb"
}
]

View File

@ -0,0 +1,66 @@
{
"_projection" : {
"type": "HostingNode",
"_emit" : {
"id" : "Id",
},
"metadata" : {
"_emit" : {
"lastUpdateTime" : "Last Update Time"
}
},
"consistsOf": [
{
"type": "IsIdentifiedBy",
"target": {
"type": "NetworkingFacet",
"_emit" : {
"hostName" : "HostName"
}
}
},
{
"type": "ConsistsOf",
"target": {
"type": "StateFacet",
"_emit" : {
"value" : "Status"
}
}
},
{
"type": "HasPersistentMemory",
"target": {
"type": "MemoryFacet",
"_concat" : {
"values" : [
{
"_sub" : [
"size",
"used"
]
},
"unit"
],
"separator" : " ",
"as" : "HD Space Left"
}
}
},
{
"type": "HasVolatileMemory",
"target": {
"type": "MemoryFacet",
"_concat" : {
"values" : [
"size",
"unit"
],
"separator" : " ",
"as" : "Mem. Available."
}
}
}
]
}
}