You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix: null-reference crash when a Where predicate null-checks a captured value (#122)
* fix: null-reference crash when a Where predicate null-checks a captured value
A LINQ predicate like `x => filterPredicate == null || filterPredicate(x)`
crashed with a NullReferenceException on `Get`: branches the visitor could
not translate (i.e. a delegate invocation) were combined with the
null-forgiving operator, putting a literal null into the nested filter
criteria that `PrepareFilter` later dereferenced.
The visitor now tracks the lambda parameter and evaluates branches that
never reference the model locally, folding them through `||`/`&&` with
C#'s short-circuit semantics. The reported idiom therefore works: when
the captured delegate is null the predicate is always true and `Where`
applies no filter. Branches that do reference the model but cannot be
translated (a compiled delegate is opaque) now throw a descriptive
ArgumentException pointing at the conditional-query workaround, as does
an always-false predicate (silently matching every row would be dangerous
for `Update`/`Delete`).
Also moves the `== null` -> `is.null` / `!= null` -> `not.is.null`
conversion from `Table.Where` (top level only) into the visitor so nested
null checks like `x => x.Name == null || x.Name == "foo"` no longer emit
an invalid `eq.` filter, and guards `PrepareFilter` against null nested
filters supplied through the public QueryFilter constructor.
Fixessupabase-community/supabase-csharp#192
* refactor: remove comments
* refactor: extract LINQ Where tests
* docs: update Where predicate documentation
* refactor: code cleanup
/// <item><description>The left side of a comparison must be a Model property (with a `Column` or `PrimaryKey` attribute); the right side a constant, captured variable, or instantiation (`new DateTime(...)`).</description></item>
372
+
/// <item><description>Comparisons (`==`, `!=`, `>`, `>=`, `<`, `<=`) can be combined with `&&` and `||`, and `String`/collection `Contains` is supported.</description></item>
373
+
/// <item><description>Null checks translate to Postgrest `is null` filters: `x => x.Name == null` and `x => x.Name == null || x.Name == "Top Gun"` both work.</description></item>
374
+
/// <item><description>Conditions that never reference the Model (i.e. `x => localVariable == null`) are evaluated locally: an always-true predicate applies no filter, an always-false one throws.</description></item>
375
+
/// </list>
376
+
///
377
+
/// Invoking a delegate (`Func<TModel, bool>`) inside the predicate is not supported: a compiled
378
+
/// delegate cannot be translated into a Postgrest filter and throws an <see cref="ArgumentException"/>.
379
+
/// To apply an optional filter, declare it as an `Expression<Func<TModel, bool>>` and pass it
380
+
/// conditionally:
381
+
/// <code>
382
+
/// var query = client.Table<Movie>();
383
+
/// if (optionalFilter != null)
384
+
/// query = query.Where(optionalFilter);
385
+
/// </code>
386
+
/// </remarks>
367
387
/// <param name="predicate"></param>
368
388
/// <returns></returns>
389
+
/// <exception cref="ArgumentException">The predicate contains an expression that cannot be translated into a Postgrest filter, or never matches any row.</exception>
$"Unable to translate expression '{expression}' into a Postgrest filter. If the condition depends on values that are not model columns (i.e. invoking a delegate), evaluate it outside of `Where` and build the query conditionally instead.");
Copy file name to clipboardExpand all lines: Postgrest/Table.cs
+14-7Lines changed: 14 additions & 7 deletions
Original file line number
Diff line number
Diff line change
@@ -355,17 +355,18 @@ public IPostgrestTable<TModel> Where(Expression<Func<TModel, bool>> predicate)
355
355
varvisitor=newWhereExpressionVisitor();
356
356
visitor.Visit(predicate);
357
357
358
+
if(visitor.ConstantValue==true)
359
+
returnthis;
360
+
361
+
if(visitor.ConstantValue==false)
362
+
thrownewArgumentException(
363
+
"The supplied predicate always evaluates to false, so no row would ever match. Evaluate the condition outside of `Where` and build the query conditionally instead.");
364
+
358
365
if(visitor.Filter==null)
359
366
thrownewArgumentException(
360
367
"Unable to parse the supplied predicate, did you return a predicate where each left hand of the condition is a Model property?");
0 commit comments