Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support JsonOverlaps function #1985

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions src/EFCore.MySql/Extensions/MySqlJsonDbFunctionsExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,20 @@ public static T JsonExtract<T>(
[NotNull] params string[] paths)
=> throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(JsonExtract)));

/// <summary>
/// Checks if <paramref name="json"/> overlaps <paramref name="candidate"/>.
/// </summary>
/// <param name="_">DbFunctions instance</param>
/// <param name="json">
/// A JSON column or value. Can be a JSON DOM object, a string property mapped to JSON, or a user POCO mapped to JSON.
/// </param>
/// <param name="candidate">
/// A JSON column or value. Can be a JSON DOM object, a string, or a user POCO mapped to JSON.
/// </param>
public static bool JsonOverlaps(
[CanBeNull] this DbFunctions _, [NotNull] object json, [NotNull] object candidate)
Copy link
Collaborator

@lauxjpn lauxjpn Mar 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The parameters should probably be renamed to json1 and json2 respectively, to be closer to the ones used in MySQL/MariaDB (candidate is the parameter name MySQL uses for the JSON_CONTAINS() function).

=> throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(JsonOverlaps)));

/// <summary>
/// Checks if <paramref name="json"/> contains <paramref name="candidate"/>.
/// </summary>
Expand Down
1 change: 1 addition & 0 deletions src/EFCore.MySql/Infrastructure/MariaDbServerVersion.cs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ internal MariaDbServerVersionSupport([NotNull] ServerVersion serverVersion)
public override bool LimitWithNonConstantValue => false;
public override bool JsonTable => ServerVersion.Version >= new Version(10, 6, 0); // Since there seems to be no implicit LATERAL support for JSON_TABLE, this is pretty useless except for cases where the JSON is provided by a parameter instead of a column of an outer table.
public override bool JsonValue => true;
public override bool JsonOverlaps => ServerVersion.Version >= new Version(10, 9, 0);
public override bool Values => ServerVersion.Version >= new Version(10, 3, 3);
public override bool ValuesWithRows => false;
public override bool WhereSubqueryReferencesOuterQuery => false;
Expand Down
1 change: 1 addition & 0 deletions src/EFCore.MySql/Infrastructure/MySqlServerVersion.cs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ internal MySqlServerVersionSupport([NotNull] ServerVersion serverVersion)
public override bool LimitWithNonConstantValue => false;
public override bool JsonTable => ServerVersion.Version >= new Version(8, 0, 4);
public override bool JsonValue => ServerVersion.Version >= new Version(8, 0, 21);
public override bool JsonOverlaps => ServerVersion.Version >= new Version(8, 0, 0);
public override bool Values => false;
public override bool ValuesWithRows => ServerVersion.Version >= new Version(8, 0, 19);
public override bool WhereSubqueryReferencesOuterQuery => false;
Expand Down
1 change: 1 addition & 0 deletions src/EFCore.MySql/Infrastructure/ServerVersionSupport.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ public virtual bool PropertyOrVersion(string propertyNameOrServerVersion)
public virtual bool CrossApply => false;
public virtual bool OuterReferenceInMultiLevelSubquery => false;
public virtual bool Json => false;
public virtual bool JsonOverlaps => false;
public virtual bool GeneratedColumns => false;
public virtual bool NullableGeneratedColumns => false;
public virtual bool ParenthesisEnclosedGeneratedColumnExpressions => false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,11 @@ public virtual SqlExpression Translate(
method.ReturnType,
_sqlExpressionFactory.FindMapping(method.ReturnType, "json"),
false),
nameof(MySqlJsonDbFunctionsExtensions.JsonOverlaps)
=> _sqlExpressionFactory.NullableFunction(
"JSON_OVERLAPS",
new[] { Json(args[0]), args[1] },
typeof(bool)),
nameof(MySqlJsonDbFunctionsExtensions.JsonContains)
=> _sqlExpressionFactory.NullableFunction(
"JSON_CONTAINS",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,74 @@ WHERE JSON_UNQUOTE(`j`.`CustomerElement`) = 'foo'

#region Functions

[ConditionalFact]
[SupportedServerVersionCondition(nameof(ServerVersionSupport.JsonOverlaps))]
public void JsonOverlaps_with_json_element()
{
using var ctx = CreateContext();
var element = JsonDocument.Parse(@"{""Name"": ""Joe"", ""Age"": -1}").RootElement;
var count = ctx.JsonEntities.Count(e =>
EF.Functions.JsonOverlaps(e.CustomerElement, element));

Assert.Equal(1, count);
AssertSql(
$@"@__element_1='{{""Name"":""Joe"",""Age"":-1}}' (Nullable = false) (Size = 4000)

SELECT COUNT(*)
FROM `JsonEntities` AS `j`
WHERE JSON_OVERLAPS(`j`.`CustomerElement`, {InsertJsonConvert("@__element_1")})");
}

[ConditionalFact]
[SupportedServerVersionCondition(nameof(ServerVersionSupport.JsonOverlaps))]
public void JsonOverlaps_with_string()
{
using var ctx = CreateContext();
var count = ctx.JsonEntities.Count(e =>
EF.Functions.JsonOverlaps(e.CustomerElement, @"{""Name"": ""Joe"", ""Age"": -1}"));

Assert.Equal(1, count);
AssertSql(
@"SELECT COUNT(*)
FROM `JsonEntities` AS `j`
WHERE JSON_OVERLAPS(`j`.`CustomerElement`, '{""Name"": ""Joe"", ""Age"": -1}')");
}

[ConditionalFact]
[SupportedServerVersionCondition(nameof(ServerVersionSupport.JsonOverlaps))]
public void JsonOverlaps_using_JsonExtract_with_json_element()
{
using var ctx = CreateContext();
var element = JsonDocument.Parse(@"[3,-1]").RootElement;
var count = ctx.JsonEntities.Count(e =>
EF.Functions.JsonOverlaps(EF.Functions.JsonExtract<string[]>(e.CustomerElement, "$.Statistics.Nested.IntArray"), element));

Assert.Equal(1, count);
var dd = InsertJsonConvert("@__element_1") ;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably obsolete code that can be removed.

AssertSql(
$@"@__element_1='[3,-1]' (Nullable = false) (Size = 4000)

SELECT COUNT(*)
FROM `JsonEntities` AS `j`
WHERE JSON_OVERLAPS(JSON_EXTRACT(`j`.`CustomerElement`, '$.Statistics.Nested.IntArray'), {InsertJsonConvert("@__element_1")})");
}

[ConditionalFact]
[SupportedServerVersionCondition(nameof(ServerVersionSupport.JsonOverlaps))]
public void JsonOverlaps_using_JsonExtract_with_json_string()
{
using var ctx = CreateContext();
var count = ctx.JsonEntities.Count(e =>
EF.Functions.JsonOverlaps(EF.Functions.JsonExtract<string[]>(e.CustomerElement, "$.Statistics.Nested.IntArray"), @"[3,-1]"));

Assert.Equal(1, count);
var dd = InsertJsonConvert("@__element_1");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably obsolete code that can be removed.

AssertSql(
$@"SELECT COUNT(*)
FROM `JsonEntities` AS `j`
WHERE JSON_OVERLAPS(JSON_EXTRACT(`j`.`CustomerElement`, '$.Statistics.Nested.IntArray'), '[3,-1]')");
}

[Fact]
public void JsonContains_with_json_element()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -346,7 +346,75 @@ WHERE JSON_UNQUOTE(`j`.`CustomerJToken`) = 'foo'
LIMIT 2");
}

#region Functions
#region Functions

[ConditionalFact]
[SupportedServerVersionCondition(nameof(ServerVersionSupport.JsonOverlaps))]
public void JsonOverlaps_with_json_element()
{
using var ctx = CreateContext();
var element = JObject.Parse(@"{""Name"": ""Joe"", ""Age"": -1}").Root;
var count = ctx.JsonEntities.Count(e =>
EF.Functions.JsonOverlaps(e.CustomerJToken, element));

Assert.Equal(1, count);
AssertSql(
$@"@__element_1='{{""Name"":""Joe"",""Age"":-1}}' (Size = 4000)

SELECT COUNT(*)
FROM `JsonEntities` AS `j`
WHERE JSON_OVERLAPS(`j`.`CustomerJToken`, {InsertJsonConvert("@__element_1")})");
}

[ConditionalFact]
[SupportedServerVersionCondition(nameof(ServerVersionSupport.JsonOverlaps))]
public void JsonOverlaps_with_string()
{
using var ctx = CreateContext();
var count = ctx.JsonEntities.Count(e =>
EF.Functions.JsonOverlaps(e.CustomerJToken, @"{""Name"": ""Joe"", ""Age"": -1}"));

Assert.Equal(1, count);
AssertSql(
@"SELECT COUNT(*)
FROM `JsonEntities` AS `j`
WHERE JSON_OVERLAPS(`j`.`CustomerJToken`, '{""Name"": ""Joe"", ""Age"": -1}')");
}

[ConditionalFact]
[SupportedServerVersionCondition(nameof(ServerVersionSupport.JsonOverlaps))]
public void JsonOverlaps_using_JsonExtract_with_json_element()
{
using var ctx = CreateContext();
var element = JArray.Parse(@"[3,-1]");
var count = ctx.JsonEntities.Count(e =>
EF.Functions.JsonOverlaps(EF.Functions.JsonExtract<string[]>(e.CustomerJToken, "$.Statistics.Nested.IntArray"), element));

Assert.Equal(1, count);
var dd = InsertJsonConvert("@__element_1");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably obsolete code that can be removed.

AssertSql(
$@"@__element_1='[3,-1]' (Size = 4000)

SELECT COUNT(*)
FROM `JsonEntities` AS `j`
WHERE JSON_OVERLAPS(JSON_EXTRACT(`j`.`CustomerJToken`, '$.Statistics.Nested.IntArray'), {InsertJsonConvert("@__element_1")})");
}

[ConditionalFact]
[SupportedServerVersionCondition(nameof(ServerVersionSupport.JsonOverlaps))]
public void JsonOverlaps_using_JsonExtract_with_json_string()
{
using var ctx = CreateContext();
var count = ctx.JsonEntities.Count(e =>
EF.Functions.JsonOverlaps(EF.Functions.JsonExtract<string[]>(e.CustomerJToken, "$.Statistics.Nested.IntArray"), @"[3,-1]"));

Assert.Equal(1, count);
var dd = InsertJsonConvert("@__element_1");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably obsolete code that can be removed.

AssertSql(
$@"SELECT COUNT(*)
FROM `JsonEntities` AS `j`
WHERE JSON_OVERLAPS(JSON_EXTRACT(`j`.`CustomerJToken`, '$.Statistics.Nested.IntArray'), '[3,-1]')");
}

[Fact]
public void JsonContains_with_json_element()
Expand Down