objectbox-java icon indicating copy to clipboard operation
objectbox-java copied to clipboard

QueryBuilder's notEqual doesn't return objects where that field is null

Open raja-baz opened this issue 5 years ago • 3 comments

It's in the title, but a concrete example would be. Say we have a model with a String field called field. We have two objects in the database, one of them has field set to an empty String and another one has it set to null. The following query returns the one with the empty field but not the one with null:

box.query().notEqual(Model_.field, "hello world").build().find()

Basic info (please complete the following information):

  • ObjectBox version: latest (2.7.1)
  • Reproducibility: always
  • Device: any
  • OS: any

Expected behavior

An object with a particular field being null should match a notEqual which contains any non-null value, instead it doesn't.

Code

        store = MyObjectBox.builder().androidContext(this).buildDefault()
        val box = store.boxFor(Model::class.java)
        box.removeAll()
        Log.d("TEST_LOG", "putting null with id: " + box.put(Model(0, null)))
        Log.d("TEST_LOG", "putting empty with id: " + box.put(Model(0, "")))
        val logString = models.map { it.id }.sorted().joinToString()
        Log.d("TEST_LOG", "models: $logString")

Where Model has a single String field called field

The above code clearly logs only the model with the empty field being returned but not the one with the null value for field. Maybe I'm misunderstanding something but this is a very unintuitive definition of notEqual and nothing in the docs seems to indicate it would behave this way.

raja-baz avatar Sep 23 '20 19:09 raja-baz

You need to use .isNull() instead

greenrobot avatar Sep 24 '20 07:09 greenrobot

I get that I could just or() .notEqual() and .isNull() to get the desired result, but it feels like a bug that this isn't the behavior already. As it stands, notEqual() is not a proper negation of equal() as there are objects that will match neither. If this behavior is desired it should at least be clarified in the documentation as most people will expect notEqual() to be equivalent to returning everything that equal() doesn't match.

As a concrete example, say I have an Address model with a nullable street field. If I wanted to express the following query: "Select all addresses that aren't on <x> street". I would expect the way to do this with QueryBuilder to be:

store.boxFor(Address.class).notEqual(Address_.street, "<x>").build()

However, right now, the proper way to do it is:

store.boxFor(Address.class).notEqual(Address_.street, "<x>").or().isNull(Address_.street).build()

This is, to say the least, surprising and should be documented if this is a feature not a bug(but, arguably, given that this is not the way any other database treats the not-equal operator this feels more like a bug)

raja-baz avatar Sep 24 '20 08:09 raja-baz

Note: this has also come up as part of the default values discussion. https://github.com/objectbox/objectbox-java/issues/157#issuecomment-467322905

greenrobot-team avatar Sep 28 '20 06:09 greenrobot-team