Skip to content

Commit

Permalink
Merge pull request #178 from JetBrains/feature-XD-1065-query-tests
Browse files Browse the repository at this point in the history
XD-1065: Fix UnaryNot; Property/Edge is null; difference optimisation
  • Loading branch information
andrii0lomakin committed Jul 4, 2024
2 parents 19c16e1 + a89688b commit 7f7a785
Show file tree
Hide file tree
Showing 11 changed files with 249 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,11 @@ import jetbrains.exodus.entitystore.orientdb.query.OSelect

class OLinkExistsEntityIterable(
txn: StoreTransaction,
private val className: String,
private val entityType: String,
private val linkName: String,
) : OQueryEntityIterableBase(txn) {

override fun query(): OSelect {
return OClassSelect(className, OEdgeExistsCondition(linkName.asEdgeClass))
return OClassSelect(entityType, OEdgeExistsCondition(linkName.asEdgeClass))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Copyright ${inceptionYear} - ${year} ${owner}
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package jetbrains.exodus.entitystore.orientdb.iterate.link

import jetbrains.exodus.entitystore.StoreTransaction
import jetbrains.exodus.entitystore.orientdb.asEdgeClass
import jetbrains.exodus.entitystore.orientdb.iterate.OQueryEntityIterableBase
import jetbrains.exodus.entitystore.orientdb.query.OClassSelect
import jetbrains.exodus.entitystore.orientdb.query.OEdgeIsNullCondition
import jetbrains.exodus.entitystore.orientdb.query.OSelect

class OLinkIsNullEntityIterable(
txn: StoreTransaction,
private val entityType: String,
private val linkName: String,
) : OQueryEntityIterableBase(txn) {

override fun query(): OSelect {
return OClassSelect(entityType, OEdgeIsNullCondition(linkName.asEdgeClass))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Copyright ${inceptionYear} - ${year} ${owner}
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package jetbrains.exodus.entitystore.iterate.property

import jetbrains.exodus.entitystore.StoreTransaction
import jetbrains.exodus.entitystore.orientdb.iterate.OQueryEntityIterableBase
import jetbrains.exodus.entitystore.orientdb.query.OClassSelect
import jetbrains.exodus.entitystore.orientdb.query.OFieldIsNullCondition
import jetbrains.exodus.entitystore.orientdb.query.OSelect

class OPropertyIsNullIterable(
txn: StoreTransaction,
private val entityType: String,
private val propertyName: String,
) : OQueryEntityIterableBase(txn) {

override fun query(): OSelect {
return OClassSelect(entityType, OFieldIsNullCondition(propertyName))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,43 @@ class OStartsWithCondition(
override fun params() = listOf("${value}%")
}

class OFieldExistsCondition(
val field: String
) : OCondition {

override fun sql(builder: SqlBuilder) {
builder.append("not(").append(field).append(" is null)")
}
}

class OFieldIsNullCondition(
val field: String
) : OCondition {

override fun sql(builder: SqlBuilder) {
builder.append(field).append(" is null")
}
}

// Edge
class OEdgeExistsCondition(
val edge: String
) : OCondition {

override fun sql(builder: SqlBuilder) {
builder.append("outE('").append(edge).append("').size() > 0")
}
}

class OEdgeIsNullCondition(
val edge: String
) : OCondition {

override fun sql(builder: SqlBuilder) {
builder.append("outE('").append(edge).append("').size() == 0")
}
}

// Binary
sealed class OBiCondition(
val operation: String,
Expand All @@ -77,6 +114,20 @@ sealed class OBiCondition(
class OAndCondition(left: OCondition, right: OCondition) : OBiCondition("AND", left, right)
class OOrCondition(left: OCondition, right: OCondition) : OBiCondition("OR", left, right)

// Negation
class NotCondition(
val condition: OCondition
) : OCondition {

override fun sql(builder: SqlBuilder) {
builder.append("NOT (")
condition.sql(builder.deepen())
builder.append(")")
}

override fun params() = condition.params()
}

class OAndNotCondition(
val left: OCondition,
val right: OCondition
Expand Down Expand Up @@ -108,31 +159,13 @@ class ORangeCondition(
override fun params() = listOf(minInclusive, maxInclusive)
}

class OEdgeExistsCondition(
val edge: String
) : OCondition {

override fun sql(builder: SqlBuilder) {
builder.append("outE('").append(edge).append("').size() > 0")
}
}

class OFieldExistsCondition(
val field: String
) : OCondition {

override fun sql(builder: SqlBuilder) {
builder.append("not(").append(field).append(" is null)")
}
}

class OInstanceOfCondition(
val className: String,
val invert: Boolean
) : OCondition {

override fun sql(builder: SqlBuilder) {
if (invert){
if (invert) {
builder.append("NOT ")
}
builder.append("@this INSTANCEOF '$className'")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,11 @@ fun OCondition?.or(other: OCondition?): OCondition? {
}

fun OCondition?.andNot(other: OCondition?): OCondition? {
if (this == null) return other
if (other == null) return this
return OAndNotCondition(this, other)
if (this == null && other == null) return null
if (this == null && other != null) return NotCondition(other)
if (other == null && this != null) return NotCondition(this)
if (this != null && other != null) return OAndNotCondition(this, other)
return null
}

fun equal(field: String, value: Any) = OEqualCondition(field, value)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@
package jetbrains.exodus.entitystore.orientdb.iterate

import com.google.common.truth.Truth.assertThat
import com.orientechnologies.orient.core.db.ODatabaseSession
import com.orientechnologies.orient.core.record.OVertex
import jetbrains.exodus.entitystore.iterate.property.OPropertyIsNullIterable
import jetbrains.exodus.entitystore.orientdb.OStoreTransaction
import jetbrains.exodus.entitystore.orientdb.OVertexEntity
import jetbrains.exodus.entitystore.orientdb.getOrCreateVertexClass
import jetbrains.exodus.entitystore.orientdb.iterate.link.OLinkIsNullEntityIterable
import jetbrains.exodus.entitystore.orientdb.iterate.property.OInstanceOfIterable
import jetbrains.exodus.entitystore.orientdb.setLocalEntityIdIfAbsent
import jetbrains.exodus.entitystore.orientdb.testutil.*
Expand All @@ -37,6 +37,38 @@ class OEntityIterableBaseTest : OTestMixin {

override val orientDb = orientDbRule

@Test
fun `should iterable property is null`() {
// Given
val test = givenTestCase()
orientDb.withSession {
test.issue1.setProperty("none", "n1")
}

// When
oTransactional { tx ->
val issues = OPropertyIsNullIterable(tx, Issues.CLASS, "none")

// Then
assertNamesExactlyInOrder(issues, "issue2", "issue3")
}
}

@Test
fun `should iterable link is null`() {
// Given
val test = givenTestCase()
orientDb.addIssueToProject(test.issue1, test.project1)

// When
oTransactional { tx ->
val issues = OLinkIsNullEntityIterable(tx, Issues.CLASS, Issues.Links.IN_PROJECT)

// Then
assertNamesExactlyInOrder(issues, "issue2", "issue3")
}
}

@Test
fun `should iterable union different issues`() {
// Given
Expand Down Expand Up @@ -155,6 +187,26 @@ class OEntityIterableBaseTest : OTestMixin {
}
}

@Test
fun `should iterable minus with properties and all`() {
// Given
val test = givenTestCase()
orientDb.withSession {
test.issue1.setProperty("complex", "true")
test.issue2.setProperty("complex", "true")
}

// When
oTransactional { tx ->
val issues = tx.getAll(Issues.CLASS)
val complexIssues = tx.find(Issues.CLASS, "complex", "true")
val simpleIssues = issues.minus(complexIssues)

// Then
assertNamesExactly(simpleIssues, "issue3")
}
}

@Test
fun `should iterable minus with properties`() {
// Given
Expand Down
4 changes: 4 additions & 0 deletions query/src/main/java/jetbrains/exodus/query/LinkEqual.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import jetbrains.exodus.entitystore.EntityId;
import jetbrains.exodus.entitystore.PersistentEntityId;
import jetbrains.exodus.entitystore.orientdb.OEntityId;
import jetbrains.exodus.entitystore.orientdb.iterate.link.OLinkIsNullEntityIterable;
import jetbrains.exodus.entitystore.orientdb.iterate.link.OLinkToEntityIterable;
import jetbrains.exodus.query.metadata.ModelMetaData;

Expand All @@ -44,6 +45,9 @@ private LinkEqual(String name, EntityId id, Entity entity) {
@Override
public Iterable<Entity> instantiate(String entityType, QueryEngine queryEngine, ModelMetaData metaData, InstantiateContext context) {
var txn = queryEngine.getPersistentStore().getAndCheckCurrentTransaction();
if (entity == null) {
return new OLinkIsNullEntityIterable(txn, entityType, name);
}
return new OLinkToEntityIterable(txn, name, (OEntityId) entity.getId());
}

Expand Down
4 changes: 4 additions & 0 deletions query/src/main/java/jetbrains/exodus/query/PropertyEqual.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@


import jetbrains.exodus.entitystore.Entity;
import jetbrains.exodus.entitystore.iterate.property.OPropertyIsNullIterable;
import jetbrains.exodus.entitystore.orientdb.iterate.property.OPropertyEqualIterable;
import jetbrains.exodus.query.metadata.ModelMetaData;
import org.jetbrains.annotations.Nullable;
Expand Down Expand Up @@ -45,6 +46,9 @@ public String getName() {
@Override
public Iterable<Entity> instantiate(String entityType, QueryEngine queryEngine, ModelMetaData metaData, InstantiateContext context) {
var txn = queryEngine.getPersistentStore().getAndCheckCurrentTransaction();
if (value == null) {
return new OPropertyIsNullIterable(txn, entityType, name);
}
return new OPropertyEqualIterable(txn, entityType, name, value);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,8 @@


import jetbrains.exodus.entitystore.Entity;
import jetbrains.exodus.entitystore.PersistentStoreTransaction;
import jetbrains.exodus.entitystore.iterate.property.OPropertyExistsIterable;
import jetbrains.exodus.entitystore.orientdb.iterate.link.OLinkExistsEntityIterable;
import jetbrains.exodus.entitystore.orientdb.query.OFieldExistsCondition;
import jetbrains.exodus.query.metadata.EntityMetaData;
import jetbrains.exodus.query.metadata.ModelMetaData;
import jetbrains.exodus.query.metadata.PropertyMetaData;
import jetbrains.exodus.query.metadata.PropertyType;

import static jetbrains.exodus.query.Utils.safe_equals;

Expand Down
8 changes: 7 additions & 1 deletion query/src/main/java/jetbrains/exodus/query/UnaryNot.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@


import jetbrains.exodus.entitystore.Entity;
import jetbrains.exodus.entitystore.orientdb.OQueryEntityIterable;
import jetbrains.exodus.entitystore.orientdb.iterate.OEntityOfTypeIterable;
import jetbrains.exodus.entitystore.orientdb.iterate.binop.OMinusEntityIterable;
import jetbrains.exodus.query.metadata.ModelMetaData;

public class UnaryNot extends UnaryNode {
Expand All @@ -26,7 +29,10 @@ public UnaryNot(final NodeBase child) {

@Override
public Iterable<Entity> instantiate(String entityType, QueryEngine queryEngine, ModelMetaData metaData, InstantiateContext context) {
throw new RuntimeException("Can't instantiate single UnaryNot query!");
var txn = queryEngine.getPersistentStore().getAndCheckCurrentTransaction();
var all = new OEntityOfTypeIterable(txn, entityType);
var minus = (OQueryEntityIterable) child.instantiate(entityType, queryEngine, metaData, context);
return new OMinusEntityIterable(txn, all, minus);
}

@Override
Expand Down
Loading

0 comments on commit 7f7a785

Please sign in to comment.