-
Notifications
You must be signed in to change notification settings - Fork 3.2k
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
Use JsonScalarExpression for primitive collection indexing #30769
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,36 +17,28 @@ public class JsonScalarExpression : SqlExpression | |
/// <summary> | ||
/// Creates a new instance of the <see cref="JsonScalarExpression" /> class. | ||
/// </summary> | ||
/// <param name="jsonColumn">A column containg JSON value.</param> | ||
/// <param name="property">A property representing the result of this expression.</param> | ||
/// <param name="json">An expression representing a JSON value.</param> | ||
/// <param name="type">The <see cref="System.Type" /> of the expression.</param> | ||
/// <param name="typeMapping">The <see cref="RelationalTypeMapping" /> associated with the expression.</param> | ||
/// <param name="path">A list of path segments leading to the scalar from the root of the JSON stored in the column.</param> | ||
/// <param name="nullable">A value indicating whether the expression is nullable.</param> | ||
public JsonScalarExpression( | ||
ColumnExpression jsonColumn, | ||
IProperty property, | ||
IReadOnlyList<PathSegment> path, | ||
bool nullable) | ||
: this(jsonColumn, path, property.ClrType.UnwrapNullableType(), property.FindRelationalTypeMapping()!, nullable) | ||
{ | ||
} | ||
|
||
internal JsonScalarExpression( | ||
ColumnExpression jsonColumn, | ||
SqlExpression json, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This changes JsonScalarExpression to be over any arbitrary SqlExpression, not necessarily a ColumnExpression and without an IProperty. This is necessary to support indexing over JSON parameter collections. |
||
IReadOnlyList<PathSegment> path, | ||
Type type, | ||
RelationalTypeMapping typeMapping, | ||
RelationalTypeMapping? typeMapping, | ||
bool nullable) | ||
: base(type, typeMapping) | ||
{ | ||
JsonColumn = jsonColumn; | ||
Json = json; | ||
Path = path; | ||
IsNullable = nullable; | ||
} | ||
|
||
/// <summary> | ||
/// The column containing the JSON value. | ||
/// The expression containing the JSON value. | ||
/// </summary> | ||
public virtual ColumnExpression JsonColumn { get; } | ||
public virtual SqlExpression Json { get; } | ||
|
||
/// <summary> | ||
/// The list of path segments leading to the scalar from the root of the JSON stored in the column. | ||
|
@@ -61,46 +53,91 @@ internal JsonScalarExpression( | |
/// <inheritdoc /> | ||
protected override Expression VisitChildren(ExpressionVisitor visitor) | ||
{ | ||
var jsonColumn = (ColumnExpression)visitor.Visit(JsonColumn); | ||
var jsonColumnMadeNullable = jsonColumn.IsNullable && !JsonColumn.IsNullable; | ||
var newJson = (SqlExpression)visitor.Visit(Json); | ||
|
||
var nullable = IsNullable; | ||
if (newJson is ColumnExpression jsonColumnExpression) | ||
{ | ||
nullable |= jsonColumnExpression.IsNullable; | ||
} | ||
|
||
PathSegment[]? newPath = null; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note that VisitChildren didn't visit inside array index expression in the path - added support for that. |
||
|
||
for (var i = 0; i < Path.Count; i++) | ||
{ | ||
var segment = Path[i]; | ||
PathSegment newSegment; | ||
|
||
if (segment.PropertyName is not null) | ||
{ | ||
// PropertyName segments are (currently) constants, nothing to visit. | ||
newSegment = segment; | ||
} | ||
else | ||
{ | ||
var newArrayIndex = (SqlExpression)visitor.Visit(segment.ArrayIndex)!; | ||
if (newArrayIndex == segment.ArrayIndex) | ||
{ | ||
newSegment = segment; | ||
} | ||
else | ||
{ | ||
newSegment = new PathSegment(newArrayIndex); | ||
|
||
if (newPath is null) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. you do it this way to avoid allocation in case all segments are the same? or is there some other reason? should we switch to this pattern everywhere we visit a collection? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yep, that's why... And yeah, I've been gradually switching to this pattern for new code I write... Note that we may be able to extract this out to a utility function so as not to repeat it every time... |
||
{ | ||
newPath = new PathSegment[Path.Count]; | ||
for (var j = 0; j < i; i++) | ||
{ | ||
newPath[j] = Path[j]; | ||
} | ||
} | ||
} | ||
} | ||
|
||
if (newPath is not null) | ||
{ | ||
newPath[i] = newSegment; | ||
} | ||
} | ||
|
||
// TODO Call update: Issue#28887 | ||
return jsonColumn != JsonColumn | ||
? new JsonScalarExpression( | ||
jsonColumn, | ||
Path, | ||
return newJson == Json && newPath is null | ||
? this | ||
: new JsonScalarExpression( | ||
newJson, | ||
newPath ?? Path, | ||
Type, | ||
TypeMapping!, | ||
IsNullable || jsonColumnMadeNullable) | ||
: this; | ||
nullable); | ||
} | ||
|
||
/// <summary> | ||
/// Creates a new expression that is like this one, but using the supplied children. If all of the children are the same, it will | ||
/// return this expression. | ||
/// </summary> | ||
/// <param name="jsonColumn">The <see cref="JsonColumn" /> property of the result.</param> | ||
/// <param name="json">The <see cref="Json" /> property of the result.</param> | ||
/// <returns>This expression if no children changed, or an expression with the updated children.</returns> | ||
public virtual JsonScalarExpression Update(ColumnExpression jsonColumn) | ||
=> jsonColumn != JsonColumn | ||
? new JsonScalarExpression(jsonColumn, Path, Type, TypeMapping!, IsNullable) | ||
public virtual JsonScalarExpression Update(SqlExpression json) | ||
=> json != Json | ||
? new JsonScalarExpression(json, Path, Type, TypeMapping!, IsNullable) | ||
: this; | ||
|
||
/// <inheritdoc /> | ||
protected override void Print(ExpressionPrinter expressionPrinter) | ||
{ | ||
expressionPrinter.Append("JsonScalarExpression(column: "); | ||
expressionPrinter.Visit(JsonColumn); | ||
expressionPrinter.Visit(Json); | ||
expressionPrinter.Append($""", "{string.Join(".", Path.Select(e => e.ToString()))}")"""); | ||
} | ||
|
||
/// <inheritdoc /> | ||
public override bool Equals(object? obj) | ||
=> obj is JsonScalarExpression jsonScalarExpression | ||
&& JsonColumn.Equals(jsonScalarExpression.JsonColumn) | ||
&& Json.Equals(jsonScalarExpression.Json) | ||
&& Path.SequenceEqual(jsonScalarExpression.Path); | ||
|
||
/// <inheritdoc /> | ||
public override int GetHashCode() | ||
=> HashCode.Combine(base.GetHashCode(), JsonColumn, Path); | ||
=> HashCode.Combine(base.GetHashCode(), Json, Path); | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that here we'd infer the array's mapping from the element's inferred one, after #30730