Skip to content

Not nullable array Hits are sometimes null #8481

Closed
@hesehus-bizzkit-mibu

Description

@hesehus-bizzkit-mibu

Elastic.Clients.Elasticsearch version:
8.17.3

Elasticsearch version:
8.17.2

.NET runtime version:
net9.0

Operating system version:
Docker/Windows

Description of the problem including expected versus actual behavior:
This might seem like an edge case but since two different teams ran in to this little gotcha individually. I would just make sure you where aware of it.

Accessing SearchResponse<T>.Hits may cause System.NullReferenceException

Steps to reproduce:
Simplified project to show behavior:

NullableElasticClient.csproj

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net9.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Elastic.Clients.Elasticsearch" Version="8.17.3" />
  </ItemGroup>

</Project>

Program.cs

using Elastic.Clients.Elasticsearch;
using Elastic.Clients.Elasticsearch.QueryDsl;
using Elastic.Transport;
using System.Text.Json;

var settings = new ElasticsearchClientSettings(new Uri("https://localhost:9200"))
    .DisableDirectStreaming()
    .Authentication(new BasicAuthentication("elastic", "YOUR_ELASTIC_PASSWORD"));

var client = new ElasticsearchClient(settings);

await client.Indices.CreateAsync("my_index");

var response1 = await client.SearchAsync<MyDoc>(s => s
    .Index("my_index")
    .Query(q => q.MatchAll(new MatchAllQuery()))
);

Console.WriteLine(JsonSerializer.Serialize(response1.Hits));

var response2 = await client.SearchAsync<MyDoc>(s => s
    .Index("my_index")
    .Query(q => q.MatchAll(new MatchAllQuery()))
    .FilterPath("hits.hits._source")
);

try
{
    Console.WriteLine(JsonSerializer.Serialize(response2.Hits));
}
catch (Exception e)
{
    Console.WriteLine("Error:"+e.Message);
}

await client.IndexAsync(new MyDoc { Id = 42, Name="name" }, i => i
            .Index("my_index")
            .Refresh(Refresh.True));

var response3 = await client.SearchAsync<MyDoc>(s => s
    .Index("my_index")
    .Query(q => q.MatchAll(new MatchAllQuery()))
    .FilterPath("hits.hits._source")
);

Console.WriteLine(JsonSerializer.Serialize(response3.Hits));

class MyDoc
{
    public int Id { get; set; }
    public required string Name { get; set; }
}

Expected behavior

When the result set is empty in query 2 the expected output is [] but instead the exception is thrown. This example is of cause simplified but there exist valid reasons for filtering aggressively in returned data.

Looking at

using System.Collections.Generic;
using System.Linq;
using System.Text.Json.Serialization;

namespace Elastic.Clients.Elasticsearch;

public partial class SearchResponse<TDocument>
{
	[JsonIgnore]
	public IReadOnlyCollection<Core.Search.Hit<TDocument>> Hits => HitsMetadata.Hits;

	[JsonIgnore]
	public IReadOnlyCollection<TDocument> Documents => HitsMetadata.Hits.Select(s => s.Source).ToReadOnlyCollection();

	[JsonIgnore]
	public long Total => HitsMetadata?.Total?.Item1?.Value ?? HitsMetadata?.Total?.Item2 ?? -1;
}

It would be expected that Hits would guard against null similar to Total.


Thanks for a great product.

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