1 package de.dlr.shepard.common.util;
2
3 import de.dlr.shepard.common.configuration.feature.toggles.VersioningFeatureToggle;
4 import de.dlr.shepard.common.exceptions.ShepardParserException;
5 import de.dlr.shepard.common.neo4j.endpoints.OrderByAttribute;
6 import de.dlr.shepard.common.search.endpoints.BasicContainerAttributes;
7 import java.util.List;
8 import java.util.UUID;
9 import java.util.stream.Collectors;
10
11 public class CypherQueryHelper {
12
13 public enum Neighborhood {
14 EVERYTHING,
15 OUTGOING,
16 ESSENTIAL,
17 }
18
19 private CypherQueryHelper() {}
20
21 public static String getObjectPartWithVersion(String variable, String type, boolean hasName, String versionVariable) {
22 String ret = getObjectPart(variable, type, hasName);
23 ret = ret + "-[:has_version]->(" + versionVariable + ":Version)";
24 return ret;
25 }
26
27 public static String getObjectPart(String variable, String type, boolean hasName) {
28 if (hasName) return getObjectPartWithName(variable, type);
29 else return getObjectPartWithoutName(variable, type);
30 }
31
32 private static String getObjectPartWithName(String variable, String type) {
33 var namePart = "{ name : $name, deleted: FALSE }";
34 var result = "(%s:%s %s)".formatted(variable, type, namePart);
35 return result;
36 }
37
38 private static String getObjectPartWithoutName(String variable, String type) {
39 var namePart = "{ deleted: FALSE }";
40 var result = "(%s:%s %s)".formatted(variable, type, namePart);
41 return result;
42 }
43
44 public static String getPaginationPart() {
45 return "SKIP $offset LIMIT $size";
46 }
47
48 public static String getPaginationPart(PaginationHelper paginationParams) {
49 return "SKIP %d LIMIT %d".formatted(paginationParams.getOffset(), paginationParams.getSize());
50 }
51
52 public static String getReturnPart(String entity) {
53 return getReturnPart(entity, Neighborhood.EVERYTHING, 1);
54 }
55
56 public static String getReturnPart(String entity, int depth) {
57 return getReturnPart(entity, Neighborhood.EVERYTHING, depth);
58 }
59
60 public static String getReturnPart(String entity, Neighborhood neighborhood) {
61 return getReturnPart(entity, neighborhood, 1);
62 }
63
64 public static String getReturnPart(String entity, Neighborhood neighborhood, PaginationHelper pagination) {
65 return getReturnPart(entity, neighborhood, 1, pagination);
66 }
67
68 public static String getReturnCountPart(String entity, Neighborhood neighborhood) {
69 return (getNeighborhoodPart(entity, neighborhood, 1) + " RETURN " + "COUNT(%s)".formatted(entity));
70 }
71
72 public static String getReturnPart(String entity, Neighborhood neighborhood, int depth) {
73 return (
74 getNeighborhoodPart(entity, neighborhood, depth) +
75 " RETURN " +
76 "%s, nodes(path), relationships(path)".formatted(entity)
77 );
78 }
79
80 public static String getReturnPart(String entity, Neighborhood neighborhood, int depth, PaginationHelper pagination) {
81 return (
82 getNeighborhoodPart(entity, neighborhood, depth) +
83 (pagination != null ? " " + CypherQueryHelper.getPaginationPart(pagination) : "") +
84 " RETURN " +
85 "%s, nodes(path), relationships(path)".formatted(entity)
86 );
87 }
88
89 private static String getNeighborhoodPart(String entity, Neighborhood neighborhood, int depth) {
90
91 depth = Math.max(1, Math.min(3, depth));
92 String match =
93 switch (neighborhood) {
94 case EVERYTHING -> "path=(%s)-[*0..%d]-(n) WHERE n.deleted = FALSE OR n.deleted IS NULL";
95 case OUTGOING -> "path=(%s)-[*0..%d]->(n) WHERE n.deleted = FALSE OR n.deleted IS NULL";
96 case ESSENTIAL -> "path=(%s)-[*0..%d]->(n) WHERE n:Permission OR n:User";
97 };
98 return "MATCH " + match.formatted(entity, depth);
99 }
100
101 public static String getReturnPartLight(String entity) {
102 return "RETURN " + entity;
103 }
104
105 public static String getOrderByPart(String variable, OrderByAttribute orderByAttribute, Boolean orderDesc) {
106 String ret;
107 boolean isString = orderByAttribute.isString();
108 if (!isString) ret = "ORDER BY " + variable + "." + orderByAttribute;
109 else if (
110 orderByAttribute instanceof BasicContainerAttributes attributes && attributes == BasicContainerAttributes.type
111 ) ret = "ORDER BY LABELS(" + variable + ")";
112 else ret = "ORDER BY toLower(" + variable + "." + orderByAttribute + ")";
113 if (orderByAttribute.toString() == "id") ret = "ORDER BY id(" + variable + ")";
114 if (orderDesc != null && orderDesc) ret = ret + " DESC";
115 return ret;
116 }
117
118 public static String getShepardIdPart(String variable, long shepardId) {
119 return variable + "." + Constants.SHEPARD_ID + " = " + shepardId;
120 }
121
122 public static String getShepardIdsPart(String variable, List<Long> shepardIds) {
123 String commaSeparatedIds = shepardIds.stream().map(String::valueOf).collect(Collectors.joining(","));
124 return variable + "." + Constants.SHEPARD_ID + " in [" + commaSeparatedIds + "]";
125 }
126
127 public static String getReadableByQuery(String variable, String username) {
128 String ret =
129 """
130 (NOT exists((%s)-[:has_permissions]->(:Permissions)) \
131 OR exists((%s)-[:has_permissions]->(:Permissions)-[:readable_by|owned_by]->(:User { username: "%s" })) \
132 OR exists((%s)-[:has_permissions]->(:Permissions {permissionType: "Public"})) \
133 OR exists((%s)-[:has_permissions]->(:Permissions {permissionType: "PublicReadable"})) \
134 OR exists((%s)-[:has_permissions]->(:Permissions)-[:readable_by_group]->(:UserGroup)<-[:is_in_group]-(:User { username: "%s"})))""".formatted(
135 variable,
136 variable,
137 username,
138 variable,
139 variable,
140 variable,
141 username
142 );
143 return ret;
144 }
145
146 public static String getVersionHeadPart(String variable) {
147 if (VersioningFeatureToggle.isEnabled()) {
148 return "(" + variable + ".isHEADVersion = true)";
149 }
150 return "(1=1)";
151 }
152
153 public static String getVersionPart(String variable, UUID versionUID) {
154 return "(" + variable + ".uid = '" + versionUID + "')";
155 }
156
157 public static String[] splitByExactlyOneDoubleColon(String doubleColonSeparatedString) {
158
159
160
161
162
163
164
165
166 if (!doubleColonSeparatedString.matches(SearchConstants.JSON_COLON_MATCHER)) throw new ShepardParserException(
167 SearchConstants.INCORRECT_COLON_MESSAGE + doubleColonSeparatedString
168 );
169 String[] propertyValuePair = doubleColonSeparatedString.split("::");
170 String[] ret = new String[2];
171
172 if (propertyValuePair.length == 0) {
173 ret[0] = "";
174 ret[1] = "";
175 }
176
177 if (propertyValuePair.length == 1) {
178 ret[0] = propertyValuePair[0];
179 ret[1] = "";
180 }
181
182 if (propertyValuePair.length == 2) {
183 ret[0] = propertyValuePair[0];
184 ret[1] = propertyValuePair[1];
185 }
186 return ret;
187 }
188 }