Forbid subqueries in key expressions (ORDER BY, PRIMARY KEY, PARTITION BY, INDEX, TTL)

Subqueries in key expressions (e.g., `ORDER BY (1 = ANY(SELECT 1))`)
caused a LOGICAL_ERROR exception "Not-ready Set" during INSERT because
the `ExpressionAnalyzer` creates `FutureSetFromSubquery` objects that
nobody builds. Rather than fixing the complex interaction between
expression analysis, set building, and column naming across multiple
code paths, forbid subqueries in key expressions entirely — they have
no practical use case.

The check is added in `KeyDescription::getSortingKeyFromAST` (covers
ORDER BY, PRIMARY KEY, PARTITION BY), `IndexDescription::getIndexFromAST`
(covers skip indices), and `TTLDescription::getTTLFromAST` (covers TTL),
via a shared `checkExpressionDoesntContainSubqueries` function.

Closes https://github.com/ClickHouse/ClickHouse/issues/96657

Co-Authored-By: Claude Opus 4.6 <[email protected]>
This commit is contained in:
Alexey Milovidov
2026-02-14 02:21:18 +01:00
co-authored by Claude Opus 4.6
parent 2e525b608b
commit 7896defecb
6 changed files with 28 additions and 14 deletions
+1
View File
@@ -108,6 +108,7 @@ IndexDescription IndexDescription::getIndexFromAST(
result.is_implicitly_created = is_implicitly_created;
result.escape_filenames = escape_filenames;
checkExpressionDoesntContainSubqueries(*index_definition->getExpression());
result.initExpressionInfo(index_definition->getExpression(), columns, context);
for (auto & elem : result.sample_block)
-14
View File
@@ -3,13 +3,11 @@
#include <Functions/IFunction.h>
#include <Parsers/ASTIdentifier.h>
#include <Parsers/ASTFunction.h>
#include <Parsers/ASTSubquery.h>
#include <Interpreters/ExpressionActions.h>
#include <Interpreters/ExpressionAnalyzer.h>
#include <Interpreters/TreeRewriter.h>
#include <Storages/ColumnsDescription.h>
#include <Storages/extractKeyExpressionList.h>
#include <Common/checkStackSize.h>
#include <Common/quoteString.h>
#include <Interpreters/FunctionNameNormalizer.h>
#include <Parsers/ASTOrderByElement.h>
@@ -22,7 +20,6 @@ namespace DB
namespace ErrorCodes
{
extern const int BAD_ARGUMENTS;
extern const int LOGICAL_ERROR;
extern const int DATA_TYPE_CANNOT_BE_USED_IN_KEY;
}
@@ -122,17 +119,6 @@ bool KeyDescription::moduloToModuloLegacyRecursive(ASTPtr node_expr)
return modulo_in_ast;
}
static void checkExpressionDoesntContainSubqueries(const IAST & ast)
{
checkStackSize();
if (ast.as<ASTSubquery>())
throw Exception(ErrorCodes::BAD_ARGUMENTS, "Key expressions cannot contain subqueries");
for (const auto & child : ast.children)
checkExpressionDoesntContainSubqueries(*child);
}
KeyDescription KeyDescription::getSortingKeyFromAST(
const ASTPtr & definition_ast,
const ColumnsDescription & columns,
+3
View File
@@ -11,6 +11,7 @@
#include <Interpreters/addTypeConversionToAST.h>
#include <Parsers/ASTFunction.h>
#include <Parsers/ASTTTLElement.h>
#include <Storages/extractKeyExpressionList.h>
#include <Parsers/ASTIdentifier.h>
#include <Parsers/ASTAssignment.h>
#include <Storages/ColumnsDescription.h>
@@ -228,6 +229,8 @@ TTLDescription TTLDescription::getTTLFromAST(
else /// It's columns TTL without any additions, just copy it
result.expression_ast = definition_ast->clone();
checkExpressionDoesntContainSubqueries(*result.expression_ast);
auto ttl_ast = result.expression_ast->clone();
auto expression = buildExpressionAndSets(ttl_ast, columns.getAllPhysical(), context).expression;
result.expression_columns = expression->getRequiredColumnsWithTypes();
+19
View File
@@ -1,9 +1,28 @@
#include <Storages/extractKeyExpressionList.h>
#include <Parsers/ASTFunction.h>
#include <Parsers/ASTExpressionList.h>
#include <Parsers/ASTSubquery.h>
#include <Common/checkStackSize.h>
#include <Common/Exception.h>
namespace DB
{
namespace ErrorCodes
{
extern const int BAD_ARGUMENTS;
}
void checkExpressionDoesntContainSubqueries(const IAST & ast)
{
checkStackSize();
if (ast.as<ASTSubquery>())
throw Exception(ErrorCodes::BAD_ARGUMENTS, "Key expressions cannot contain subqueries");
for (const auto & child : ast.children)
checkExpressionDoesntContainSubqueries(*child);
}
ASTPtr extractKeyExpressionList(const ASTPtr & node)
{
if (!node)
+3
View File
@@ -5,4 +5,7 @@
namespace DB
{
ASTPtr extractKeyExpressionList(const ASTPtr & node);
/// Throws BAD_ARGUMENTS if the AST contains any subqueries.
void checkExpressionDoesntContainSubqueries(const IAST & ast);
}
@@ -5,3 +5,5 @@ CREATE TABLE t0 (c0 Int) ENGINE = MergeTree() ORDER BY (1 = ANY(SELECT 1)); -- {
CREATE TABLE t0 (c0 Int) ENGINE = MergeTree() ORDER BY (c0 IN (SELECT 1)); -- { serverError BAD_ARGUMENTS }
CREATE TABLE t0 (c0 Int) ENGINE = MergeTree() PARTITION BY (1 IN (SELECT 1)) ORDER BY c0; -- { serverError BAD_ARGUMENTS }
CREATE TABLE t0 (c0 Int) ENGINE = MergeTree() ORDER BY c0 PRIMARY KEY (c0 IN (SELECT 1)); -- { serverError BAD_ARGUMENTS }
CREATE TABLE t0 (c0 Int, INDEX idx (c0 IN (SELECT 1)) TYPE minmax GRANULARITY 1) ENGINE = MergeTree() ORDER BY c0; -- { serverError BAD_ARGUMENTS }
CREATE TABLE t0 (c0 Int, d DateTime) ENGINE = MergeTree() ORDER BY c0 TTL d + INTERVAL (1 IN (SELECT 1)) DAY; -- { serverError BAD_ARGUMENTS }