Skip to content

Commit

Permalink
Fix for opensearch-project#375 - Added .Strict(bool) and `.Verbatim…
Browse files Browse the repository at this point in the history
…(bool)` methods on `QueryContainer` that recursively apply the respective attribute to all subqueries. Also added a `.Name(string)` method on `QueryContainer` that names the (root) contained query.

Signed-off-by: Kostas <[email protected]>
  • Loading branch information
DumboJetEngine committed Jan 11, 2024
1 parent 4e721ee commit 0ca18bc
Show file tree
Hide file tree
Showing 7 changed files with 264 additions and 3 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,18 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
- Removed support for the `net461` target ([#256](https://github.com/opensearch-project/opensearch-net/pull/256))
- Fixed naming of `ClusterManagerTimeout` and `MasterTimeout` properties from `*TimeSpanout` in the low-level client ([#332](https://github.com/opensearch-project/opensearch-net/pull/332))


### Added
- Added `.Strict(bool)` and `.Verbatim(bool)` methods on `QueryContainer` that recursively apply the respective attribute to all subqueries. Also added a `.Name(string)` method on `QueryContainer` that names the (root) contained query.

### Removed
- Removed the `Features` API which is not supported by OpenSearch from the low-level client ([#331](https://github.com/opensearch-project/opensearch-net/pull/331))
- Removed the deprecated low-level `IndexTemplateV2` APIs in favour of the `ComposableIndexTemplate` APIs ([#437](https://github.com/opensearch-project/opensearch-net/pull/437))

### Fixed
- Fix `HttpConnection.ConvertHttpMethod` to support `Patch` method ([#489](https://github.com/opensearch-project/opensearch-net/pull/489))
- Fixed `IEnumerable<int?>` property mapping. ([#503](https://github.com/opensearch-project/opensearch-net/pull/503))


### Dependencies
- Bumps `Microsoft.CodeAnalysis.CSharp` from 4.2.0 to 4.6.0
Expand Down
20 changes: 17 additions & 3 deletions src/OpenSearch.Client/Mapping/Visitor/PropertyWalker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -205,11 +205,25 @@ private static Type GetUnderlyingType(Type type)
if (type.IsArray)
return type.GetElementType();

if (type.IsGenericType && type.GetGenericArguments().Length == 1
&& (type.GetInterfaces().HasAny(t => t == typeof(IEnumerable)) || Nullable.GetUnderlyingType(type) != null))
return type.GetGenericArguments()[0];
if (ShouldUnwrapType(type))
{
var returnType = type.GetGenericArguments()[0];
if (ShouldUnwrapType(returnType)) // This is needed for types like IEnumerable<int?>.
{
return returnType.GetGenericArguments()[0];
}
return returnType;
}

return type;
}

private static bool ShouldUnwrapType(Type ty) =>
ty.IsGenericType &&
ty.GetGenericArguments().Length == 1 &&
(
ty.GetInterfaces().HasAny(t => t == typeof(IEnumerable)) ||
Nullable.GetUnderlyingType(ty) != null
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@

using System;
using System.Runtime.Serialization;
using OpenSearch.Client.QueryDsl.Visitor;
using OpenSearch.Net.Utf8Json;

namespace OpenSearch.Client
Expand Down Expand Up @@ -131,5 +132,42 @@ out QueryContainer queryContainer

// ReSharper disable once UnusedMember.Global
internal bool ShouldSerialize(IJsonFormatterResolver formatterResolver) => IsWritable;


public QueryContainer Name(string name)
{
ContainedQuery.Name = name;
return this;
}

/// <summary>
/// Applies or removes the `strict` attribute to the query container and all child sub-queries.
/// </summary>
/// <param name="strict">When null, the value is taken from the root query.</param>
/// <returns></returns>
public QueryContainer Strict(bool? strict)
{
if (!strict.HasValue)
strict = ContainedQuery.IsStrict;

var visitor = new QueryNodeModifierVisitor((query, ctx) => query.IsStrict = strict.Value);
Accept(visitor);
return this;
}

/// <summary>
/// Applies or removes the `verbatim` attribute to the query container and all child sub-queries.
/// </summary>
/// <param name="verbatim">When null, the value is taken from the root query.</param>
/// <returns></returns>
public QueryContainer Verbatim(bool? verbatim)
{
if (!verbatim.HasValue)
verbatim = ContainedQuery.IsVerbatim;

var visitor = new QueryNodeModifierVisitor((query, ctx) => query.IsVerbatim = verbatim.Value);
Accept(visitor);
return this;
}
}
}
32 changes: 32 additions & 0 deletions src/OpenSearch.Client/QueryDsl/Visitor/QueryNodeModifierVisitor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/

using System;

namespace OpenSearch.Client.QueryDsl.Visitor
{
public class QueryNodeModifierVisitor : QueryVisitor
{
private readonly Action<IQuery, QueryNodeModifierVisitorContext> action;
private QueryNodeModifierVisitorContext context;

public QueryNodeModifierVisitor(Action<IQuery, QueryNodeModifierVisitorContext> action)
{
this.action = action;
context = new QueryNodeModifierVisitorContext();
}

public override void Visit(IQuery query)
{
context.Depth = Depth;
context.Scope = Scope;

action(query, context);
base.Visit(query);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/

namespace OpenSearch.Client.QueryDsl.Visitor
{
public struct QueryNodeModifierVisitorContext
{
public int Depth { get; internal set; }
public VisitorScope Scope { get; internal set; }
}
}
84 changes: 84 additions & 0 deletions tests/Tests/Mapping/PropertyWalkerTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/

using System;
using System.Collections.Generic;
using System.Linq;
using FluentAssertions;
using OpenSearch.Client;
using OpenSearch.OpenSearch.Xunit.XunitPlumbing;

namespace Tests.Mapping
{
public class PropertyWalkerTests
{
[U]
public void BoolGetsMappedCorrectly()
{
var result = TestPropertyType<bool>();
result.Should().Be("boolean");
}

[U]
public void StringGetsMappedCorrectly()
{
var result = TestPropertyType<string>();
result.Should().Be("text");
}


[U]
public void DateTimeGetsMappedCorrectly()
{
var result = TestPropertyType<DateTime>();
result.Should().Be("date");
}

[U]
public void IEnumerableOfNullableGetsMappedCorrectly()
{
var result = TestPropertyType<IEnumerable<int?>>();
result.Should().Be("integer");
}

[U]
public void IListOfNullableGetsMappedCorrectly()
{
var result = TestPropertyType<IList<int?>>();
result.Should().Be("integer");
}

[U]
public void IEnumerableOfValueTypesGetsMappedCorrectly()
{
var result = TestPropertyType<IEnumerable<int>>();
result.Should().Be("integer");
}

[U]
public void IListOfValueTypesGetsMappedCorrectly()
{
var result = TestPropertyType<IList<int>>();
result.Should().Be("integer");
}

private static string TestPropertyType<T>()
{
var walker = new PropertyWalker(typeof(Test<T>), null, 0);
var properties = walker.GetProperties();
var result = properties.SingleOrDefault();
return result.Value?.Type;
}


private class Test<T>
{
public T Values { get; set; }
}

}
}
72 changes: 72 additions & 0 deletions tests/Tests/QueryDsl/Container/QueryContainerTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/

using FluentAssertions;
using OpenSearch.Client;
using OpenSearch.OpenSearch.Xunit.XunitPlumbing;
using Xunit;

namespace Tests.QueryDsl.Container
{
public class QueryContainerTests
{
[TU]
[InlineData(false, false)]
[InlineData(false, true)]
[InlineData(true, false)]
[InlineData(true, true)]
public void StrictAndVerbatimAttributesAreRecursivelySetCorrectly(bool targetStrict, bool targetVerbatim)
{
// Arrange
var query0 = new TermQuery { Field = "field", Value = 1, IsStrict = !targetStrict, IsVerbatim = !targetVerbatim };
var query1 = new BoolQuery { MustNot = new QueryContainer[] { query0 } };
var query2 = new TermQuery { Field = "field2", Value = 7, IsStrict = !targetStrict, IsVerbatim = !targetVerbatim };
var query3 = new BoolQuery { Must = new QueryContainer[] { query1, query2 } };
var queryContainer = new QueryContainer(query3);

// Act
queryContainer.Strict(targetStrict);
queryContainer.Verbatim(targetVerbatim);

// Assert
query0.IsStrict.Should().Be(targetStrict);
query0.IsVerbatim.Should().Be(targetVerbatim);
query1.IsStrict.Should().Be(targetStrict);
query1.IsVerbatim.Should().Be(targetVerbatim);
query2.IsStrict.Should().Be(targetStrict);
query2.IsVerbatim.Should().Be(targetVerbatim);
query3.IsStrict.Should().Be(targetStrict);
query3.IsVerbatim.Should().Be(targetVerbatim);
}

[TU]
[InlineData("name1")]
[InlineData("a name")]
[InlineData(null)]
public void SettingTheNameOnTheQueryContainerSetTheNameOnTheContainedQuery(string name)
{
// Arrange
var query0 = new TermQuery { Name = "a", Field = "field", Value = 1 };
var query1 = new BoolQuery { Name = "b", MustNot = new QueryContainer[] { query0 } };
var query2 = new TermQuery { Name = "c", Field = "field2", Value = 7 };
var query3 = new BoolQuery { Name = "d", Must = new QueryContainer[] { query1, query2 } };
var queryContainer = new QueryContainer(query3);

// Act
queryContainer.Name(name);

// Assert
query3.Name.Should().Be(name);
queryContainer.ContainedQuery.Name.Should().Be(name);
query0.Name.Should().Be("a");
query1.Name.Should().Be("b");
query2.Name.Should().Be("c");
}


}
}

0 comments on commit 0ca18bc

Please sign in to comment.