@@ -214,9 +214,140 @@ protected override ShapedQueryExpression TranslateElementAtOrDefault(
214214
215215 protected override ShapedQueryExpression TransformJsonQueryToTable ( JsonQueryExpression jsonQueryExpression )
216216 {
217- // Call the base implementation which will handle the transformation using the default approach
218- // This should work now that we have proper JSON_TABLE support
219- return base . TransformJsonQueryToTable ( jsonQueryExpression ) ;
217+ // Calculate the table alias for the JSON_TABLE function based on the last named path segment
218+ // (or the JSON column name if there are none)
219+ var lastNamedPathSegment = jsonQueryExpression . Path . LastOrDefault ( ps => ps . PropertyName is not null ) ;
220+ var tableAlias = _sqlAliasManager . GenerateTableAlias (
221+ lastNamedPathSegment . PropertyName ?? jsonQueryExpression . JsonColumn . Name ) ;
222+
223+ var jsonTypeMapping = jsonQueryExpression . JsonColumn . TypeMapping ! ;
224+
225+ // We now add all of the projected entity's properties and navigations into the JSON_TABLE's COLUMNS clause
226+ var columnInfos = new List < MySqlJsonTableExpression . ColumnInfo > ( ) ;
227+
228+ // We're only interested in properties which actually exist in the JSON, filter out uninteresting shadow keys
229+ foreach ( var property in jsonQueryExpression . StructuralType . GetPropertiesInHierarchy ( ) )
230+ {
231+ if ( property . GetJsonPropertyName ( ) is string jsonPropertyName )
232+ {
233+ columnInfos . Add (
234+ new MySqlJsonTableExpression . ColumnInfo (
235+ Name : jsonPropertyName ,
236+ TypeMapping : property . GetRelationalTypeMapping ( ) ,
237+ // Path for JSON_TABLE: $[0] to access array element properties
238+ Path : [ new PathSegment ( _sqlExpressionFactory . Constant ( 0 , _typeMappingSource . FindMapping ( typeof ( int ) ) ) ) ] ,
239+ AsJson : false ) ) ;
240+ }
241+ }
242+
243+ // Add navigations to owned entities mapped to JSON
244+ switch ( jsonQueryExpression . StructuralType )
245+ {
246+ case IEntityType entityType :
247+ foreach ( var navigation in entityType . GetNavigationsInHierarchy ( )
248+ . Where ( n => n . ForeignKey . IsOwnership
249+ && n . TargetEntityType . IsMappedToJson ( )
250+ && n . ForeignKey . PrincipalToDependent == n ) )
251+ {
252+ var jsonNavigationName = navigation . TargetEntityType . GetJsonPropertyName ( ) ;
253+ Check . DebugAssert ( jsonNavigationName is not null , $ "No JSON property name for navigation { navigation . Name } ") ;
254+
255+ columnInfos . Add (
256+ new MySqlJsonTableExpression . ColumnInfo (
257+ Name : jsonNavigationName ,
258+ TypeMapping : jsonTypeMapping ,
259+ Path : [ new PathSegment ( _sqlExpressionFactory . Constant ( 0 , _typeMappingSource . FindMapping ( typeof ( int ) ) ) ) ] ,
260+ AsJson : true ) ) ;
261+ }
262+ break ;
263+
264+ case IComplexType complexType :
265+ foreach ( var complexProperty in complexType . GetComplexProperties ( ) )
266+ {
267+ var jsonPropertyName = complexProperty . ComplexType . GetJsonPropertyName ( ) ;
268+ Check . DebugAssert ( jsonPropertyName is not null , $ "No JSON property name for complex property { complexProperty . Name } ") ;
269+
270+ columnInfos . Add (
271+ new MySqlJsonTableExpression . ColumnInfo (
272+ Name : jsonPropertyName ,
273+ TypeMapping : jsonTypeMapping ,
274+ Path : [ new PathSegment ( _sqlExpressionFactory . Constant ( 0 , _typeMappingSource . FindMapping ( typeof ( int ) ) ) ) ] ,
275+ AsJson : true ) ) ;
276+ }
277+ break ;
278+
279+ default :
280+ throw new UnreachableException ( ) ;
281+ }
282+
283+ // For MySQL JSON_TABLE, we need to extract the nested JSON document using JSON_EXTRACT
284+ // Build the full path by combining the JSON column path with the query expression path
285+ var fullPath = jsonQueryExpression . Path . ToList ( ) ;
286+
287+ // Create a JSON_EXTRACT expression to get the nested JSON document
288+ // JSON_EXTRACT returns the JSON fragment as-is (with quotes for strings)
289+ SqlExpression jsonSource ;
290+ if ( fullPath . Count > 0 )
291+ {
292+ // Build JSON path string like $.path.to.array
293+ var pathBuilder = new System . Text . StringBuilder ( "$" ) ;
294+ foreach ( var segment in fullPath )
295+ {
296+ if ( segment . PropertyName is not null )
297+ {
298+ pathBuilder . Append ( '.' ) . Append ( segment . PropertyName ) ;
299+ }
300+ else if ( segment . ArrayIndex is SqlConstantExpression { Value : int index } )
301+ {
302+ pathBuilder . Append ( '[' ) . Append ( index ) . Append ( ']' ) ;
303+ }
304+ }
305+
306+ // Use JSON_EXTRACT to get the nested JSON document
307+ jsonSource = _sqlExpressionFactory . Function (
308+ "JSON_EXTRACT" ,
309+ [ jsonQueryExpression . JsonColumn , _sqlExpressionFactory . Constant ( pathBuilder . ToString ( ) ) ] ,
310+ nullable : true ,
311+ argumentsPropagateNullability : [ true , true ] ,
312+ typeof ( string ) ,
313+ jsonTypeMapping ) ;
314+ }
315+ else
316+ {
317+ // No path, use the JSON column directly
318+ jsonSource = jsonQueryExpression . JsonColumn ;
319+ }
320+
321+ // Construct the JSON_TABLE expression with column definitions
322+ var jsonTableExpression = new MySqlJsonTableExpression (
323+ tableAlias ,
324+ jsonSource ,
325+ // Path to iterate over array elements: $[*]
326+ [ new PathSegment ( _sqlExpressionFactory . Constant ( "*" , RelationalTypeMapping . NullMapping ) ) ] ,
327+ [ .. columnInfos ] ) ;
328+
329+ // MySQL JSON_TABLE returns a 'key' column for array ordering (similar to PostgreSQL's ordinality)
330+ var keyColumnTypeMapping = _typeMappingSource . FindMapping ( typeof ( uint ) ) ! ;
331+
332+ #pragma warning disable EF1001 // Internal EF Core API usage.
333+ // Use CreateSelect helper method (from base class) to create the SelectExpression
334+ var selectExpression = CreateSelect (
335+ jsonQueryExpression ,
336+ jsonTableExpression ,
337+ "key" ,
338+ typeof ( uint ) ,
339+ keyColumnTypeMapping ) ;
340+ #pragma warning restore EF1001 // Internal EF Core API usage.
341+
342+ return new ShapedQueryExpression (
343+ selectExpression ,
344+ new RelationalStructuralTypeShaperExpression (
345+ jsonQueryExpression . StructuralType ,
346+ new ProjectionBindingExpression (
347+ selectExpression ,
348+ new ProjectionMember ( ) ,
349+ typeof ( ValueBuffer ) ) ,
350+ false ) ) ;
220351 }
221352
222353 protected override ShapedQueryExpression TranslatePrimitiveCollection ( SqlExpression sqlExpression , IProperty property , string tableAlias )
0 commit comments