diff --git a/query/ir/interpreter/NLTranslator.cpp b/query/ir/interpreter/NLTranslator.cpp index 07f7046c7f..64524d073b 100644 --- a/query/ir/interpreter/NLTranslator.cpp +++ b/query/ir/interpreter/NLTranslator.cpp @@ -234,6 +234,12 @@ bool isConstantLike(mlir::Value value) { [](mlir::Value operand) { return isConstantLike(operand); }); } +// A predicate or a NOT over constants folds to a ColumnConst just as an arithmetic +// operation does, which the column answers and isConstantLike, reading the op, does not. +bool isConstantColumn(const Column* column) { + return column->getContainerKind() == ContainerKind::code(); +} + // A drain wires its emit loop's variables into the accumulator's one output slot per // column, so an accumulator can serve exactly one nl.collect or nl.unwind_collect. A // second drain would rebind the first's outputs to its own, incompatible column types @@ -1220,7 +1226,7 @@ void NLTranslator::addTruncateColumn(mlir::Value inputValue, Column* output = nullptr; NLBroadcastFunction copyPrefix = nullptr; - if (isConstantLike(inputValue)) { + if (isConstantColumn(input)) { output = _memory->allocSame(input); copyPrefix = NLExecutor::selectConstBlockRepeatFunction(); } else if (const auto nullableType = mlir::dyn_cast(elementType)) { @@ -1313,7 +1319,7 @@ void NLTranslator::addSkipColumn(mlir::Value inputValue, Column* output = nullptr; NLCopyFunction copySuffix = nullptr; - if (isConstantLike(inputValue)) { + if (isConstantColumn(input)) { output = _memory->allocSame(input); copySuffix = NLExecutor::selectConstCopyFunction(); } else if (const auto nullableType = mlir::dyn_cast(elementType)) { diff --git a/test/query/ir/CMakeLists.txt b/test/query/ir/CMakeLists.txt index a42a60fc51..d64fb74e97 100644 --- a/test/query/ir/CMakeLists.txt +++ b/test/query/ir/CMakeLists.txt @@ -479,3 +479,13 @@ target_link_libraries(test_query_ir_order_by_aggregate_unprojected_key PRIVATE turing_db_storage_s MLIRParser) target_include_directories(test_query_ir_order_by_aggregate_unprojected_key PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + +add_turing_test(test_query_ir_constant_predicate_projection ConstantPredicateProjectionTest.cpp) +target_link_libraries(test_query_ir_constant_predicate_projection PRIVATE + turing_db_s + turing_testenv_s + turing_db_examples_s + turing_db_system_s + turing_db_storage_s + turing_db_interpreter_v3_s) +target_include_directories(test_query_ir_constant_predicate_projection PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/test/query/ir/ConstantPredicateProjectionTest.cpp b/test/query/ir/ConstantPredicateProjectionTest.cpp new file mode 100644 index 0000000000..3bfe567032 --- /dev/null +++ b/test/query/ir/ConstantPredicateProjectionTest.cpp @@ -0,0 +1,156 @@ +#include + +#include +#include +#include +#include +#include + +#include "NLOutputSink.h" +#include "QueryInterpreterV3.h" +#include "QueryStatus.h" + +#include "Graph.h" +#include "SimpleGraph.h" +#include "SystemAccessor.h" +#include "SystemManager.h" +#include "columns/ColumnConst.h" +#include "columns/ColumnMask.h" +#include "metadata/PropertyType.h" +#include "versioning/ChangeID.h" +#include "versioning/CommitHash.h" + +#include "TuringException.h" +#include "TuringTest.h" +#include "TuringTestEnv.h" + +using namespace db; +using namespace turing::test; + +namespace { + +using BoolRow = std::vector; +using BoolRows = std::vector; + +// A predicate over constants alone is the one value it computes, so it reaches the sink as +// a ColumnConst standing for every row, where a predicate evaluated per row comes as a mask. +bool readBool(const Column* column, size_t row) { + if (const auto* constants = dynamic_cast*>(column)) { + return static_cast((*constants)[row]); + } else if (const auto* mask = dynamic_cast(column)) { + return (*mask)[row]; + } + + throw TuringException("ConstantPredicateProjectionTest: unsupported output column type"); +} + +class CollectingBoolSink : public NLOutputSink { +public: + void appendChunks(std::span chunks, size_t offset, size_t rowCount) override { + for (size_t rowIndex = offset; rowIndex < offset + rowCount; rowIndex++) { + BoolRow& row = _rows.emplace_back(); + for (const Column* column : chunks) { + row.push_back(readBool(column, rowIndex)); + } + } + } + + const BoolRows& getRows() const { return _rows; } + +private: + BoolRows _rows; +}; + +} + +// A projection of a boolean expression over constants alone: the comparison and the +// conjunction are computed once, and the window SKIP 0 LIMIT 1 keeps the single row they +// stand for. +class ConstantPredicateProjectionTest : public TuringTest { +protected: + void initialize() override { + _env = TuringTestEnv::create(fs::Path {_outDir} / "turing"); + _interpreter = std::make_unique(&_env->getSystemManager()); + + SystemAccessor system = _env->getSystemManager().accessUnique(); + Graph* graph = system.createGraph(_graphName); + SimpleGraph::createSimpleGraph(graph); + } + + void expectRows(std::string_view query, const BoolRows& expected) { + CollectingBoolSink sink; + + QueryStatus status; + _interpreter->execute(status, + query, + _graphName, + CommitHash::head(), + ChangeID::head(), + &_env->getMem(), + &sink); + + ASSERT_TRUE(status.isOk()) << "query: " << query << "\nerror: " << status.getError(); + + EXPECT_EQ(sink.getRows(), expected) << "query: " << query; + } + + const std::string _graphName = "simpledb"; + std::unique_ptr _env; + std::unique_ptr _interpreter; +}; + +TEST_F(ConstantPredicateProjectionTest, emitsAConstantComparison) { + const BoolRows expected = {{true}}; + expectRows("RETURN 1 < 2", expected); +} + +TEST_F(ConstantPredicateProjectionTest, emitsAConstantConjunction) { + const BoolRows expected = {{false}}; + expectRows("RETURN true and false", expected); +} + +TEST_F(ConstantPredicateProjectionTest, emitsAConstantComparisonUnderAWindow) { + const BoolRows expected = {{true}}; + expectRows("RETURN 1 < 2 SKIP 0 LIMIT 1", expected); +} + +TEST_F(ConstantPredicateProjectionTest, emitsAConstantConjunctionUnderAWindow) { + const BoolRows expected = {{false}}; + expectRows("RETURN true and false SKIP 0 LIMIT 1", expected); +} + +TEST_F(ConstantPredicateProjectionTest, emitsAConstantNegationUnderAWindow) { + const BoolRows expected = {{false}}; + expectRows("RETURN not true SKIP 0 LIMIT 1", expected); +} + +TEST_F(ConstantPredicateProjectionTest, emitsAConstantComparisonUnderALimit) { + const BoolRows expected = {{true}}; + expectRows("RETURN 1 < 2 LIMIT 1", expected); +} + +TEST_F(ConstantPredicateProjectionTest, emitsAConstantComparisonUnderASkip) { + const BoolRows expected = {{true}}; + expectRows("RETURN 1 < 2 SKIP 0", expected); +} + +TEST_F(ConstantPredicateProjectionTest, emitsNothingWhenTheSkipPassesTheOnlyRow) { + expectRows("RETURN 1 < 2 SKIP 1", {}); +} + +TEST_F(ConstantPredicateProjectionTest, emitsNothingWhenTheWindowSkipsTheOnlyRow) { + expectRows("RETURN 1 < 2 SKIP 1 LIMIT 1", {}); +} + +TEST_F(ConstantPredicateProjectionTest, emitsNothingWhenTheLimitIsZero) { + expectRows("RETURN true and false SKIP 0 LIMIT 0", {}); +} + +TEST_F(ConstantPredicateProjectionTest, emitsEveryConstantPredicateColumnOfTheRow) { + const BoolRows expected = {{true, false}}; + expectRows("RETURN 1 < 2, true and false SKIP 0 LIMIT 1", expected); +} + +int main(int argc, char** argv) { + return turing::test::turingTestMain(argc, argv); +}