Skip to content

Commit 346c373

Browse files
Ticket #926 : Fix performance issue in SCIM MongoDB
1 parent 86e8a79 commit 346c373

4 files changed

Lines changed: 154 additions & 154 deletions

File tree

src/Scim/SimpleIdServer.Scim.Parser/SCIMExpressionLinqExtensions.cs

Lines changed: 136 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using SimpleIdServer.Scim.Parser.Operators;
55
using System;
66
using System.Collections.Generic;
7+
using System.ComponentModel;
78
using System.Linq;
89
using System.Linq.Expressions;
910

@@ -81,7 +82,7 @@ public static Expression EvaluateAttributes(this SCIMLogicalExpression expressio
8182
}
8283
}
8384

84-
public static Expression EvaluateAttributes(this SCIMComparisonExpression expression, ParameterExpression parameterExpression, string propertyName = "CachedChildren")
85+
public static Expression EvaluateAttributes(this SCIMComparisonExpression expression, ParameterExpression parameterExpression, string propertyName = "CachedChildren", bool useRegex = false)
8586
{
8687
var schemaAttributeId = Expression.Property(parameterExpression, "SchemaAttributeId");
8788
var propertyValueString = Expression.Property(parameterExpression, "ValueString");
@@ -97,7 +98,8 @@ public static Expression EvaluateAttributes(this SCIMComparisonExpression expres
9798
propertyValueBoolean,
9899
propertyValueDecimal,
99100
propertyValueBinary,
100-
parameterExpression);
101+
parameterExpression,
102+
useRegex);
101103
return Expression.And(Expression.Equal(schemaAttributeId, Expression.Constant(expression.LeftExpression.SchemaAttribute.Id)), comparison);
102104
}
103105

