Skip to content

Delete by query leaves "residue" from non-stored fields #2434

Description

@kjac

Bug Description

It looks like delete by query doesn't play nice if the field targeted by the delete query is not configured as stored (store).

For stored fields, delete by query works exactly as expected.

For non-stored fields, the documents are indeed deleted from the index as expected, but they somehow still count towards the search result hit count and the facet counts.

In the repro script below, the failing test case produces a result like this:

{
    "facet_counts": [{
            "counts": [{
                    "count": 2,
                    "highlighted": "facet1",
                    "value": "facet1"
                }
            ],
            "field_name": "text",
            "sampled": false,
            "stats": {
                "total_values": 1
            }
        }
    ],
    "found": 2,
    "hits": [],
    "out_of": 2,
    "page": 1,
    "request_params": {
        "collection_name": "testindex2",
        "first_q": "*",
        "per_page": 10,
        "q": "*"
    },
    "search_cutoff": false,
    "search_time_ms": 1
}

...after all documents with the "facet1" value have been deleted by query.

Reproduction Steps

set -x

export TYPESENSE_HOST=http://localhost:8108
export TYPESENSE_API_KEY=xyz

docker stop typesense-repro 2>/dev/null
docker rm typesense-repro 2>/dev/null
rm -rf "$(pwd)"/typesense-data-dir-repro
mkdir "$(pwd)"/typesense-data-dir-repro

# Wait for Typesense to be ready
docker run -d -p 8108:8108 --name typesense-repro \
            -v"$(pwd)"/typesense-data-dir-repro:/data \
            typesense/typesense:29.0 \
            --data-dir /data \
            --api-key=$TYPESENSE_API_KEY \
            --enable-cors

# Wait till typesense is ready.
until curl -s -o /dev/null -w "%{http_code}" "${TYPESENSE_HOST}/health" -H "X-TYPESENSE-API-KEY: ${TYPESENSE_API_KEY}" | grep -q "200"; do
  sleep 2
done

curl -s "${TYPESENSE_HOST}/debug" \
       -H "X-TYPESENSE-API-KEY: ${TYPESENSE_API_KEY}"

# 1. create two identical indexes here - only difference is that the first of them stores the field, while the second does not.
curl -s "${TYPESENSE_HOST}/collections" \
       -X POST \
       -H "Content-Type: application/json" \
       -H "X-TYPESENSE-API-KEY: ${TYPESENSE_API_KEY}" \
       -d '{
             "name": "testindex1",
             "fields": [
               {"name": "text", "type": "string", "facet": true, "store": true }
             ],
             "default_sorting_field": ""
           }'
curl -s "${TYPESENSE_HOST}/collections" \
       -X POST \
       -H "Content-Type: application/json" \
       -H "X-TYPESENSE-API-KEY: ${TYPESENSE_API_KEY}" \
       -d '{
             "name": "testindex2",
             "fields": [
               {"name": "text", "type": "string", "facet": true, "store": false }
             ],
             "default_sorting_field": ""
           }'

# 2. add identical documents to the two indexes
curl -s "${TYPESENSE_HOST}/collections/testindex1/documents/import?action=create" \
        -H "X-TYPESENSE-API-KEY: ${TYPESENSE_API_KEY}" \
        -H "Content-Type: text/plain" \
        -X POST \
        -d '{"id": "1","text": "facet1"}
            {"id": "2","text": "facet2"}
            {"id": "3","text": "facet1"}
            {"id": "4","text": "facet2"}'
curl -s "${TYPESENSE_HOST}/collections/testindex2/documents/import?action=create" \
        -H "X-TYPESENSE-API-KEY: ${TYPESENSE_API_KEY}" \
        -H "Content-Type: text/plain" \
        -X POST \
        -d '{"id": "1","text": "facet1"}
            {"id": "2","text": "facet2"}
            {"id": "3","text": "facet1"}
            {"id": "4","text": "facet2"}'

# 3. delete documents with "facet1"
curl -s "${TYPESENSE_HOST}/collections/testindex1/documents/?filter_by=text:=facet1" \
	    -H "X-TYPESENSE-API-KEY: ${TYPESENSE_API_KEY}" \
        -X DELETE
curl -s "${TYPESENSE_HOST}/collections/testindex2/documents/?filter_by=text:=facet1" \
	    -H "X-TYPESENSE-API-KEY: ${TYPESENSE_API_KEY}" \
        -X DELETE

##################
### TEST CASES ###
##################

# ✔️ filter documents in the first index by "facet1"
# => yields no facets and result count = 0
# ==> works as expected
curl -g "${TYPESENSE_HOST}/collections/testindex1/documents/search?q=*&filter_by=text:=facet1&facet_by=text" \
	    -H "X-TYPESENSE-API-KEY: ${TYPESENSE_API_KEY}"

# ❌ filter documents in the second index by "facet1"
# => yields facet count = 2 for "facet1" and result count = 2, although no documents are returned
# ==> expected the same result as above
curl -g "${TYPESENSE_HOST}/collections/testindex2/documents/search?q=*&filter_by=text:=facet1&facet_by=text" \
	    -H "X-TYPESENSE-API-KEY: ${TYPESENSE_API_KEY}"


docker stop typesense-repro
docker rm typesense-repro

Expected vs Actual

Expected behavior

Deleted documents should not count against the search result hit count, nor the facet counts, no matter the field configuration.

Actual behavior

Deleted documents count against the search result hit count and the facet counts, when fields are not stored.

Environment

  • Typesense version: v29.0
  • Operating system: Windows 11

Schema / Configuration

Include any relevant schema definitions, JSON payloads, or config files:

{
    "name": "testindex2",
    "fields": [{"name": "text", "type": "string", "facet": true, "store": false}],
    "default_sorting_field": ""
}

No activity

Activity on this issue will appear here.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions