|
| 1 | +/* |
| 2 | + * Licensed to the Apache Software Foundation (ASF) under one or more |
| 3 | + * contributor license agreements. See the NOTICE file distributed with |
| 4 | + * this work for additional information regarding copyright ownership. |
| 5 | + * The ASF licenses this file to You under the Apache License, Version 2.0 |
| 6 | + * (the "License"); you may not use this file except in compliance with |
| 7 | + * the License. You may obtain a copy of the License at |
| 8 | + * |
| 9 | + * http://www.apache.org/licenses/LICENSE-2.0 |
| 10 | + * |
| 11 | + * Unless required by applicable law or agreed to in writing, software |
| 12 | + * distributed under the License is distributed on an "AS IS" BASIS, |
| 13 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 14 | + * See the License for the specific language governing permissions and |
| 15 | + * limitations under the License. |
| 16 | + */ |
| 17 | + |
| 18 | +package org.apache.hugegraph.backend.query; |
| 19 | + |
| 20 | +import java.util.ArrayList; |
| 21 | +import java.util.Arrays; |
| 22 | +import java.util.Collection; |
| 23 | +import java.util.List; |
| 24 | + |
| 25 | +import org.apache.hugegraph.backend.id.Id; |
| 26 | +import org.apache.hugegraph.backend.query.Condition.Relation; |
| 27 | +import org.apache.hugegraph.backend.query.Condition.RelationType; |
| 28 | +import org.apache.hugegraph.structure.HugeEdge; |
| 29 | +import org.apache.hugegraph.structure.HugeElement; |
| 30 | +import org.apache.hugegraph.type.HugeType; |
| 31 | +import org.apache.hugegraph.type.define.Directions; |
| 32 | +import org.apache.hugegraph.type.define.HugeKeys; |
| 33 | +import org.apache.hugegraph.util.CollectionUtil; |
| 34 | +import org.apache.hugegraph.util.E; |
| 35 | +import org.apache.hugegraph.util.InsertionOrderUtil; |
| 36 | + |
| 37 | +import com.google.common.collect.ImmutableList; |
| 38 | + |
| 39 | +public class AdjacentEdgesQuery extends ConditionQuery { |
| 40 | + |
| 41 | + private Id ownerVertex; |
| 42 | + private Directions direction; |
| 43 | + private Id[] edgeLabels; |
| 44 | + |
| 45 | + private List<Condition> selfConditions; |
| 46 | + |
| 47 | + private static final Id[] EMPTY = new Id[0]; |
| 48 | + |
| 49 | + public AdjacentEdgesQuery(Id ownerVertex, Directions direction) { |
| 50 | + this(ownerVertex, direction, EMPTY); |
| 51 | + } |
| 52 | + |
| 53 | + public AdjacentEdgesQuery(Id ownerVertex, Directions direction, |
| 54 | + Id[] edgeLabels) { |
| 55 | + super(HugeType.EDGE); |
| 56 | + E.checkArgument(ownerVertex != null, |
| 57 | + "The edge query must contain source vertex"); |
| 58 | + E.checkArgument(direction != null, |
| 59 | + "The edge query must contain direction"); |
| 60 | + this.ownerVertex = ownerVertex; |
| 61 | + this.direction = direction; |
| 62 | + this.edgeLabels = edgeLabels; |
| 63 | + this.selfConditions = null; |
| 64 | + } |
| 65 | + |
| 66 | + @Override |
| 67 | + public int conditionsSize() { |
| 68 | + int size = super.conditionsSize() + 2; |
| 69 | + if (this.edgeLabels.length > 0) { |
| 70 | + size += 1; |
| 71 | + } |
| 72 | + return size; |
| 73 | + } |
| 74 | + |
| 75 | + @Override |
| 76 | + public ConditionQuery query(Condition condition) { |
| 77 | + if (!condition.isRelation()) { |
| 78 | + return super.query(condition); |
| 79 | + } |
| 80 | + |
| 81 | + // Reset this.selfConditions cache |
| 82 | + this.selfConditions = null; |
| 83 | + |
| 84 | + // Update condition fields |
| 85 | + Relation relation = ((Condition.Relation) condition); |
| 86 | + Object key = relation.key(); |
| 87 | + RelationType relationType = relation.relation(); |
| 88 | + |
| 89 | + if (key == HugeKeys.OWNER_VERTEX) { |
| 90 | + this.ownerVertex = (Id) relation.value(); |
| 91 | + return this; |
| 92 | + } |
| 93 | + if (key == HugeKeys.DIRECTION) { |
| 94 | + this.direction = (Directions) relation.value(); |
| 95 | + return this; |
| 96 | + } |
| 97 | + if (key == HugeKeys.LABEL) { |
| 98 | + Collection<Id> labels = null; |
| 99 | + if (relationType == RelationType.EQ) { |
| 100 | + labels = ImmutableList.of((Id) relation.value()); |
| 101 | + } else if (relationType == RelationType.IN) { |
| 102 | + @SuppressWarnings("unchecked") |
| 103 | + Collection<Id> value = (Collection<Id>) relation.value(); |
| 104 | + labels = value; |
| 105 | + } else { |
| 106 | + E.checkArgument(false, |
| 107 | + "Unexpected relation type '%s' for '%s'", |
| 108 | + relationType, key); |
| 109 | + } |
| 110 | + |
| 111 | + if (this.edgeLabels.length == 0) { |
| 112 | + this.edgeLabels = labels.toArray(new Id[0]); |
| 113 | + } else { |
| 114 | + Collection<Id> edgeLabels = CollectionUtil.intersect( |
| 115 | + Arrays.asList(this.edgeLabels), |
| 116 | + labels); |
| 117 | + if (edgeLabels.isEmpty()) { |
| 118 | + // Returns empty result if conditions are conflicting |
| 119 | + this.limit(0L); |
| 120 | + } |
| 121 | + this.edgeLabels = edgeLabels.toArray(new Id[0]); |
| 122 | + } |
| 123 | + return this; |
| 124 | + } |
| 125 | + |
| 126 | + return super.query(condition); |
| 127 | + } |
| 128 | + |
| 129 | + @Override |
| 130 | + public Collection<Condition> conditions() { |
| 131 | + List<Condition> conds = this.selfConditions(); |
| 132 | + if (super.conditionsSize() > 0) { |
| 133 | + conds.addAll(super.conditions()); |
| 134 | + } |
| 135 | + return conds; |
| 136 | + } |
| 137 | + |
| 138 | + private List<Condition> selfConditions() { |
| 139 | + if (this.selfConditions != null) { |
| 140 | + /* |
| 141 | + * Return selfConditions cache if it has been collected before |
| 142 | + * NOTE: it's also to keep the serialized condition value |
| 143 | + */ |
| 144 | + return this.selfConditions; |
| 145 | + } |
| 146 | + |
| 147 | + List<Condition> conds = InsertionOrderUtil.newList(); |
| 148 | + |
| 149 | + conds.add(Condition.eq(HugeKeys.OWNER_VERTEX, this.ownerVertex)); |
| 150 | + |
| 151 | + if (this.direction == Directions.BOTH) { |
| 152 | + conds.add(Condition.in(HugeKeys.DIRECTION, ImmutableList.of( |
| 153 | + Directions.OUT, |
| 154 | + Directions.IN))); |
| 155 | + } else { |
| 156 | + conds.add(Condition.eq(HugeKeys.DIRECTION, this.direction)); |
| 157 | + } |
| 158 | + |
| 159 | + if (this.edgeLabels.length == 1) { |
| 160 | + conds.add(Condition.eq(HugeKeys.LABEL, this.edgeLabels[0])); |
| 161 | + } else if (this.edgeLabels.length > 1) { |
| 162 | + conds.add(Condition.in(HugeKeys.LABEL, |
| 163 | + Arrays.asList(this.edgeLabels))); |
| 164 | + } |
| 165 | + |
| 166 | + this.selfConditions = conds; |
| 167 | + return conds; |
| 168 | + } |
| 169 | + |
| 170 | + @Override |
| 171 | + public <T> T condition(Object key) { |
| 172 | + T cond = this.selfCondition(key); |
| 173 | + if (cond != null) { |
| 174 | + return cond; |
| 175 | + } |
| 176 | + return super.condition(key); |
| 177 | + } |
| 178 | + |
| 179 | + @SuppressWarnings("unchecked") |
| 180 | + private <T> T selfCondition(Object key) { |
| 181 | + if (key == HugeKeys.OWNER_VERTEX) { |
| 182 | + return (T) this.ownerVertex; |
| 183 | + } |
| 184 | + if (key == HugeKeys.DIRECTION) { |
| 185 | + return (T) this.direction; |
| 186 | + } |
| 187 | + if (key == HugeKeys.LABEL) { |
| 188 | + if (this.edgeLabels.length == 0) { |
| 189 | + return null; |
| 190 | + } |
| 191 | + if (this.edgeLabels.length == 1) { |
| 192 | + return (T) this.edgeLabels[0]; |
| 193 | + } |
| 194 | + E.checkState(false, |
| 195 | + "Illegal key '%s' with more than one value", key); |
| 196 | + } |
| 197 | + return null; |
| 198 | + } |
| 199 | + |
| 200 | + private Condition.Relation selfRelation(Object key) { |
| 201 | + if (key == HugeKeys.OWNER_VERTEX) { |
| 202 | + return Condition.eq(HugeKeys.OWNER_VERTEX, this.ownerVertex); |
| 203 | + } |
| 204 | + if (key == HugeKeys.DIRECTION) { |
| 205 | + if (this.direction == Directions.BOTH) { |
| 206 | + return Condition.in(HugeKeys.DIRECTION, ImmutableList.of( |
| 207 | + Directions.OUT, |
| 208 | + Directions.IN)); |
| 209 | + } else { |
| 210 | + return Condition.eq(HugeKeys.DIRECTION, this.direction); |
| 211 | + } |
| 212 | + } |
| 213 | + if (key == HugeKeys.LABEL) { |
| 214 | + if (this.edgeLabels.length == 0) { |
| 215 | + return null; |
| 216 | + } |
| 217 | + if (this.edgeLabels.length == 1) { |
| 218 | + return Condition.eq(HugeKeys.LABEL, this.edgeLabels[0]); |
| 219 | + } |
| 220 | + return Condition.in(HugeKeys.LABEL, Arrays.asList(this.edgeLabels)); |
| 221 | + } |
| 222 | + return null; |
| 223 | + } |
| 224 | + |
| 225 | + @Override |
| 226 | + public boolean containsCondition(HugeKeys key) { |
| 227 | + if (key == HugeKeys.OWNER_VERTEX) { |
| 228 | + return true; |
| 229 | + } |
| 230 | + if (key == HugeKeys.DIRECTION) { |
| 231 | + return true; |
| 232 | + } |
| 233 | + if (key == HugeKeys.LABEL) { |
| 234 | + if (this.edgeLabels.length == 0) { |
| 235 | + return false; |
| 236 | + } |
| 237 | + return true; |
| 238 | + } |
| 239 | + return super.containsCondition(key); |
| 240 | + } |
| 241 | + |
| 242 | + @Override |
| 243 | + public void unsetCondition(Object key) { |
| 244 | + if (key == HugeKeys.OWNER_VERTEX || |
| 245 | + key == HugeKeys.DIRECTION || |
| 246 | + key == HugeKeys.LABEL) { |
| 247 | + E.checkArgument(false, "Can't unset condition '%s'", key); |
| 248 | + } |
| 249 | + super.unsetCondition(key); |
| 250 | + } |
| 251 | + |
| 252 | + @Override |
| 253 | + public List<Condition> syspropConditions() { |
| 254 | + List<Condition> conds = this.selfConditions(); |
| 255 | + if (super.conditionsSize() > 0) { |
| 256 | + conds.addAll(super.syspropConditions()); |
| 257 | + } |
| 258 | + return conds; |
| 259 | + } |
| 260 | + |
| 261 | + @Override |
| 262 | + public List<Condition> syspropConditions(HugeKeys key) { |
| 263 | + Condition cond = this.selfRelation(key); |
| 264 | + if (cond != null) { |
| 265 | + return ImmutableList.of(cond); |
| 266 | + } |
| 267 | + return super.syspropConditions(key); |
| 268 | + } |
| 269 | + |
| 270 | + @Override |
| 271 | + public List<Condition.Relation> relations() { |
| 272 | + // NOTE: selfConditions() actually return a list of Relation |
| 273 | + @SuppressWarnings({ "rawtypes", "unchecked" }) |
| 274 | + List<Condition.Relation> relations = (List) this.selfConditions(); |
| 275 | + if (super.conditionsSize() > 0) { |
| 276 | + relations.addAll(super.relations()); |
| 277 | + } |
| 278 | + return relations; |
| 279 | + } |
| 280 | + |
| 281 | + @Override |
| 282 | + public Relation relation(Id key){ |
| 283 | + Relation relation = this.selfRelation(key); |
| 284 | + if (relation != null) { |
| 285 | + return relation; |
| 286 | + } |
| 287 | + return super.relation(key); |
| 288 | + } |
| 289 | + |
| 290 | + @Override |
| 291 | + public boolean allSysprop() { |
| 292 | + return super.allSysprop(); |
| 293 | + } |
| 294 | + |
| 295 | + @Override |
| 296 | + public boolean allRelation() { |
| 297 | + if (!this.isFlattened()) { |
| 298 | + return false; |
| 299 | + } |
| 300 | + return super.allRelation(); |
| 301 | + } |
| 302 | + |
| 303 | + @Override |
| 304 | + public AdjacentEdgesQuery copy() { |
| 305 | + return (AdjacentEdgesQuery) super.copy(); |
| 306 | + } |
| 307 | + |
| 308 | + @Override |
| 309 | + public AdjacentEdgesQuery copyAndResetUnshared() { |
| 310 | + return (AdjacentEdgesQuery) super.copyAndResetUnshared(); |
| 311 | + } |
| 312 | + |
| 313 | + @Override |
| 314 | + public boolean isFlattened() { |
| 315 | + if (this.direction == Directions.BOTH) { |
| 316 | + return false; |
| 317 | + } |
| 318 | + if (this.edgeLabels.length > 1) { |
| 319 | + return false; |
| 320 | + } |
| 321 | + return super.isFlattened(); |
| 322 | + } |
| 323 | + |
| 324 | + @Override |
| 325 | + public boolean canFlatten() { |
| 326 | + return super.conditionsSize() == 0; |
| 327 | + } |
| 328 | + |
| 329 | + @Override |
| 330 | + public List<ConditionQuery> flatten() { |
| 331 | + int labels = this.edgeLabels.length; |
| 332 | + int directions = this.direction == Directions.BOTH ? 2 : 1; |
| 333 | + int size = labels * directions; |
| 334 | + List<ConditionQuery> queries = new ArrayList<>(size); |
| 335 | + if (labels == 0) { |
| 336 | + Id label = null; |
| 337 | + if (this.direction == Directions.BOTH) { |
| 338 | + queries.add(this.copyQuery(Directions.OUT, label)); |
| 339 | + queries.add(this.copyQuery(Directions.IN, label)); |
| 340 | + } else { |
| 341 | + queries.add(this.copyQuery(this.direction, label)); |
| 342 | + } |
| 343 | + } else { |
| 344 | + for (Id label : this.edgeLabels) { |
| 345 | + if (this.direction == Directions.BOTH) { |
| 346 | + queries.add(this.copyQuery(Directions.OUT, label)); |
| 347 | + queries.add(this.copyQuery(Directions.IN, label)); |
| 348 | + } else { |
| 349 | + queries.add(this.copyQuery(this.direction, label)); |
| 350 | + } |
| 351 | + } |
| 352 | + } |
| 353 | + E.checkState(super.conditionsSize() == 0, |
| 354 | + "Can't flatten query: %s", this); |
| 355 | + return queries; |
| 356 | + } |
| 357 | + |
| 358 | + private AdjacentEdgesQuery copyQuery(Directions direction, |
| 359 | + Id edgeLabel) { |
| 360 | + AdjacentEdgesQuery query = this.copy(); |
| 361 | + query.direction = direction; |
| 362 | + if (edgeLabel != null) { |
| 363 | + query.edgeLabels = new Id[]{edgeLabel}; |
| 364 | + } else { |
| 365 | + query.edgeLabels = EMPTY; |
| 366 | + } |
| 367 | + query.selfConditions = null; |
| 368 | + return query; |
| 369 | + } |
| 370 | + |
| 371 | + @Override |
| 372 | + public boolean test(HugeElement element) { |
| 373 | + if (!super.test(element)) { |
| 374 | + return false; |
| 375 | + } |
| 376 | + HugeEdge edge = (HugeEdge) element; |
| 377 | + if (!edge.ownerVertex().id().equals(this.ownerVertex)) { |
| 378 | + return false; |
| 379 | + } |
| 380 | + if (!edge.matchDirection(this.direction)) { |
| 381 | + return false; |
| 382 | + } |
| 383 | + |
| 384 | + boolean matchedLabel = false; |
| 385 | + if (this.edgeLabels.length == 0) { |
| 386 | + matchedLabel = true; |
| 387 | + } else { |
| 388 | + for (Id label : this.edgeLabels) { |
| 389 | + if (edge.schemaLabel().id().equals(label)) { |
| 390 | + matchedLabel = true; |
| 391 | + } |
| 392 | + } |
| 393 | + } |
| 394 | + return matchedLabel; |
| 395 | + } |
| 396 | +} |
0 commit comments