@@ -252,7 +254,8 @@ public static Expression BuildComparisonExpression(
252254
MemberExpression propertyValueBoolean = null,
253255
MemberExpression propertyValueDecimal = null,
254256
MemberExpression propertyValueBinary = null,
255-
ParameterExpression representationParameter = null)
257+
ParameterExpression representationParameter = null,
258+
bool useRegex = false)
256259
{
257260
representationParameter = representationParameter ?? Expression.Parameter(typeof(SCIMRepresentationAttribute), "ra");
258261
propertyValueString = propertyValueString ?? Expression.Property(representationParameter, "ValueString");
@@ -268,22 +271,22 @@ public static Expression BuildComparisonExpression(
268271
switch (schemaAttr.Type)
269272
{
270273
case SCIMSchemaAttributeTypes.STRING:
271-
comparison = NotEqual(propertyValueString, Expression.Constant(comparisonExpression.Value), schemaAttr.CaseExact);
274+
comparison = NotEqual(propertyValueString, Expression.Constant(comparisonExpression.Value), schemaAttr.CaseExact, useRegex);
272275
break;
273276
case SCIMSchemaAttributeTypes.INTEGER:
274-
comparison = NotEqual(propertyValueInteger, Expression.Constant(ParseInt(comparisonExpression.Value)));
277+
comparison = NotEqual(propertyValueInteger, Expression.Constant(ParseInt(comparisonExpression.Value)), useRegex: useRegex);
275278
break;
276279
case SCIMSchemaAttributeTypes.DATETIME:
277-
comparison = NotEqual(propertyValueDatetime, Expression.Constant(ParseDateTime(comparisonExpression.Value)));
280+
comparison = NotEqual(propertyValueDatetime, Expression.Constant(ParseDateTime(comparisonExpression.Value)), useRegex: useRegex);
278281
break;
279282
case SCIMSchemaAttributeTypes.BOOLEAN:
280-
comparison = NotEqual(propertyValueBoolean, Expression.Constant(ParseBoolean(comparisonExpression.Value)));
283+
comparison = NotEqual(propertyValueBoolean, Expression.Constant(ParseBoolean(comparisonExpression.Value)), useRegex: useRegex);
281284
break;
282285
case SCIMSchemaAttributeTypes.DECIMAL:
283-
comparison = NotEqual(propertyValueDecimal, Expression.Constant(ParseDecimal(comparisonExpression.Value)));
286+
comparison = NotEqual(propertyValueDecimal, Expression.Constant(ParseDecimal(comparisonExpression.Value)), useRegex: useRegex);
284287
break;
285288
case SCIMSchemaAttributeTypes.BINARY:
286-
comparison = NotEqual(propertyValueBinary, Expression.Constant(comparisonExpression.Value));
289+
comparison = NotEqual(propertyValueBinary, Expression.Constant(comparisonExpression.Value), useRegex: useRegex);
287290
break;
288291
}
289292
break;
@@ -347,22 +350,22 @@ public static Expression BuildComparisonExpression(
347350
switch (schemaAttr.Type)
348351
{
349352
case SCIMSchemaAttributeTypes.STRING:
350-
comparison = Equal(propertyValueString, Expression.Constant(comparisonExpression.Value), schemaAttr.CaseExact);
353+
comparison = Equal(propertyValueString, Expression.Constant(comparisonExpression.Value), schemaAttr.CaseExact, useRegex);
351354
break;
352355
case SCIMSchemaAttributeTypes.INTEGER:
353-
comparison = Equal(propertyValueInteger, Expression.Constant(ParseInt(comparisonExpression.Value)));
356+
comparison = Equal(propertyValueInteger, Expression.Constant(ParseInt(comparisonExpression.Value)), useRegex: useRegex);
354357
break;
355358
case SCIMSchemaAttributeTypes.DATETIME:
356-
comparison = Equal(propertyValueDatetime, Expression.Constant(ParseDateTime(comparisonExpression.Value)));
359+
comparison = Equal(propertyValueDatetime, Expression.Constant(ParseDateTime(comparisonExpression.Value)), useRegex: useRegex);
357360
break;
358361
case SCIMSchemaAttributeTypes.BOOLEAN:
359-
comparison = Equal(propertyValueBoolean, Expression.Constant(ParseBoolean(comparisonExpression.Value)));
362+
comparison = Equal(propertyValueBoolean, Expression.Constant(ParseBoolean(comparisonExpression.Value)), useRegex: useRegex);
360363
break;
361364
case SCIMSchemaAttributeTypes.DECIMAL:
362-
comparison = Equal(propertyValueDecimal, Expression.Constant(ParseDecimal(comparisonExpression.Value)));
365+
comparison = Equal(propertyValueDecimal, Expression.Constant(ParseDecimal(comparisonExpression.Value)), useRegex: useRegex);
363366
break;
364367
case SCIMSchemaAttributeTypes.BINARY:
365-
comparison = Equal(propertyValueBinary, Expression.Constant(comparisonExpression.Value));
368+
comparison = Equal(propertyValueBinary, Expression.Constant(comparisonExpression.Value), useRegex: useRegex);
366369
break;
367370
}
368371
break;
@@ -373,10 +376,29 @@ public static Expression BuildComparisonExpression(
373376
var startWith = typeof(string).GetMethod("StartsWith", new Type[] { typeof(string) });
374377
if (!schemaAttr.CaseExact)
375378
{
376-
comparison = Expression.Call(
377-
Expression.Call(Expression.Coalesce(propertyValueString, Expression.Constant(string.Empty)), typeof(string).GetMethod("ToLower", System.Type.EmptyTypes)),
378-
startWith,
379-
Expression.Call(Expression.Constant(comparisonExpression.Value), typeof(string).GetMethod("ToLower", System.Type.EmptyTypes)));
379+
if(useRegex)
380+
{
381+
var regexMethod = typeof(System.Text.RegularExpressions.Regex).GetMethod(
382+
"IsMatch",
383+
new Type[] { typeof(string), typeof(string), typeof(System.Text.RegularExpressions.RegexOptions) }
384+
);
385+
var escapedValue = System.Text.RegularExpressions.Regex.Escape(comparisonExpression.Value);
386+
var pattern = $"^{escapedValue}";
387+
comparison = Expression.Call(
388+
null,
389+
regexMethod,
390+
propertyValueString,
391+
Expression.Constant(pattern),
392+
Expression.Constant(System.Text.RegularExpressions.RegexOptions.IgnoreCase)
393+
);
394+
}
395+
else
396+
{
397+
comparison = Expression.Call(
398+
Expression.Call(Expression.Coalesce(propertyValueString, Expression.Constant(string.Empty)), typeof(string).GetMethod("ToLower", System.Type.EmptyTypes)),
399+
startWith,
400+
Expression.Call(Expression.Constant(comparisonExpression.Value), typeof(string).GetMethod("ToLower", System.Type.EmptyTypes)));
401+
}
380402
}
381403
else
382404
{
@@ -390,12 +412,32 @@ public static Expression BuildComparisonExpression(
390412
{
391413
case SCIMSchemaAttributeTypes.STRING:
392414
var endWith = typeof(string).GetMethod("EndsWith", new Type[] { typeof(string) });
393-
if(!schemaAttr.CaseExact)
415+
if (!schemaAttr.CaseExact)
394416
{
395-
comparison = Expression.Call(
396-
Expression.Call(Expression.Coalesce(propertyValueString, Expression.Constant(string.Empty)), typeof(string).GetMethod("ToLower", System.Type.EmptyTypes)),
397-
endWith,
398-
Expression.Call(Expression.Constant(comparisonExpression.Value), typeof(string).GetMethod("ToLower", System.Type.EmptyTypes)));
417+
if(useRegex)
418+
{
419+
var regexMethod = typeof(System.Text.RegularExpressions.Regex).GetMethod(
420+
"IsMatch",
421+
new Type[] { typeof(string), typeof(string), typeof(System.Text.RegularExpressions.RegexOptions) }
422+
);
423+
var escapedValue = System.Text.RegularExpressions.Regex.Escape(comparisonExpression.Value);
424+
var pattern = $"{escapedValue}$";
425+
426+
comparison = Expression.Call(
427+
null,
428+
regexMethod,
429+
propertyValueString,
430+
Expression.Constant(pattern),
431+
Expression.Constant(System.Text.RegularExpressions.RegexOptions.IgnoreCase)
432+
);
433+
}
434+
else
435+
{
436+
comparison = Expression.Call(
437+
Expression.Call(Expression.Coalesce(propertyValueString, Expression.Constant(string.Empty)), typeof(string).GetMethod("ToLower", System.Type.EmptyTypes)),
438+
endWith,
439+
Expression.Call(Expression.Constant(comparisonExpression.Value), typeof(string).GetMethod("ToLower", System.Type.EmptyTypes)));
440+
}
399441
}
400442
else
401443
{
@@ -405,21 +447,37 @@ public static Expression BuildComparisonExpression(
405447
}
406448
break;
407449
case SCIMComparisonOperators.CO:
408-
if (schemaAttr.Type == SCIMSchemaAttributeTypes.STRING)
450+
var contains = typeof(string).GetMethod("Contains", new[] { typeof(string) });
451+
if (!schemaAttr.CaseExact)
409452
{
410-
var contains = typeof(string).GetMethod("Contains", new[] { typeof(string) });
411-
if(!schemaAttr.CaseExact)
453+
if(useRegex)
412454
{
455+
var regexMethod = typeof(System.Text.RegularExpressions.Regex).GetMethod(
456+
"IsMatch",
457+
new Type[] { typeof(string), typeof(string), typeof(System.Text.RegularExpressions.RegexOptions) }
458+
);
459+
var escapedValue = System.Text.RegularExpressions.Regex.Escape(comparisonExpression.Value);
460+
413461
comparison = Expression.Call(
414-
Expression.Call(Expression.Coalesce(propertyValueString, Expression.Constant(string.Empty)), typeof(string).GetMethod("ToLower", System.Type.EmptyTypes)),
415-
contains,
416-
Expression.Call(Expression.Constant(comparisonExpression.Value), typeof(string).GetMethod("ToLower", System.Type.EmptyTypes)));
462+
null,
463+
regexMethod,
464+
propertyValueString,
465+
Expression.Constant(escapedValue),
466+
Expression.Constant(System.Text.RegularExpressions.RegexOptions.IgnoreCase)
467+
);
417468
}
418469
else
419470
{
420-
comparison = Expression.Call(propertyValueString, contains, Expression.Constant(comparisonExpression.Value));
471+
comparison = Expression.Call(
472+
Expression.Call(Expression.Coalesce(propertyValueString, Expression.Constant(string.Empty)), typeof(string).GetMethod("ToLower", System.Type.EmptyTypes)),
473+
contains,
474+
Expression.Call(Expression.Constant(comparisonExpression.Value), typeof(string).GetMethod("ToLower", System.Type.EmptyTypes)));
421475
}
422476
}
477+
else
478+
{
479+
comparison = Expression.Call(propertyValueString, contains, Expression.Constant(comparisonExpression.Value));
480+
}
423481
break;
424482
}
425483

@@ -454,7 +512,7 @@ private static Expression LessThanOrEqual(Expression e1, Expression e2)
454512
return Expression.LessThanOrEqual(e1, e2);
455513
}
456514

457-
private static Expression Equal(Expression e1, Expression e2, bool caseSensitive = true)
515+
private static Expression Equal(Expression e1, Expression e2, bool caseSensitive = true, bool useRegex = false)
458516
{
459517
if (IsNullableType(e1.Type) && !IsNullableType(e2.Type))
460518
{
@@ -465,35 +523,63 @@ private static Expression Equal(Expression e1, Expression e2, bool caseSensitive
465523
e1 = Expression.Convert(e1, e2.Type);
466524
}
467525

468-
if(!caseSensitive)
526+
var checkCaseNotsensitive = !caseSensitive && e1.Type == typeof(string);
527+
if (!checkCaseNotsensitive)
528+
{
529+
return Expression.Equal(e1, e2);
530+
}
531+
532+
if (!useRegex)
469533
{
470534
e1 = Expression.Coalesce(e1, Expression.Constant(string.Empty));
471535
e1 = Expression.Call(e1, typeof(string).GetMethod("ToLower", System.Type.EmptyTypes));
472536
e2 = Expression.Call(e2, typeof(string).GetMethod("ToLower", System.Type.EmptyTypes));
537+
return Expression.Equal(e1, e2);
473538
}
474539

475-
return Expression.Equal(e1, e2);
476-
}
477-
478-
private static Expression NotEqual(Expression e1, Expression e2, bool caseSensitive = true)
479-
{
480-
if (IsNullableType(e1.Type) && !IsNullableType(e2.Type))
481-
{
482-
e2 = Expression.Convert(e2, e1.Type);
483-
}
484-
else if (!IsNullableType(e1.Type) && IsNullableType(e2.Type))
540+
var regexMethod = typeof(System.Text.RegularExpressions.Regex).GetMethod(
541+
"IsMatch",
542+
new Type[] { typeof(string), typeof(string), typeof(System.Text.RegularExpressions.RegexOptions) }
543+
);
544+
var escapeMethod = typeof(System.Text.RegularExpressions.Regex).GetMethod("Escape", new Type[] { typeof(string) });
545+
if (e2 is ConstantExpression constantExpr && constantExpr.Value is string strValue)
485546
{
486-
e1 = Expression.Convert(e1, e2.Type);
547+
var escapedValue = System.Text.RegularExpressions.Regex.Escape(strValue);
548+
var pattern = $"^{escapedValue}$";
549+
return Expression.Call(
550+
null,
551+
regexMethod,
552+
e1,
553+
Expression.Constant(pattern),
554+
Expression.Constant(System.Text.RegularExpressions.RegexOptions.IgnoreCase)
555+
);
487556
}
488-
489-
if (!caseSensitive)
557+
else
490558
{
491-
e1 = Expression.Coalesce(e1, Expression.Constant(string.Empty));
492-
e1 = Expression.Call(e1, typeof(string).GetMethod("ToLower", System.Type.EmptyTypes));
493-
e2 = Expression.Call(e2, typeof(string).GetMethod("ToLower", System.Type.EmptyTypes));
559+
var escapedE2 = Expression.Call(null, escapeMethod, e2);
560+
var concatMethod = typeof(string).GetMethod("Concat", new Type[] { typeof(string), typeof(string), typeof(string) });
561+
var pattern = Expression.Call(
562+
null,
563+
concatMethod,
564+
Expression.Constant("^"),
565+
escapedE2,
566+
Expression.Constant("$")
567+
);
568+
569+
return Expression.Call(
570+
null,
571+
regexMethod,
572+
e1,
573+
pattern,
574+
Expression.Constant(System.Text.RegularExpressions.RegexOptions.IgnoreCase)
575+
);
494576
}
577+
}
495578

496-
return Expression.NotEqual(e1, e2);
579+
private static Expression NotEqual(Expression e1, Expression e2, bool caseSensitive = true, bool useRegex = false)
580+
{
581+
var equalExpr = Equal(e1, e2, caseSensitive, useRegex);
582+
return Expression.Not(equalExpr);
497583
}
498584

499585
private static Expression GreaterThanOrEqual(Expression e1, Expression e2)

0 commit comments

Comments
 (0)