Skip to content

Query: revisit design for function non-determinism and client evaluatability #12672

@divega

Description

@divega

Problem

Currently EvaluatableExpressionFilter conflates how we treat two different kinds of functions:

  1. Expressions like DateTime.Now are there because we want to evaluate them on the server to avoid inconsistent results
  2. Expressions like Guid.NewGuid are there because we need to make sure we evaluate them once per row when evaluating in memory

This design leads to potentially incorrect results and severe inefficiencies. E.g. in SQLite, for this query:

    likeToday = await db.Records
        .OrderBy(x => x.Timestamp)
        .Where(x => x.Timestamp.Day == DateTime.Now.AddDays(1).AddDays(-1).Day)
        .FirstOrDefaultAsync();

Desired behavior

For this is case is to obtain the value for DateTime.Now.AddDays(1).AddDays(-1).Day and pass it to a parameter so that it can be compared against x.Timestamp.Day on the server.

Actual behavior

We end up evaluating the whole predicate in-memory.

This is bad because:

  1. We don't really want to move the comparison against x.Timestamp.Day to the client if it can perfectly be evaluated on the server.

  2. As previously stated, we do not want to ever evaluate DateTime.Now on the client to avoid inconsistencies with machine state and time zone differences.

Explanation

Normally we would have translated DateTime.Now to GETDATE() but because it is composed over with functions which (in SQLite) have no server translation, we end up evaluating the whole predicate in-memory. We are doing this only because DateTime.Now is in the black list of "non-deterministic functions" in EvaluatableExpressionFilter which actually implies that it needs to be evaluated once per row. We certainly need that for Guid.NewGuid and Random.Next, but not for DateTime.Now and similar.

DateTime.Now is non-deterministic, but unlike RNG-based functions like Guid.NewGuid() or Random.Next(), calling it has no side-effects on the results of the next call. That is why it is sufficient to call it once per query.

In SQL Server, usage of GETDATE() is a query is treated as a "non-deterministic runtime constant", and is evaluated only once.

Proposal

We should have a different mechanism to keep track of functions that should be evaluated on the server because the client implementation isn't adequate.

This mechanism should work for provider specific and model-mapped functions rather than being hardcoded.

If we cannot evaluate one of these on the server, we should at least issue a client eval warning (which can be turned into an error).

We should not rely on the body of the method throwing. Sometimes the in-memory implementation exists, but isn't adequate.

DateTime.Now (and similar) should not be in the EvaluatableExpressionFilter blacklist because it does not need to be evaluated once per row. Instead, we should use the new mechanism for it.

Ultimately, we can add the ability to issue a separate query to evaluate on the server (tracked by #11466).

For more details, see discussions at:

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions