1
1
// Licensed to the .NET Foundation under one or more agreements.
2
2
// The .NET Foundation licenses this file to you under the MIT license.
3
3
4
- using System . Runtime . CompilerServices ;
5
4
using Microsoft . EntityFrameworkCore . Query . SqlExpressions ;
6
5
using Microsoft . EntityFrameworkCore . Storage . Internal ;
7
6
@@ -169,21 +168,22 @@ protected override Expression VisitSqlFragment(SqlFragmentExpression sqlFragment
169
168
}
170
169
171
170
private static bool IsNonComposedSetOperation ( SelectExpression selectExpression )
172
- => selectExpression . Offset == null
173
- && selectExpression . Limit == null
174
- && ! selectExpression . IsDistinct
175
- && selectExpression . Predicate == null
176
- && selectExpression . Having == null
177
- && selectExpression . Orderings . Count == 0
178
- && selectExpression . GroupBy . Count == 0
179
- && selectExpression . Tables . Count == 1
180
- && selectExpression . Tables [ 0 ] is SetOperationBase setOperation
171
+ => selectExpression is
172
+ {
173
+ Tables : [ SetOperationBase setOperation ] ,
174
+ Offset : null ,
175
+ Limit : null ,
176
+ IsDistinct : false ,
177
+ Predicate : null ,
178
+ Having : null ,
179
+ Orderings . Count : 0 ,
180
+ GroupBy . Count : 0
181
+ }
181
182
&& selectExpression . Projection . Count == setOperation . Source1 . Projection . Count
182
183
&& selectExpression . Projection . Select (
183
184
( pe , index ) => pe . Expression is ColumnExpression column
184
- && string . Equals ( column . TableAlias , setOperation . Alias , StringComparison . Ordinal )
185
- && string . Equals (
186
- column . Name , setOperation . Source1 . Projection [ index ] . Alias , StringComparison . Ordinal ) )
185
+ && column . TableAlias == setOperation . Alias
186
+ && column . Name == setOperation . Source1 . Projection [ index ] . Alias )
187
187
. All ( e => e ) ;
188
188
189
189
/// <inheritdoc />
@@ -226,12 +226,7 @@ protected override Expression VisitSelect(SelectExpression selectExpression)
226
226
subQueryIndent = _relationalCommandBuilder . Indent ( ) ;
227
227
}
228
228
229
- if ( IsNonComposedSetOperation ( selectExpression ) )
230
- {
231
- // Naked set operation
232
- GenerateSetOperation ( ( SetOperationBase ) selectExpression . Tables [ 0 ] ) ;
233
- }
234
- else
229
+ if ( ! TryGenerateWithoutWrappingSelect ( selectExpression ) )
235
230
{
236
231
_relationalCommandBuilder . Append ( "SELECT " ) ;
237
232
@@ -300,6 +295,43 @@ protected override Expression VisitSelect(SelectExpression selectExpression)
300
295
return selectExpression ;
301
296
}
302
297
298
+ /// <summary>
299
+ /// If possible, generates the expression contained within the provided <paramref name="selectExpression" /> without the wrapping
300
+ /// SELECT. This can be done for set operations and VALUES, which can appear as top-level statements without needing to be wrapped
301
+ /// in SELECT.
302
+ /// </summary>
303
+ protected virtual bool TryGenerateWithoutWrappingSelect ( SelectExpression selectExpression )
304
+ {
305
+ if ( IsNonComposedSetOperation ( selectExpression ) )
306
+ {
307
+ GenerateSetOperation ( ( SetOperationBase ) selectExpression . Tables [ 0 ] ) ;
308
+ return true ;
309
+ }
310
+
311
+ if ( selectExpression is
312
+ {
313
+ Tables : [ ValuesExpression valuesExpression ] ,
314
+ Offset : null ,
315
+ Limit : null ,
316
+ IsDistinct : false ,
317
+ Predicate : null ,
318
+ Having : null ,
319
+ Orderings . Count : 0 ,
320
+ GroupBy . Count : 0 ,
321
+ }
322
+ && selectExpression . Projection . Count == valuesExpression . ColumnNames . Count
323
+ && selectExpression . Projection . Select (
324
+ ( pe , index ) => pe . Expression is ColumnExpression column
325
+ && column . Name == valuesExpression . ColumnNames [ index ] )
326
+ . All ( e => e ) )
327
+ {
328
+ GenerateValues ( valuesExpression ) ;
329
+ return true ;
330
+ }
331
+
332
+ return false ;
333
+ }
334
+
303
335
/// <summary>
304
336
/// Generates a pseudo FROM clause. Required by some providers when a query has no actual FROM clause.
305
337
/// </summary>
@@ -312,9 +344,7 @@ protected virtual void GeneratePseudoFromClause()
312
344
/// </summary>
313
345
/// <param name="selectExpression">SelectExpression for which the empty projection will be generated.</param>
314
346
protected virtual void GenerateEmptyProjection ( SelectExpression selectExpression )
315
- {
316
- _relationalCommandBuilder . Append ( "1" ) ;
317
- }
347
+ => _relationalCommandBuilder . Append ( "1" ) ;
318
348
319
349
/// <inheritdoc />
320
350
protected override Expression VisitProjection ( ProjectionExpression projectionExpression )
@@ -371,16 +401,16 @@ protected override Expression VisitSqlFunction(SqlFunctionExpression sqlFunction
371
401
/// <inheritdoc />
372
402
protected override Expression VisitTableValuedFunction ( TableValuedFunctionExpression tableValuedFunctionExpression )
373
403
{
374
- if ( ! string . IsNullOrEmpty ( tableValuedFunctionExpression . StoreFunction . Schema ) )
404
+ if ( ! string . IsNullOrEmpty ( tableValuedFunctionExpression . Schema ) )
375
405
{
376
406
_relationalCommandBuilder
377
- . Append ( _sqlGenerationHelper . DelimitIdentifier ( tableValuedFunctionExpression . StoreFunction . Schema ) )
407
+ . Append ( _sqlGenerationHelper . DelimitIdentifier ( tableValuedFunctionExpression . Schema ) )
378
408
. Append ( "." ) ;
379
409
}
380
410
381
- var name = tableValuedFunctionExpression . StoreFunction . IsBuiltIn
382
- ? tableValuedFunctionExpression . StoreFunction . Name
383
- : _sqlGenerationHelper . DelimitIdentifier ( tableValuedFunctionExpression . StoreFunction . Name ) ;
411
+ var name = tableValuedFunctionExpression . IsBuiltIn
412
+ ? tableValuedFunctionExpression . Name
413
+ : _sqlGenerationHelper . DelimitIdentifier ( tableValuedFunctionExpression . Name ) ;
384
414
385
415
_relationalCommandBuilder
386
416
. Append ( name )
@@ -607,6 +637,7 @@ protected override Expression VisitSqlParameter(SqlParameterExpression sqlParame
607
637
{
608
638
var invariantName = sqlParameterExpression . Name ;
609
639
var parameterName = sqlParameterExpression . Name ;
640
+ var typeMapping = sqlParameterExpression . TypeMapping ! ;
610
641
611
642
// Try to see if a parameter already exists - if so, just integrate the same placeholder into the SQL instead of sending the same
612
643
// data twice.
@@ -615,11 +646,10 @@ protected override Expression VisitSqlParameter(SqlParameterExpression sqlParame
615
646
var parameter = _relationalCommandBuilder . Parameters . FirstOrDefault (
616
647
p =>
617
648
p . InvariantName == parameterName
618
- && p is TypeMappedRelationalParameter typeMappedRelationalParameter
619
- && string . Equals (
620
- typeMappedRelationalParameter . RelationalTypeMapping . StoreType , sqlParameterExpression . TypeMapping ! . StoreType ,
621
- StringComparison . OrdinalIgnoreCase )
622
- && typeMappedRelationalParameter . RelationalTypeMapping . Converter == sqlParameterExpression . TypeMapping ! . Converter ) ;
649
+ && p is TypeMappedRelationalParameter { RelationalTypeMapping : var existingTypeMapping }
650
+ && string . Equals ( existingTypeMapping . StoreType , typeMapping . StoreType , StringComparison . OrdinalIgnoreCase )
651
+ && ( existingTypeMapping . Converter is null && typeMapping . Converter is null
652
+ || existingTypeMapping . Converter is not null && existingTypeMapping . Converter . Equals ( typeMapping . Converter ) ) ) ;
623
653
624
654
if ( parameter is null )
625
655
{
@@ -1132,6 +1162,28 @@ protected override Expression VisitRowNumber(RowNumberExpression rowNumberExpres
1132
1162
return rowNumberExpression ;
1133
1163
}
1134
1164
1165
+ /// <inheritdoc />
1166
+ protected override Expression VisitRowValue ( RowValueExpression rowValueExpression )
1167
+ {
1168
+ Sql . Append ( "(" ) ;
1169
+
1170
+ var values = rowValueExpression . Values ;
1171
+ var count = values . Count ;
1172
+ for ( var i = 0 ; i < count ; i ++ )
1173
+ {
1174
+ if ( i > 0 )
1175
+ {
1176
+ Sql . Append ( ", " ) ;
1177
+ }
1178
+
1179
+ Visit ( values [ i ] ) ;
1180
+ }
1181
+
1182
+ Sql . Append ( ")" ) ;
1183
+
1184
+ return rowValueExpression ;
1185
+ }
1186
+
1135
1187
/// <summary>
1136
1188
/// Generates a set operation in the relational command.
1137
1189
/// </summary>
@@ -1141,18 +1193,16 @@ protected virtual void GenerateSetOperation(SetOperationBase setOperation)
1141
1193
GenerateSetOperationOperand ( setOperation , setOperation . Source1 ) ;
1142
1194
_relationalCommandBuilder
1143
1195
. AppendLine ( )
1144
- . Append ( GetSetOperation ( setOperation ) )
1196
+ . Append (
1197
+ setOperation switch
1198
+ {
1199
+ ExceptExpression => "EXCEPT" ,
1200
+ IntersectExpression => "INTERSECT" ,
1201
+ UnionExpression => "UNION" ,
1202
+ _ => throw new InvalidOperationException ( CoreStrings . UnknownEntity ( "SetOperationType" ) )
1203
+ } )
1145
1204
. AppendLine ( setOperation . IsDistinct ? string . Empty : " ALL" ) ;
1146
1205
GenerateSetOperationOperand ( setOperation , setOperation . Source2 ) ;
1147
-
1148
- static string GetSetOperation ( SetOperationBase operation )
1149
- => operation switch
1150
- {
1151
- ExceptExpression => "EXCEPT" ,
1152
- IntersectExpression => "INTERSECT" ,
1153
- UnionExpression => "UNION" ,
1154
- _ => throw new InvalidOperationException ( CoreStrings . UnknownEntity ( "SetOperationType" ) )
1155
- } ;
1156
1206
}
1157
1207
1158
1208
/// <summary>
@@ -1311,6 +1361,66 @@ void LiftPredicate(TableExpressionBase joinTable)
1311
1361
RelationalStrings . ExecuteOperationWithUnsupportedOperatorInSqlGeneration ( nameof ( RelationalQueryableExtensions . ExecuteUpdate ) ) ) ;
1312
1362
}
1313
1363
1364
+ /// <inheritdoc />
1365
+ protected override Expression VisitValues ( ValuesExpression valuesExpression )
1366
+ {
1367
+ _relationalCommandBuilder . Append ( "(" ) ;
1368
+
1369
+ GenerateValues ( valuesExpression ) ;
1370
+
1371
+ _relationalCommandBuilder
1372
+ . Append ( ")" )
1373
+ . Append ( AliasSeparator )
1374
+ . Append ( _sqlGenerationHelper . DelimitIdentifier ( valuesExpression . Alias ) ) ;
1375
+
1376
+ return valuesExpression ;
1377
+ }
1378
+
1379
+ /// <summary>
1380
+ /// Generates a VALUES expression.
1381
+ /// </summary>
1382
+ protected virtual void GenerateValues ( ValuesExpression valuesExpression )
1383
+ {
1384
+ var rowValues = valuesExpression . RowValues ;
1385
+
1386
+ // Some databases support providing the names of columns projected out of VALUES, e.g.
1387
+ // SQL Server/PG: (VALUES (1, 3), (2, 4)) AS x(a, b). Others unfortunately don't; so by default, we extract out the first row,
1388
+ // and generate a SELECT for it with the names, and a UNION ALL over the rest of the values.
1389
+ _relationalCommandBuilder . Append ( "SELECT " ) ;
1390
+
1391
+ Check . DebugAssert ( rowValues . Count > 0 , "rowValues.Count > 0" ) ;
1392
+ var firstRowValues = rowValues [ 0 ] . Values ;
1393
+ for ( var i = 0 ; i < firstRowValues . Count ; i ++ )
1394
+ {
1395
+ if ( i > 0 )
1396
+ {
1397
+ _relationalCommandBuilder . Append ( ", " ) ;
1398
+ }
1399
+
1400
+ Visit ( firstRowValues [ i ] ) ;
1401
+
1402
+ _relationalCommandBuilder
1403
+ . Append ( AliasSeparator )
1404
+ . Append ( _sqlGenerationHelper . DelimitIdentifier ( valuesExpression . ColumnNames [ i ] ) ) ;
1405
+ }
1406
+
1407
+ if ( rowValues . Count > 1 )
1408
+ {
1409
+ _relationalCommandBuilder . Append ( " UNION ALL VALUES " ) ;
1410
+
1411
+ for ( var i = 1 ; i < rowValues . Count ; i ++ )
1412
+ {
1413
+ // TODO: Do we want newlines here?
1414
+ if ( i > 1 )
1415
+ {
1416
+ _relationalCommandBuilder . Append ( ", " ) ;
1417
+ }
1418
+
1419
+ Visit ( valuesExpression . RowValues [ i ] ) ;
1420
+ }
1421
+ }
1422
+ }
1423
+
1314
1424
/// <inheritdoc />
1315
1425
protected override Expression VisitJsonScalar ( JsonScalarExpression jsonScalarExpression )
1316
1426
=> throw new InvalidOperationException (
0 commit comments