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,15 +168,17 @@ 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
@@ -226,12 +227,7 @@ protected override Expression VisitSelect(SelectExpression selectExpression)
226
227
subQueryIndent = _relationalCommandBuilder . Indent ( ) ;
227
228
}
228
229
229
- if ( IsNonComposedSetOperation ( selectExpression ) )
230
- {
231
- // Naked set operation
232
- GenerateSetOperation ( ( SetOperationBase ) selectExpression . Tables [ 0 ] ) ;
233
- }
234
- else
230
+ if ( ! TryGenerateWithoutWrappingSelect ( selectExpression ) )
235
231
{
236
232
_relationalCommandBuilder . Append ( "SELECT " ) ;
237
233
@@ -300,6 +296,39 @@ protected override Expression VisitSelect(SelectExpression selectExpression)
300
296
return selectExpression ;
301
297
}
302
298
299
+ /// <summary>
300
+ /// If possible, generates the expression contained within the provided <paramref name="selectExpression" /> without the wrapping
301
+ /// SELECT. This can be done for set operations and VALUES, which can appear as top-level statements without needing to be wrapped
302
+ /// in SELECT.
303
+ /// </summary>
304
+ protected virtual bool TryGenerateWithoutWrappingSelect ( SelectExpression selectExpression )
305
+ {
306
+ if ( IsNonComposedSetOperation ( selectExpression ) )
307
+ {
308
+ // Naked set operation
309
+ GenerateSetOperation ( ( SetOperationBase ) selectExpression . Tables [ 0 ] ) ;
310
+ return true ;
311
+ }
312
+
313
+ if ( selectExpression is
314
+ {
315
+ Tables : [ ValuesExpression valuesExpression ] ,
316
+ Offset : null ,
317
+ Limit : null ,
318
+ IsDistinct : false ,
319
+ Predicate : null ,
320
+ Having : null ,
321
+ Orderings . Count : 0 ,
322
+ GroupBy . Count : 0 ,
323
+ } )
324
+ {
325
+ GenerateValues ( valuesExpression , withParentheses : false ) ;
326
+ return true ;
327
+ }
328
+
329
+ return false ;
330
+ }
331
+
303
332
/// <summary>
304
333
/// Generates a pseudo FROM clause. Required by some providers when a query has no actual FROM clause.
305
334
/// </summary>
@@ -312,9 +341,7 @@ protected virtual void GeneratePseudoFromClause()
312
341
/// </summary>
313
342
/// <param name="selectExpression">SelectExpression for which the empty projection will be generated.</param>
314
343
protected virtual void GenerateEmptyProjection ( SelectExpression selectExpression )
315
- {
316
- _relationalCommandBuilder . Append ( "1" ) ;
317
- }
344
+ => _relationalCommandBuilder . Append ( "1" ) ;
318
345
319
346
/// <inheritdoc />
320
347
protected override Expression VisitProjection ( ProjectionExpression projectionExpression )
@@ -371,16 +398,16 @@ protected override Expression VisitSqlFunction(SqlFunctionExpression sqlFunction
371
398
/// <inheritdoc />
372
399
protected override Expression VisitTableValuedFunction ( TableValuedFunctionExpression tableValuedFunctionExpression )
373
400
{
374
- if ( ! string . IsNullOrEmpty ( tableValuedFunctionExpression . StoreFunction . Schema ) )
401
+ if ( ! string . IsNullOrEmpty ( tableValuedFunctionExpression . Schema ) )
375
402
{
376
403
_relationalCommandBuilder
377
- . Append ( _sqlGenerationHelper . DelimitIdentifier ( tableValuedFunctionExpression . StoreFunction . Schema ) )
404
+ . Append ( _sqlGenerationHelper . DelimitIdentifier ( tableValuedFunctionExpression . Schema ) )
378
405
. Append ( "." ) ;
379
406
}
380
407
381
- var name = tableValuedFunctionExpression . StoreFunction . IsBuiltIn
382
- ? tableValuedFunctionExpression . StoreFunction . Name
383
- : _sqlGenerationHelper . DelimitIdentifier ( tableValuedFunctionExpression . StoreFunction . Name ) ;
408
+ var name = tableValuedFunctionExpression . IsBuiltIn
409
+ ? tableValuedFunctionExpression . Name
410
+ : _sqlGenerationHelper . DelimitIdentifier ( tableValuedFunctionExpression . Name ) ;
384
411
385
412
_relationalCommandBuilder
386
413
. Append ( name )
@@ -1132,6 +1159,28 @@ protected override Expression VisitRowNumber(RowNumberExpression rowNumberExpres
1132
1159
return rowNumberExpression ;
1133
1160
}
1134
1161
1162
+ /// <inheritdoc />
1163
+ protected override Expression VisitRowValue ( RowValueExpression rowValueExpression )
1164
+ {
1165
+ Sql . Append ( "(" ) ;
1166
+
1167
+ var values = rowValueExpression . Values ;
1168
+ var count = values . Count ;
1169
+ for ( var i = 0 ; i < count ; i ++ )
1170
+ {
1171
+ if ( i > 0 )
1172
+ {
1173
+ Sql . Append ( ", " ) ;
1174
+ }
1175
+
1176
+ Visit ( values [ i ] ) ;
1177
+ }
1178
+
1179
+ Sql . Append ( ")" ) ;
1180
+
1181
+ return rowValueExpression ;
1182
+ }
1183
+
1135
1184
/// <summary>
1136
1185
/// Generates a set operation in the relational command.
1137
1186
/// </summary>
@@ -1141,18 +1190,16 @@ protected virtual void GenerateSetOperation(SetOperationBase setOperation)
1141
1190
GenerateSetOperationOperand ( setOperation , setOperation . Source1 ) ;
1142
1191
_relationalCommandBuilder
1143
1192
. AppendLine ( )
1144
- . Append ( GetSetOperation ( setOperation ) )
1193
+ . Append (
1194
+ setOperation switch
1195
+ {
1196
+ ExceptExpression => "EXCEPT" ,
1197
+ IntersectExpression => "INTERSECT" ,
1198
+ UnionExpression => "UNION" ,
1199
+ _ => throw new InvalidOperationException ( CoreStrings . UnknownEntity ( "SetOperationType" ) )
1200
+ } )
1145
1201
. AppendLine ( setOperation . IsDistinct ? string . Empty : " ALL" ) ;
1146
1202
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
1203
}
1157
1204
1158
1205
/// <summary>
@@ -1311,6 +1358,73 @@ void LiftPredicate(TableExpressionBase joinTable)
1311
1358
RelationalStrings . ExecuteOperationWithUnsupportedOperatorInSqlGeneration ( nameof ( RelationalQueryableExtensions . ExecuteUpdate ) ) ) ;
1312
1359
}
1313
1360
1361
+ /// <inheritdoc />
1362
+ protected override Expression VisitValues ( ValuesExpression valuesExpression )
1363
+ {
1364
+ GenerateValues ( valuesExpression , withParentheses : true ) ;
1365
+
1366
+ return valuesExpression ;
1367
+ }
1368
+
1369
+ /// <summary>
1370
+ /// Generates a VALUES expression.
1371
+ /// </summary>
1372
+ protected virtual void GenerateValues ( ValuesExpression valuesExpression , bool withParentheses = true )
1373
+ {
1374
+ // TODO: Review this!
1375
+ if ( withParentheses && valuesExpression . Alias is not null )
1376
+ {
1377
+ _relationalCommandBuilder . Append ( "(" ) ;
1378
+ }
1379
+
1380
+ var rowValues = valuesExpression . RowValues ;
1381
+
1382
+ // Some databases support providing the names of columns projected out of VALUES, e.g.
1383
+ // 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,
1384
+ // and generate a SELECT for it with the names, and a UNION ALL over the rest of the values.
1385
+ _relationalCommandBuilder . Append ( "SELECT " ) ;
1386
+
1387
+ Check . DebugAssert ( rowValues . Count > 0 , "rowValues.Count > 0" ) ;
1388
+ var firstRowValues = rowValues [ 0 ] . Values ;
1389
+ for ( var i = 0 ; i < firstRowValues . Count ; i ++ )
1390
+ {
1391
+ if ( i > 0 )
1392
+ {
1393
+ _relationalCommandBuilder . Append ( ", " ) ;
1394
+ }
1395
+
1396
+ Visit ( firstRowValues [ i ] ) ;
1397
+
1398
+ _relationalCommandBuilder
1399
+ . Append ( AliasSeparator )
1400
+ . Append ( valuesExpression . ColumnNames [ i ] ) ;
1401
+ }
1402
+
1403
+ if ( rowValues . Count > 1 )
1404
+ {
1405
+ _relationalCommandBuilder . Append ( " UNION ALL VALUES " ) ;
1406
+
1407
+ for ( var i = 1 ; i < rowValues . Count ; i ++ )
1408
+ {
1409
+ // TODO: Do we want newlines here?
1410
+ if ( i > 1 )
1411
+ {
1412
+ _relationalCommandBuilder . Append ( ", " ) ;
1413
+ }
1414
+
1415
+ Visit ( valuesExpression . RowValues [ i ] ) ;
1416
+ }
1417
+ }
1418
+
1419
+ if ( withParentheses && valuesExpression . Alias is not null )
1420
+ {
1421
+ _relationalCommandBuilder
1422
+ . Append ( ")" )
1423
+ . Append ( AliasSeparator )
1424
+ . Append ( _sqlGenerationHelper . DelimitIdentifier ( valuesExpression . Alias ) ) ;
1425
+ }
1426
+ }
1427
+
1314
1428
/// <inheritdoc />
1315
1429
protected override Expression VisitJsonScalar ( JsonScalarExpression jsonScalarExpression )
1316
1430
=> throw new InvalidOperationException (
0 commit comments