Skip to content

Stored queries literals with type only - #4409

Draft
sergei-pustovykh wants to merge 19 commits into
FoundationDB:mainfrom
sergei-pustovykh:stored-query-literal-type
Draft

Stored queries literals with type only#4409
sergei-pustovykh wants to merge 19 commits into
FoundationDB:mainfrom
sergei-pustovykh:stored-query-literal-type

Conversation

@sergei-pustovykh

@sergei-pustovykh sergei-pustovykh commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Adds inline typed parameters ?{type} to stored-query bodies, e.g.:

CREATE STORED QUERY by_col1 AS SELECT * FROM t1 WHERE col1 > ?{bigint}

A ?{type} declares only a type, not a value, so the stored query is planned value-free at warm-up: one cached plan is reused by any runtime query that binds a value of the same type at the same position (e.g. ... WHERE col1 > ?). This avoids authoring a stored query per concrete literal.

Grammar: ?{type} is a single lexer token (TYPED_PARAMETER). Being one token preserves the parameter's constantId and canonicalizes to ?, so a typed body shares the plan-cache key with a runtime ?.

Planning: the parameter becomes an unbound ConstantObjectValue of the declared type - no value seeded, no binding in the warmup EvaluationContext. The plan constraint is OfType(declared) + IS NOT NULL (or IS NULL for ?{null}), i.e. value-independent.

Null: the special ?{null} declares the nullable NULL type and warms a plan constrained by OF TYPE NULL + IS NULL, matching a runtime parameter bound to NULL (e.g. JDBC setNull). A typed non-null parameter and ?{null} warm separate cache entries.

Filtered indexes: a value-free parameter cannot drive filtered/range-index selection (the planner needs a concrete value). Such a stored query is skipped at warmup and logged; it still plans normally at runtime when a value is present. Use a concrete literal for those.

Offline warm-up: warm-up planning writes plans to the cache but never reads from it, so the cache lookup is skipped. There is nothing to match with anyway — a value-free stored query has no bound values, and matching a cached entry needs concrete values to evaluate its constraint. Skipping is safe (the lookup would always miss during warm-up) and each freshly planned entry is still written.

Supported types: primitives (BOOLEAN, INTEGER, BIGINT, FLOAT, DOUBLE, STRING, BYTES), UUID, and the special NULL.

The runtime value's raw type must match the declared type exactly (e.g. ?{bigint} is reused by a LONG value — setLong, or a 15L literal - not a bare INTEGER).

Non-null by default: an ordinary ?{type} carries an IS NOT NULL constraint and is not a nullable placeholder; use ?{null} to warm the NULL case.

Boolean cases are still authored as separate stored queries with concrete literals.

@sergei-pustovykh sergei-pustovykh added the enhancement New feature or request label Jul 29, 2026
@sergei-pustovykh
sergei-pustovykh marked this pull request as ready for review August 3, 2026 16:42
@Nonnull
private final Optional<EvaluationContext> evaluationContext;

@VisibleForTesting

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I had to remove Optional from the parameters to pass Teamscale

* because the cache is write-only during warm-up — the lookup would always miss anyway, and the freshly planned
* entry is written regardless. See {@link #equals}.
*/
private final boolean offlinePlanning;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Im not really pleased of this flag, but I did not find other option to skip cache lookup at offline planning step.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This looks like a design leak; we're spilling the concept of offline planning to this internal level. Ideally, we should not touch the plan cache, and gracefully handle the comparison between literalled-COV and unliteralled-COV under certain assumptions.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

agree! I remove offlinePlanning flag from the design and added MissingBindingException which QueryPlanConstraint catches at compileTimeEval and treats as false
not sure that it is the best approach, please review

* @return a value-free {@link ConstantObjectValue} of the declared type
*/
@Nonnull
public Value processTypedPreparedParam(@Nonnull final String typeName, final int tokenIndex) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I named it PreparedParam because ?{type} grammar belongs to block

* to {@code NULL} (e.g. JDBC {@code setNull}), whose runtime type is likewise {@code NULL}.
*/
@Nonnull
private static Type primitiveTypeForName(@Nonnull final String typeName) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

really do not like such manual parsing but did not find different way

* When {@code true}, planning runs offline (stored-query warm-up): the plan cache is write-only, so the cache
* lookup is forced to miss rather than matching the freshly planned entry against the value-free warm-up context.
*/
private final boolean offlinePlanning;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

again don't like such flags, but it is needed to skip cache lookup. open to redesign

@sergei-pustovykh
sergei-pustovykh requested a review from hatyo August 4, 2026 08:48
// constant id stays unbound. The declared type is applied during planning (see ExpressionVisitor).
if (allowTokenAddition) {
sqlCanonicalizer.append("?").append(" ");
parameterHash.putInt(Objects.hash("?"));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I believe the parameter hash is only used to validate physical plan continuation, so this should be fine, considering that this special type of parameter is only relevant for warming up the plan cache and nothing else, in other words, it shall never be used in a plan execution context.

…nding exception and do not match QueryPlanConstraint
@sergei-pustovykh
sergei-pustovykh marked this pull request as draft August 13, 2026 08:59
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.

2 participants