Skip to content

Commit 26e416c

Browse files
Copilotrenemadsen
andcommitted
Fix HasJsonPropertyName test by enabling TransformJsonQueryToTable implementation
- Uncommented the TransformJsonQueryToTable implementation - Fixed NullReferenceException by adding _sqlAliasManager to copy constructor - Fixed path handling for JSON_TABLE COLUMNS: use property names instead of array indices - Use JSON_EXTRACT instead of JsonScalarExpression to preserve JSON structure for JSON_TABLE - Test HasJsonPropertyName now passes Co-authored-by: renemadsen <[email protected]>
1 parent 1be22c7 commit 26e416c

File tree

1 file changed

+12
-39
lines changed

1 file changed

+12
-39
lines changed

src/EFCore.MySql/Query/Internal/MySqlQueryableMethodTranslatingExpressionVisitor.cs

Lines changed: 12 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ protected MySqlQueryableMethodTranslatingExpressionVisitor(
5252
{
5353
_sqlExpressionFactory = parentVisitor._sqlExpressionFactory;
5454
_typeMappingSource = parentVisitor._typeMappingSource;
55+
_sqlAliasManager = parentVisitor._sqlAliasManager;
5556
_options = parentVisitor._options;
5657
}
5758

@@ -226,38 +227,11 @@ protected override ShapedQueryExpression TranslateElementAtOrDefault(
226227

227228
protected override ShapedQueryExpression TransformJsonQueryToTable(JsonQueryExpression jsonQueryExpression)
228229
{
229-
// TODO: Implement JSON_TABLE support for structural types (entities/complex types) in JSON collections.
230-
//
231-
// Current Status:
232-
// - TransformJsonQueryToTable implementation is complete and matches Npgsql pattern
233-
// - JSON_TABLE syntax and COLUMNS clause generation is correct
234-
// - Issue is in EF Core base SelectExpression.AddCrossJoin or MySQL SQL generator
235-
// - When TranslateSelectMany calls AddCrossJoin, the CROSS JOIN keyword is not generated
236-
// - This results in invalid SQL: "FROM table1 JSON_TABLE(...)" instead of "FROM table1 CROSS JOIN JSON_TABLE(...)"
237-
//
238-
// Investigation completed:
239-
// - Npgsql uses identical CreateSelect pattern and it works for PostgreSQL
240-
// - MySQL supports both comma and CROSS JOIN syntax with JSON_TABLE (manually verified)
241-
// - The bug is in query assembly, not in provider-specific logic
242-
// - Requires either: override TranslateSelectMany, patch EF Core AddCrossJoin, or fix MySQL SQL generator
243-
//
244-
// Partial implementation preserved below for reference (currently commented out).
245-
// See commits: 11dc6b2, e17a1e9, 4b80703 for full implementation details.
246-
247-
// For now, throw a clear exception to inform users this is not yet supported
248-
throw new InvalidOperationException(
249-
"Composing LINQ operators (such as SelectMany) over collections of structural types inside JSON documents " +
250-
"is not currently supported by the MySQL provider. This feature requires fixes in EF Core's query assembly " +
251-
"logic or MySQL-specific SQL generation. As a workaround, consider materializing the JSON data to the client " +
252-
"using .AsEnumerable() or .ToList() before performing collection operations.");
253-
254-
/* PARTIAL IMPLEMENTATION - PRESERVED FOR FUTURE WORK
255-
256230
// Calculate the table alias for the JSON_TABLE function based on the last named path segment
257231
// (or the JSON column name if there are none)
258-
var lastNamedPathSegment = jsonQueryExpression.Path.LastOrDefault(ps => ps.PropertyName is not null);
232+
var lastNamedPathSegmentPropertyName = jsonQueryExpression.Path.LastOrDefault(ps => ps.PropertyName is not null).PropertyName;
259233
var tableAlias = _sqlAliasManager.GenerateTableAlias(
260-
lastNamedPathSegment.PropertyName ?? jsonQueryExpression.JsonColumn.Name);
234+
lastNamedPathSegmentPropertyName ?? jsonQueryExpression.JsonColumn.Name);
261235

262236
var jsonTypeMapping = jsonQueryExpression.JsonColumn.TypeMapping!;
263237

@@ -273,8 +247,8 @@ protected override ShapedQueryExpression TransformJsonQueryToTable(JsonQueryExpr
273247
new MySqlJsonTableExpression.ColumnInfo(
274248
Name: jsonPropertyName,
275249
TypeMapping: property.GetRelationalTypeMapping(),
276-
// Path for JSON_TABLE: $[0] to access array element properties
277-
Path: [new PathSegment(_sqlExpressionFactory.Constant(0, _typeMappingSource.FindMapping(typeof(int))))],
250+
// Path for JSON_TABLE COLUMNS: each row is already an array element, so access properties directly
251+
Path: [new PathSegment(jsonPropertyName)],
278252
AsJson: false));
279253
}
280254
}
@@ -295,7 +269,7 @@ protected override ShapedQueryExpression TransformJsonQueryToTable(JsonQueryExpr
295269
new MySqlJsonTableExpression.ColumnInfo(
296270
Name: jsonNavigationName,
297271
TypeMapping: jsonTypeMapping,
298-
Path: [new PathSegment(_sqlExpressionFactory.Constant(0, _typeMappingSource.FindMapping(typeof(int))))],
272+
Path: [new PathSegment(jsonNavigationName)],
299273
AsJson: true));
300274
}
301275
break;
@@ -310,7 +284,7 @@ protected override ShapedQueryExpression TransformJsonQueryToTable(JsonQueryExpr
310284
new MySqlJsonTableExpression.ColumnInfo(
311285
Name: jsonPropertyName,
312286
TypeMapping: jsonTypeMapping,
313-
Path: [new PathSegment(_sqlExpressionFactory.Constant(0, _typeMappingSource.FindMapping(typeof(int))))],
287+
Path: [new PathSegment(jsonPropertyName)],
314288
AsJson: true));
315289
}
316290
break;
@@ -319,10 +293,10 @@ protected override ShapedQueryExpression TransformJsonQueryToTable(JsonQueryExpr
319293
throw new UnreachableException();
320294
}
321295

322-
// MySQL JSON_TABLE requires the nested JSON document as raw JSON (not extracted as a scalar value).
323-
// We need to use JSON_EXTRACT (not JSON_VALUE) to get the JSON fragment with proper structure.
324-
// Unlike Npgsql which can use JsonScalarExpression (translates to json extraction in PostgreSQL),
325-
// MySQL's JsonScalarExpression translates to JSON_VALUE which strips quotes and can't feed JSON_TABLE.
296+
// json_to_recordset in Npgsql uses JsonScalarExpression which PostgreSQL handles properly.
297+
// However, MySQL's JsonScalarExpression translates to JSON_VALUE which extracts scalar values
298+
// and strips JSON structure. For JSON_TABLE, we need the raw JSON fragment.
299+
// Use JSON_EXTRACT explicitly to get the nested JSON array/object.
326300

327301
SqlExpression jsonSource;
328302
if (jsonQueryExpression.Path.Count > 0)
@@ -341,7 +315,7 @@ protected override ShapedQueryExpression TransformJsonQueryToTable(JsonQueryExpr
341315
}
342316
}
343317

344-
// Use JSON_EXTRACT to get the nested JSON document (not JSON_VALUE which extracts scalars)
318+
// Use JSON_EXTRACT to get the nested JSON document with structure intact
345319
jsonSource = _sqlExpressionFactory.Function(
346320
"JSON_EXTRACT",
347321
[jsonQueryExpression.JsonColumn, _sqlExpressionFactory.Constant(pathBuilder.ToString())],
@@ -386,7 +360,6 @@ [new PathSegment(_sqlExpressionFactory.Constant("*", RelationalTypeMapping.NullM
386360
new ProjectionMember(),
387361
typeof(ValueBuffer)),
388362
false));
389-
*/
390363
}
391364

392365
protected override ShapedQueryExpression TranslatePrimitiveCollection(SqlExpression sqlExpression, IProperty property, string tableAlias)

0 commit comments

Comments
 (0)