From 9c1ca0c8bd154d7fa6cac67ed202a2f19140e720 Mon Sep 17 00:00:00 2001 From: Arnaud Lacurie Date: Sat, 23 May 2026 17:41:54 +0100 Subject: [PATCH 1/4] Support SELECT without a FROM clause Constant-expression queries like SELECT 'bob' or SELECT 1 + 1 are now accepted. When no FROM clause is present, the planner synthesizes a single-row source (ExplodeExpression over a one-element array) so that the existing Cascades rules can plan and execute the query unchanged. --- .../query/visitors/QueryVisitor.java | 16 +++++-- .../src/test/java/YamlIntegrationTests.java | 5 +++ .../test/resources/select-without-from.yamsql | 45 +++++++++++++++++++ 3 files changed, 63 insertions(+), 3 deletions(-) create mode 100644 yaml-tests/src/test/resources/select-without-from.yamsql diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/QueryVisitor.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/QueryVisitor.java index 5ec8a60dcf..a3e2960ad3 100644 --- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/QueryVisitor.java +++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/QueryVisitor.java @@ -31,6 +31,7 @@ import com.apple.foundationdb.record.query.plan.cascades.predicates.CompatibleTypeEvolutionPredicate; import com.apple.foundationdb.record.query.plan.cascades.typing.Type; import com.apple.foundationdb.record.query.plan.cascades.values.FieldValue; +import com.apple.foundationdb.record.query.plan.cascades.values.LiteralValue; import com.apple.foundationdb.record.query.plan.cascades.values.Value; import com.apple.foundationdb.record.util.pair.NonnullPair; import com.apple.foundationdb.relational.api.exceptions.ErrorCode; @@ -222,11 +223,20 @@ public LogicalOperator handleRecursiveNamedQuery(@Nonnull final RelationalParser @Nonnull @Override public LogicalOperator visitSimpleTable(@Nonnull RelationalParser.SimpleTableContext simpleTableContext) { - Assert.notNullUnchecked(simpleTableContext.fromClause(), ErrorCode.UNSUPPORTED_QUERY, "query is not supported"); getDelegate().pushPlanFragment(); - simpleTableContext.fromClause().accept(this); + if (simpleTableContext.fromClause() != null) { + simpleTableContext.fromClause().accept(this); + } else { + // No FROM clause: synthesize a single-row source so constant expressions + // can be projected over exactly one output row (standard SQL semantics). + final var dummyElement = Expression.fromUnderlying(LiteralValue.ofScalar(true)); + final var arrayOfOne = getDelegate().resolveFunction("__internal_array", false, dummyElement); + final var explodeExpr = new ExplodeExpression(arrayOfOne.getUnderlying()); + final var syntheticQuantifier = Quantifier.forEach(Reference.initialOf(explodeExpr)); + getDelegate().getCurrentPlanFragment().setOperator(LogicalOperator.newUnnamedOperator(Expressions.empty(), syntheticQuantifier)); + } - var where = Optional.ofNullable(simpleTableContext.fromClause().whereExpr() == null ? + var where = Optional.ofNullable(simpleTableContext.fromClause() == null || simpleTableContext.fromClause().whereExpr() == null ? null : visitWhereExpr(simpleTableContext.fromClause().whereExpr())); diff --git a/yaml-tests/src/test/java/YamlIntegrationTests.java b/yaml-tests/src/test/java/YamlIntegrationTests.java index f42b76bf15..4b1a302ae9 100644 --- a/yaml-tests/src/test/java/YamlIntegrationTests.java +++ b/yaml-tests/src/test/java/YamlIntegrationTests.java @@ -493,4 +493,9 @@ public void recordTypeKeyTest(YamlTest.Runner runner) throws Exception { public void filterIndexTest(YamlTest.Runner runner) throws Exception { runner.runYamsql("filter-index.yamsql"); } + + @TestTemplate + public void selectWithoutFrom(YamlTest.Runner runner) throws Exception { + runner.runYamsql("select-without-from.yamsql"); + } } diff --git a/yaml-tests/src/test/resources/select-without-from.yamsql b/yaml-tests/src/test/resources/select-without-from.yamsql new file mode 100644 index 0000000000..962c032d34 --- /dev/null +++ b/yaml-tests/src/test/resources/select-without-from.yamsql @@ -0,0 +1,45 @@ +# +# select-without-from.yamsql +# +# This source file is part of the FoundationDB open source project +# +# Copyright 2021-2025 Apple Inc. and the FoundationDB project authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +--- +options: + supported_version: !current_version +--- +schema_template: + create table T(id bigint, primary key(id)) +--- +test_block: + preset: single_repetition_ordered + tests: + - + - query: SELECT 'bob' AS greeting + - result: [{'bob'}] + - + - query: SELECT 42 AS answer + - result: [{42}] + - + - query: SELECT 1 + 1 AS result + - result: [{2}] + - + - query: SELECT 'hello' AS a, 'world' AS b + - result: [{'hello', 'world'}] + - + - query: SELECT true AS flag + - result: [{true}] +... From f5f47d37078fe8ec95b1fb7ca0dd03d2511d8522 Mon Sep 17 00:00:00 2001 From: Arnaud Lacurie Date: Sat, 23 May 2026 17:47:46 +0100 Subject: [PATCH 2/4] Add resultMetadata checks to select-without-from.yamsql --- yaml-tests/src/test/resources/select-without-from.yamsql | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/yaml-tests/src/test/resources/select-without-from.yamsql b/yaml-tests/src/test/resources/select-without-from.yamsql index 962c032d34..8c46f61c0b 100644 --- a/yaml-tests/src/test/resources/select-without-from.yamsql +++ b/yaml-tests/src/test/resources/select-without-from.yamsql @@ -29,17 +29,22 @@ test_block: tests: - - query: SELECT 'bob' AS greeting + - resultMetadata: [{GREETING: STRING}] - result: [{'bob'}] - - query: SELECT 42 AS answer + - resultMetadata: [{ANSWER: INTEGER}] - result: [{42}] - - query: SELECT 1 + 1 AS result + - resultMetadata: [{RESULT: INTEGER}] - result: [{2}] - - query: SELECT 'hello' AS a, 'world' AS b + - resultMetadata: [{A: STRING}, {B: STRING}] - result: [{'hello', 'world'}] - - query: SELECT true AS flag + - resultMetadata: [{FLAG: BOOLEAN}] - result: [{true}] ... From c41f270783a0f88efb26da4ed0d865d5cdb557c9 Mon Sep 17 00:00:00 2001 From: Arnaud Lacurie Date: Sat, 23 May 2026 17:57:50 +0100 Subject: [PATCH 3/4] Add unaliased SELECT test case to select-without-from.yamsql --- yaml-tests/src/test/resources/select-without-from.yamsql | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/yaml-tests/src/test/resources/select-without-from.yamsql b/yaml-tests/src/test/resources/select-without-from.yamsql index 8c46f61c0b..8a32beec3d 100644 --- a/yaml-tests/src/test/resources/select-without-from.yamsql +++ b/yaml-tests/src/test/resources/select-without-from.yamsql @@ -47,4 +47,8 @@ test_block: - query: SELECT true AS flag - resultMetadata: [{FLAG: BOOLEAN}] - result: [{true}] + - + - query: SELECT 41 + - resultMetadata: [{_0: INTEGER}] + - result: [{41}] ... From c1e83ae2a6fc0b5c5ee8cbe9a461ab4a3440b083 Mon Sep 17 00:00:00 2001 From: Arnaud Lacurie Date: Sat, 23 May 2026 21:21:07 +0100 Subject: [PATCH 4/4] Add error test for WHERE without FROM --- yaml-tests/src/test/resources/select-without-from.yamsql | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/yaml-tests/src/test/resources/select-without-from.yamsql b/yaml-tests/src/test/resources/select-without-from.yamsql index 8a32beec3d..2cb4d6f851 100644 --- a/yaml-tests/src/test/resources/select-without-from.yamsql +++ b/yaml-tests/src/test/resources/select-without-from.yamsql @@ -51,4 +51,9 @@ test_block: - query: SELECT 41 - resultMetadata: [{_0: INTEGER}] - result: [{41}] + - + # WHERE without FROM is a syntax error: WHERE is part of fromClause in the grammar + # and cannot appear without a preceding FROM. + - query: SELECT 'bob' WHERE true + - error: "42601" ...