From a37675da1e7e5a6699bbe1531f68572bfe7c815e Mon Sep 17 00:00:00 2001 From: Cyrus Knopf Date: Thu, 20 Aug 2026 16:15:10 +0200 Subject: [PATCH] Fix SKIP and LIMIT --- query/ir/codegen/DBProgramGenerator.cpp | 107 ++++++++++++++++++++++-- test/query/ir/LimitSkipTest.cpp | 15 ++++ 2 files changed, 116 insertions(+), 6 deletions(-) diff --git a/query/ir/codegen/DBProgramGenerator.cpp b/query/ir/codegen/DBProgramGenerator.cpp index c7bd6e0800..c98e223c5e 100644 --- a/query/ir/codegen/DBProgramGenerator.cpp +++ b/query/ir/codegen/DBProgramGenerator.cpp @@ -74,6 +74,8 @@ #include "Symbol.h" #include "SymbolChain.h" +#include "columns/BinaryOperators.h" + #include "BioAssert.h" #include "FatalException.h" #include "TuringException.h" @@ -285,6 +287,91 @@ void flattenConjuncts(const Expr* expr, std::vector& conjuncts) { conjuncts.push_back(expr); } +int64_t applyConstantUnary(UnaryOperator op, int64_t operand) { + switch (op) { + case UnaryOperator::Plus: + return operand; + break; + + case UnaryOperator::Minus: + return -operand; + break; + + default: + throw TuringException(fmt::format("Unsupported unary operator in SKIP/LIMIT expression: {}", + UnaryOperatorDescription::value(op))); + break; + } +} + +int64_t applyConstantBinary(BinaryOperator op, int64_t lhs, int64_t rhs) { + switch (op) { + case BinaryOperator::Add: + return Add {}(lhs, rhs); + break; + + case BinaryOperator::Sub: + return Sub {}(lhs, rhs); + break; + + case BinaryOperator::Mult: + return Mul {}(lhs, rhs); + break; + + case BinaryOperator::Div: + return Div {}(lhs, rhs); + break; + + case BinaryOperator::Mod: + return Mod {}(lhs, rhs); + break; + + default: + throw TuringException(fmt::format("Unsupported operator in SKIP/LIMIT expression: {}", + BinaryOperatorDescription::value(op))); + break; + } +} + +int64_t evaluateConstantInteger(const Expr* expr) { + const Expr::Kind kind = expr->getKind(); + + switch (kind) { + case Expr::Kind::LITERAL: { + const LiteralExpr* literalExpr = static_cast(expr); + const Literal* literal = literalExpr->getLiteral(); + + if (literal->getKind() != Literal::Kind::INTEGER) { + throw TuringException("SKIP/LIMIT expression must evaluate to an integer"); + } + + const IntegerLiteral* integerLiteral = static_cast(literal); + return integerLiteral->getValue(); + } + break; + + case Expr::Kind::UNARY: { + const UnaryExpr* unaryExpr = static_cast(expr); + const int64_t operand = evaluateConstantInteger(unaryExpr->getSubExpr()); + return applyConstantUnary(unaryExpr->getOperator(), operand); + } + break; + + case Expr::Kind::BINARY: { + const BinaryExpr* binaryExpr = static_cast(expr); + const int64_t lhs = evaluateConstantInteger(binaryExpr->getLHS()); + const int64_t rhs = evaluateConstantInteger(binaryExpr->getRHS()); + return applyConstantBinary(binaryExpr->getOperator(), lhs, rhs); + } + break; + + default: + throw TuringException(fmt::format("Unsupported expression in SKIP/LIMIT: {}", + ExprKindDescription::value(kind))); + break; + } +} + } DBProgramGenerator::DBProgramGenerator(mlir::ModuleOp* mainModule) @@ -1333,9 +1420,13 @@ void DBProgramGenerator::generateOutput(const CypherAST* ast) { if (proj->hasSkip()) { const Expr* skipExpr = proj->getSkip()->getExpr(); - const LiteralExpr* litExpr = static_cast(skipExpr); - const IntegerLiteral* intLit = static_cast(litExpr->getLiteral()); - const uint64_t skipCount = static_cast(intLit->getValue()); + const int64_t skipValue = evaluateConstantInteger(skipExpr); + + if (skipValue < 0) { + throw TuringException("SKIP expression must be a non-negative integer"); + } + + const uint64_t skipCount = static_cast(skipValue); llvm::SmallVector skippedItems; llvm::SmallVector skipped; @@ -1356,9 +1447,13 @@ void DBProgramGenerator::generateOutput(const CypherAST* ast) { if (proj->hasLimit()) { const Expr* limitExpr = proj->getLimit()->getExpr(); - const LiteralExpr* litExpr = static_cast(limitExpr); - const IntegerLiteral* intLit = static_cast(litExpr->getLiteral()); - const uint64_t limitCount = static_cast(intLit->getValue()); + const int64_t limitValue = evaluateConstantInteger(limitExpr); + + if (limitValue < 0) { + throw TuringException("LIMIT expression must be a non-negative integer"); + } + + const uint64_t limitCount = static_cast(limitValue); llvm::SmallVector limitedItems; llvm::SmallVector limited; diff --git a/test/query/ir/LimitSkipTest.cpp b/test/query/ir/LimitSkipTest.cpp index 6cb7085f31..c9198b772c 100644 --- a/test/query/ir/LimitSkipTest.cpp +++ b/test/query/ir/LimitSkipTest.cpp @@ -246,6 +246,21 @@ TEST_F(LimitSkipTest, matchedConstantSkipPastTheMatchedRowsEmitsNothing) { expectRows("MATCH (n) RETURN 5 SKIP 20", {}); } +TEST_F(LimitSkipTest, matchSkipConstantExpressionSkipsTheFoldedCount) { + const ValueRows expected = {{6}}; + expectRows("MATCH (n) RETURN n SKIP 5 + 1 LIMIT 1", expected); +} + +TEST_F(LimitSkipTest, matchLimitConstantExpressionKeepsTheFoldedCount) { + const ValueRows expected = {{1}, {2}}; + expectRows("MATCH (n) RETURN n SKIP 1 LIMIT 1 + 1", expected); +} + +TEST_F(LimitSkipTest, matchedConstantSkipMultiplicationKeepsTheSurvivingRows) { + const ValueRows expected = {{5}, {5}}; + expectRows("MATCH (n) RETURN 5 SKIP 8 * 2", expected); +} + int main(int argc, char** argv) { return turing::test::turingTestMain(argc, argv); }