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