@@ -297,20 +297,8 @@ bool TryTranslateStartsEndsWithContains(
297
297
// (but SqlNullabilityProcess will convert this to a true constant if the instance is non-nullable)
298
298
"" => _sqlExpressionFactory . Like ( translatedInstance , _sqlExpressionFactory . Constant ( "%" ) ) ,
299
299
300
- string s => s . Any ( IsLikeWildChar )
301
- ? _sqlExpressionFactory . Like (
302
- translatedInstance ,
303
- _sqlExpressionFactory . Constant (
304
- methodType switch
305
- {
306
- StartsEndsWithContains . StartsWith => EscapeLikePattern ( s ) + '%' ,
307
- StartsEndsWithContains . EndsWith => '%' + EscapeLikePattern ( s ) ,
308
- StartsEndsWithContains . Contains => $ "%{ EscapeLikePattern ( s ) } %",
309
-
310
- _ => throw new ArgumentOutOfRangeException ( nameof ( methodType ) , methodType , null )
311
- } ) ,
312
- _sqlExpressionFactory . Constant ( LikeEscapeString ) )
313
- : _sqlExpressionFactory . Like (
300
+ string s when ! s . Any ( IsLikeWildChar )
301
+ => _sqlExpressionFactory . Like (
314
302
translatedInstance ,
315
303
_sqlExpressionFactory . Constant (
316
304
methodType switch
@@ -322,14 +310,35 @@ bool TryTranslateStartsEndsWithContains(
322
310
_ => throw new ArgumentOutOfRangeException ( nameof ( methodType ) , methodType , null )
323
311
} ) ) ,
324
312
313
+ // Azure Synapse does not support ESCAPE clause in LIKE
314
+ // fallback to translation like with column/expression
315
+ string s when _sqlServerSingletonOptions . EngineType == SqlServerEngineType . AzureSynapse
316
+ => TranslateWithoutLike ( patternIsNonEmptyConstantString : true ) ,
317
+
318
+ string s => _sqlExpressionFactory . Like (
319
+ translatedInstance ,
320
+ _sqlExpressionFactory . Constant (
321
+ methodType switch
322
+ {
323
+ StartsEndsWithContains . StartsWith => EscapeLikePattern ( s ) + '%' ,
324
+ StartsEndsWithContains . EndsWith => '%' + EscapeLikePattern ( s ) ,
325
+ StartsEndsWithContains . Contains => $ "%{ EscapeLikePattern ( s ) } %",
326
+
327
+ _ => throw new ArgumentOutOfRangeException ( nameof ( methodType ) , methodType , null )
328
+ } ) ,
329
+ _sqlExpressionFactory . Constant ( LikeEscapeString ) ) ,
330
+
325
331
_ => throw new UnreachableException ( )
326
332
} ;
327
333
328
334
return true ;
329
335
}
330
336
331
337
case SqlParameterExpression patternParameter
332
- when patternParameter . Name . StartsWith ( QueryCompilationContext . QueryParameterPrefix , StringComparison . Ordinal ) :
338
+ when patternParameter . Name . StartsWith ( QueryCompilationContext . QueryParameterPrefix , StringComparison . Ordinal )
339
+ // Azure Synapse does not support ESCAPE clause in LIKE
340
+ // fall through to translation like with column/expression
341
+ && _sqlServerSingletonOptions . EngineType != SqlServerEngineType . AzureSynapse :
333
342
{
334
343
// The pattern is a parameter, register a runtime parameter that will contain the rewritten LIKE pattern, where
335
344
// all special characters have been escaped.
@@ -356,61 +365,74 @@ when patternParameter.Name.StartsWith(QueryCompilationContext.QueryParameterPref
356
365
default :
357
366
// The pattern is a column or a complex expression; the possible special characters in the pattern cannot be escaped,
358
367
// preventing us from translating to LIKE.
359
- translation = methodType switch
360
- {
361
- // For StartsWith/EndsWith, use LEFT or RIGHT instead to extract substring and compare:
362
- // WHERE instance IS NOT NULL AND pattern IS NOT NULL AND LEFT(instance, LEN(pattern)) = pattern
363
- // This is less efficient than LIKE (i.e. StartsWith does an index scan instead of seek), but we have no choice.
364
- // Note that we compensate for the case where both the instance and the pattern are null (null.StartsWith(null)); a
365
- // simple equality would yield true in that case, but we want false. We technically
366
- StartsEndsWithContains . StartsWith or StartsEndsWithContains . EndsWith
367
- => _sqlExpressionFactory . AndAlso (
368
- _sqlExpressionFactory . IsNotNull ( translatedInstance ) ,
369
- _sqlExpressionFactory . AndAlso (
370
- _sqlExpressionFactory . IsNotNull ( translatedPattern ) ,
371
- _sqlExpressionFactory . Equal (
372
- _sqlExpressionFactory . Function (
373
- methodType is StartsEndsWithContains . StartsWith ? "LEFT" : "RIGHT" ,
374
- new [ ]
375
- {
368
+ translation = TranslateWithoutLike ( ) ;
369
+ return true ;
370
+ }
371
+
372
+ SqlExpression TranslateWithoutLike ( bool patternIsNonEmptyConstantString = false )
373
+ {
374
+ return methodType switch
375
+ {
376
+ // For StartsWith/EndsWith, use LEFT or RIGHT instead to extract substring and compare:
377
+ // WHERE instance IS NOT NULL AND pattern IS NOT NULL AND LEFT(instance, LEN(pattern)) = pattern
378
+ // This is less efficient than LIKE (i.e. StartsWith does an index scan instead of seek), but we have no choice.
379
+ // Note that we compensate for the case where both the instance and the pattern are null (null.StartsWith(null)); a
380
+ // simple equality would yield true in that case, but we want false. We technically
381
+ StartsEndsWithContains . StartsWith or StartsEndsWithContains . EndsWith
382
+ => _sqlExpressionFactory . AndAlso (
383
+ _sqlExpressionFactory . IsNotNull ( translatedInstance ) ,
384
+ _sqlExpressionFactory . AndAlso (
385
+ _sqlExpressionFactory . IsNotNull ( translatedPattern ) ,
386
+ _sqlExpressionFactory . Equal (
387
+ _sqlExpressionFactory . Function (
388
+ methodType is StartsEndsWithContains . StartsWith ? "LEFT" : "RIGHT" ,
389
+ new [ ]
390
+ {
376
391
translatedInstance ,
377
392
_sqlExpressionFactory . Function (
378
393
"LEN" ,
379
394
new [ ] { translatedPattern } ,
380
395
nullable : true ,
381
396
argumentsPropagateNullability : new [ ] { true } ,
382
397
typeof ( int ) )
383
- } ,
384
- nullable : true ,
385
- argumentsPropagateNullability : new [ ] { true , true } ,
386
- typeof ( string ) ,
387
- stringTypeMapping ) ,
388
- translatedPattern ) ) ) ,
389
-
390
- // For Contains, just use CHARINDEX and check if the result is greater than 0.
391
- // Add a check to return null when the pattern is an empty string (and the string isn't null)
392
- StartsEndsWithContains . Contains
393
- => _sqlExpressionFactory . AndAlso (
394
- _sqlExpressionFactory . IsNotNull ( translatedInstance ) ,
395
- _sqlExpressionFactory . AndAlso (
396
- _sqlExpressionFactory . IsNotNull ( translatedPattern ) ,
397
- _sqlExpressionFactory . OrElse (
398
- _sqlExpressionFactory . GreaterThan (
399
- _sqlExpressionFactory . Function (
400
- "CHARINDEX" ,
401
- new [ ] { translatedPattern , translatedInstance } ,
402
- nullable : true ,
403
- argumentsPropagateNullability : new [ ] { true , true } ,
404
- typeof ( int ) ) ,
405
- _sqlExpressionFactory . Constant ( 0 ) ) ,
406
- _sqlExpressionFactory . Like (
407
- translatedPattern ,
408
- _sqlExpressionFactory . Constant ( string . Empty , stringTypeMapping ) ) ) ) ) ,
409
-
410
- _ => throw new UnreachableException ( )
411
- } ;
412
-
413
- return true ;
398
+ } ,
399
+ nullable : true ,
400
+ argumentsPropagateNullability : new [ ] { true , true } ,
401
+ typeof ( string ) ,
402
+ stringTypeMapping ) ,
403
+ translatedPattern ) ) ) ,
404
+
405
+ // For Contains, just use CHARINDEX and check if the result is greater than 0.
406
+ StartsEndsWithContains . Contains when patternIsNonEmptyConstantString
407
+ => _sqlExpressionFactory . AndAlso (
408
+ _sqlExpressionFactory . IsNotNull ( translatedInstance ) ,
409
+ CharIndexGreaterThanZero ( ) ) ,
410
+
411
+ // For Contains, just use CHARINDEX and check if the result is greater than 0.
412
+ // Add a check to return null when the pattern is an empty string (and the string isn't null)
413
+ StartsEndsWithContains . Contains
414
+ => _sqlExpressionFactory . AndAlso (
415
+ _sqlExpressionFactory . IsNotNull ( translatedInstance ) ,
416
+ _sqlExpressionFactory . AndAlso (
417
+ _sqlExpressionFactory . IsNotNull ( translatedPattern ) ,
418
+ _sqlExpressionFactory . OrElse (
419
+ CharIndexGreaterThanZero ( ) ,
420
+ _sqlExpressionFactory . Like (
421
+ translatedPattern ,
422
+ _sqlExpressionFactory . Constant ( string . Empty , stringTypeMapping ) ) ) ) ) ,
423
+
424
+ _ => throw new UnreachableException ( )
425
+ } ;
426
+
427
+ SqlExpression CharIndexGreaterThanZero ( )
428
+ => _sqlExpressionFactory . GreaterThan (
429
+ _sqlExpressionFactory . Function (
430
+ "CHARINDEX" ,
431
+ new [ ] { translatedPattern , translatedInstance } ,
432
+ nullable : true ,
433
+ argumentsPropagateNullability : new [ ] { true , true } ,
434
+ typeof ( int ) ) ,
435
+ _sqlExpressionFactory . Constant ( 0 ) ) ;
414
436
}
415
437
}
416
438
}
0 commit comments