Skip to content

Add header support for caching #2650

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 12 commits into
base: main
Choose a base branch
from

Conversation

aaronburtle
Copy link
Contributor

Why make this change?

Closes #2604

What is this change?

Inside of the SqlQueryStructure, there is a private constructor, that all of the public constructors will call. This private constructor offers a single point where all of the queries will bottleneck, and therefore we add the cache control information to the query structure at this point. Then when we are in the SqlQueryEngine, we can check for this cache control information and handle the query appropriately.

The cache control options can be found

here: #2253

and here: https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cache-Control#request_directives

But for reference, they are no-cache, no-store, only-if-cached, which will mean we do not get from the cache, we do not store in the cache, and if there is a cache miss we return gateway timeout, respectively.

How was this tested?

Run against test suite.

Sample Request(s)

To test you need to include the relevant cache headers in the request, "no-cache", "no-store", or "only-if-cached"

@aaronburtle aaronburtle self-assigned this Apr 15, 2025
@aaronburtle aaronburtle added the enhancement New feature or request label Apr 15, 2025
@aaronburtle aaronburtle added this to the 1.5 milestone Apr 15, 2025
@aaronburtle
Copy link
Contributor Author

/azp run

Copy link

Azure Pipelines successfully started running 6 pipeline(s).

@aaronburtle
Copy link
Contributor Author

/azp run

Copy link

Azure Pipelines successfully started running 6 pipeline(s).

@aaronburtle
Copy link
Contributor Author

/azp run

Copy link

Azure Pipelines successfully started running 6 pipeline(s).

@aaronburtle
Copy link
Contributor Author

/azp run

Copy link

Azure Pipelines successfully started running 6 pipeline(s).

@Aniruddh25 Aniruddh25 requested a review from Copilot April 16, 2025 18:11
Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR adds support for handling caching directives via the HTTP Cache-Control header by incorporating new properties and logic into the SQL query processing and cache service layers. It introduces new cache getter and setter methods in the cache service, adds cache control handling in the SQL query engine, and updates the SQL query structure and REST request context to capture and validate the Cache-Control header.

Reviewed Changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

File Description
src/Core/Services/Cache/DabCacheService.cs Added TryGet and Set methods to retrieve/store JSON responses in the cache based on a derived cache key.
src/Core/Resolvers/SqlQueryEngine.cs Updated the query execution flow to handle cache control options and to throw exceptions when only-if-cached is used with a cache miss.
src/Core/Resolvers/Sql Query Structures/SqlQueryStructure.cs Introduced CacheControlOption property and constants, with logic to extract and validate Cache-Control header values.
src/Core/Models/RestRequestContexts/RestRequestContext.cs Added a new property to capture the Cache-Control header from REST requests.

@aaronburtle
Copy link
Contributor Author

/azp run

Copy link

Azure Pipelines successfully started running 6 pipeline(s).

@aaronburtle
Copy link
Contributor Author

/azp run

Copy link

Azure Pipelines successfully started running 6 pipeline(s).

@@ -255,6 +271,7 @@ public SqlQueryStructure(
: this(sqlMetadataProvider,
authorizationResolver,
gQLFilterParser,
httpRequestHeaders: httpContext?.Request.Headers,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can httpContext be null here? If not, do we need httpContext?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it can be null here. For GraphQL read operation we intentionally leave this null, and then use that fact to sort out when to process authorization policies. This flow was causing a bunch of tests to fail when I was forcing an httpContext to always get passed through, which is why now I just grab the Headers, and allow the context to remain null when its passed in as such.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it could be null only for GraphQL read, we need not worried here, right? Since the constructor is actually for Rest only:

public SqlQueryStructure(
RestRequestContext context,
ISqlMetadataProvider sqlMetadataProvider,

also the argument to the constructor itself is not nullable

        **HttpContext httpContext)**

switch (structure.CacheControlOption)
{
// Do not get result from cache even if it exists, still cache result.
case SqlQueryStructure.CACHE_CONTROL_NO_CACHE:
Copy link
Contributor

@Aniruddh25 Aniruddh25 Apr 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add some automated tests for these control options?

Copy link
Contributor

@Aniruddh25 Aniruddh25 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Waiting on consistently using nullable or not nullable httpContext .

And a test case.

@aaronburtle
Copy link
Contributor Author

/azp run

Copy link

Azure Pipelines successfully started running 6 pipeline(s).

@@ -102,6 +102,10 @@ public class SqlQueryStructure : BaseSqlQueryStructure

public const string CACHE_CONTROL_ONLY_IF_CACHED = "only-if-cached";

public HashSet<string> cacheControlHeaderOptions = new(
new[] { CACHE_CONTROL, CACHE_CONTROL_NO_STORE, CACHE_CONTROL_NO_CACHE, CACHE_CONTROL_ONLY_IF_CACHED },
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is CACHE_CONTROL i.e. Cache-Control itself a valid value of this option? IMO this should not be in the HashSet since you would be giving out an exception for this value if its actually an invalid value

If its valid, what does it mean?

dataSourceName: queryMetadata.DataSource);
_cache.Set<JsonElement?>(queryMetadata, cacheEntryTtl: runtimeConfig.GetEntityCacheEntryTtl(entityName: structure.EntityName), result);
return ParseResultIntoJsonDocument(result);
// Do not store result even if valid, stil get from cache if available.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: add a new line before the comment

args: null,
dataSourceName: queryMetadata.DataSource);
return ParseResultIntoJsonDocument(result);
// Only return query response if it exists in cache, return gateway timeout otherwise.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: add new line before comment

statusCode: System.Net.HttpStatusCode.GatewayTimeout,
subStatusCode: DataApiBuilderException.SubStatusCodes.ItemNotFound);
return ParseResultIntoJsonDocument(result);
default:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: add new line before default

Comment on lines +369 to +370
result = await _cache.GetOrSetAsync<JsonElement>(queryExecutor, queryMetadata, cacheEntryTtl: runtimeConfig.GetEntityCacheEntryTtl(entityName: structure.EntityName));
return ParseResultIntoJsonDocument(result);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am confused by this, shouldn't there be two different cases, one if CacheControlOption is null and one for CACHE_CONTROL?

/// </summary>
/// <typeparam name="JsonElement">The type of value in the cache</typeparam>
/// <param name="queryMetadata">Metadata used to create a cache key or fetch a response from the database.</param>
/// <param name="cacheEntryTtl">Number of seconds the cache entry should be valid before eviction.</param>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't there be a section for cacheValue?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add support for request and response HTTP headers for caching.
3 participants