[GH-ISSUE #135] Unexpected query filter behavior for contains operator when compare == []. #77

Closed
opened 2026-05-23 08:29:19 -06:00 by gitea-mirror · 6 comments
Owner

Originally created by @SilentAntenna on GitHub (Aug 9, 2022).
Original GitHub issue: https://github.com/appy-one/acebase/issues/135

Originally assigned to: @appy-one on GitHub.

According to the description of operator contains, when compare == [], the query should return all values in the database. But actually it returns nothing.

Note that compare == [] is possible in actual use. It may be followed by other query operations like sort and take.

Originally created by @SilentAntenna on GitHub (Aug 9, 2022). Original GitHub issue: https://github.com/appy-one/acebase/issues/135 Originally assigned to: @appy-one on GitHub. According to the description of operator `contains`, when `compare == []`, the query should return all values in the database. But actually it returns nothing. Note that `compare == []` is possible in actual use. It may be followed by other query operations like `sort` and `take`.
gitea-mirror 2026-05-23 08:29:19 -06:00
Author
Owner

@appy-one commented on GitHub (Aug 9, 2022):

The docs say:

'contains': value must be an array and it must contain a value equal to compare, or contain all of the values in compare array

It does not say it will match all records if you pass an empty array. I think that if you pass an empty array, it should match only nodes with empty array values.

If this is not the case, or can convince me that logic should be different, let me know. Also, please provide code examples with expected and actual output, so I can add them to the unit tests.

<!-- gh-comment-id:1209555078 --> @appy-one commented on GitHub (Aug 9, 2022): The docs say: >'contains': value must be an array and it must contain a value equal to compare, or contain all of the values in compare array It does not say it will match all records if you pass an empty array. I think that if you pass an empty array, it should match only nodes with empty array values. If this is not the case, or can convince me that logic should be different, let me know. Also, please provide code examples with expected and actual output, so I can add them to the unit tests.
Author
Owner

@SilentAntenna commented on GitHub (Aug 10, 2022):

Note: My bad. The database didn't return an empty set when compare == []. It actually threw an uncaught error. I didn't notice that because my error handler caught it in my own code.

Nevertheless, it is still a very dangerous behavior. And the error condition is not stated in the document.


It does not say it will match all records if you pass an empty array. I think that if you pass an empty array, it should match only nodes with empty array values.

If this is not the case, or can convince me that logic should be different, let me know.

Imagine an item list with property tags. We use query('address').filter('tags', 'contains', filter_tag_list).get() to get the items with related tags. If we add a new element in filter_tag_list, we will have more constraints in our query and fewer items will be returned. Now let's reverse the process. If we delete an element in filter_tag_list, we will have less constraints in our query and get more items, if we delete all the elements in filter_tag_list, there will be no constraint in our query at all.

Code example:

"use strict";
const { AceBase } = require('acebase');

(async () => {
    let db;

    const options = { logLevel: 'log', storage: { path: "./pathToDB" } };
    db = new AceBase('TestDB', options);
    await db.ready();

    await db.ref('food').push({
        name: 'apple',
        tags: ['fruit', 'sweet']
    });
    await db.ref('food').push({
        name: 'orange',
        tags: ['fruit', 'sweet', 'sour']
    });
    await db.ref('food').push({
        name: 'tomato',
        tags: ['vegetable', 'sour']
    });
    await db.ref('food').push({
        name: 'milk',
        tags: ['drink', 'sweet']
    });
    await db.ref('food').push({
        name: 'water',
        tags: ['drink']
    })
    await db.ref('food').push({
        name: 'salt',
        tags: []
    });

    console.log('Food with tags: ["fruit", "sweet"]');
    let queries = await db.query('food').filter('tags', 'contains', ['fruit', 'sweet']).get();
    for(let query of queries)console.log(query.val().name);
    console.log("");
    
    console.log('Food with tags: ["sweet"]');
    queries = await db.query('food').filter('tags', 'contains', ['sweet']).get();
    for(let query of queries)console.log(query.val().name);
    console.log("");

    console.log('Food with tags: []');
    queries = await db.query('food').filter('tags', 'contains', []).get();
    for(let query of queries)console.log(query.val().name);
    console.log("");
})();
<!-- gh-comment-id:1210360159 --> @SilentAntenna commented on GitHub (Aug 10, 2022): Note: My bad. The database didn't return an empty set when `compare == []`. It actually threw an uncaught error. I didn't notice that because my error handler caught it in my own code. Nevertheless, it is still a very dangerous behavior. And the error condition is not stated in the document. --- > It does not say it will match all records if you pass an empty array. I think that if you pass an empty array, it should match only nodes with empty array values. > > If this is not the case, or can convince me that logic should be different, let me know. Imagine an item list with property `tags`. We use `query('address').filter('tags', 'contains', filter_tag_list).get()` to get the items with related tags. If we add a new element in `filter_tag_list`, we will have more constraints in our query and fewer items will be returned. Now let's reverse the process. If we delete an element in `filter_tag_list`, we will have less constraints in our query and get more items, if we delete all the elements in `filter_tag_list`, there will be no constraint in our query at all. Code example: ```js "use strict"; const { AceBase } = require('acebase'); (async () => { let db; const options = { logLevel: 'log', storage: { path: "./pathToDB" } }; db = new AceBase('TestDB', options); await db.ready(); await db.ref('food').push({ name: 'apple', tags: ['fruit', 'sweet'] }); await db.ref('food').push({ name: 'orange', tags: ['fruit', 'sweet', 'sour'] }); await db.ref('food').push({ name: 'tomato', tags: ['vegetable', 'sour'] }); await db.ref('food').push({ name: 'milk', tags: ['drink', 'sweet'] }); await db.ref('food').push({ name: 'water', tags: ['drink'] }) await db.ref('food').push({ name: 'salt', tags: [] }); console.log('Food with tags: ["fruit", "sweet"]'); let queries = await db.query('food').filter('tags', 'contains', ['fruit', 'sweet']).get(); for(let query of queries)console.log(query.val().name); console.log(""); console.log('Food with tags: ["sweet"]'); queries = await db.query('food').filter('tags', 'contains', ['sweet']).get(); for(let query of queries)console.log(query.val().name); console.log(""); console.log('Food with tags: []'); queries = await db.query('food').filter('tags', 'contains', []).get(); for(let query of queries)console.log(query.val().name); console.log(""); })(); ```
Author
Owner

@appy-one commented on GitHub (Aug 15, 2022):

Thanks for your detailed explanation! I'll add a unit test with your code and make appropriate adjustments.

<!-- gh-comment-id:1215069570 --> @appy-one commented on GitHub (Aug 15, 2022): Thanks for your detailed explanation! I'll add a unit test with your code and make appropriate adjustments.
Author
Owner

@appy-one commented on GitHub (Aug 15, 2022):

I've made quite a few changes to the code today, managed to tackle this issue too. I'll try to release a new version within a week or so, if you'd want to test with the new codebase before then (I'm in the middle of a TypeScript port), make sure to clone both the acebase and acebase-core repositories!

<!-- gh-comment-id:1215776404 --> @appy-one commented on GitHub (Aug 15, 2022): I've made quite a few changes to the code today, managed to tackle this issue too. I'll try to release a new version within a week or so, if you'd want to test with the new codebase before then (I'm in the middle of a TypeScript port), make sure to clone both the acebase and acebase-core repositories!
Author
Owner

@appy-one commented on GitHub (Aug 19, 2022):

I published the fix in v1.22.0!

Spread the word contribute Sponsor AceBase

<!-- gh-comment-id:1221009105 --> @appy-one commented on GitHub (Aug 19, 2022): I published the fix in v1.22.0! [![Spread the word](https://user-images.githubusercontent.com/26569719/169265089-3d593555-e1ad-4390-986b-877ac2c38a47.svg)](https://twitter.com/intent/tweet?button=&url=https://github.com/appy-one/acebase&text=I'm+using+@AcebaseRealtime+in+my+project+to+make+my+life+easier!&button=) [![contribute](https://user-images.githubusercontent.com/26569719/169265318-30c4c6a5-7c89-46a0-a7a2-ef433a8192f4.svg)](https://github.com/appy-one/acebase#contributing) [![Sponsor AceBase](https://user-images.githubusercontent.com/26569719/168233053-8e56b243-4140-40ab-9a30-4cb3cc149bfe.svg)](https://github.com/sponsors/appy-one)
Author
Owner

@SilentAntenna commented on GitHub (Aug 20, 2022):

The filter is working as expected now. Thanks!

<!-- gh-comment-id:1221204527 --> @SilentAntenna commented on GitHub (Aug 20, 2022): The filter is working as expected now. Thanks!
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: github-starred/acebase#77
No description provided.