瀏覽代碼

LibSQL: Introduce SELECT ... LIMIT xxx OFFSET yyy

What it says on the tin.
Jan de Visser 3 年之前
父節點
當前提交
6e9f06fc9f

+ 105 - 0
Tests/LibSQL/TestSqlStatementExecution.cpp

@@ -510,4 +510,109 @@ TEST_CASE(select_with_order_by_column_not_in_result)
     EXPECT_EQ(rows[4].row[0].to_string(), "Test_1");
 }
 
+TEST_CASE(select_with_limit)
+{
+    ScopeGuard guard([]() { unlink(db_name); });
+    auto database = SQL::Database::construct(db_name);
+    EXPECT(!database->open().is_error());
+    create_table(database);
+    for (auto count = 0; count < 100; count++) {
+        auto result = execute(database,
+            String::formatted("INSERT INTO TestSchema.TestTable ( TextColumn, IntColumn ) VALUES ( 'Test_{}', {} );", count, count));
+        EXPECT(result->error().code == SQL::SQLErrorCode::NoError);
+        EXPECT(result->inserted() == 1);
+    }
+    auto result = execute(database, "SELECT TextColumn, IntColumn FROM TestSchema.TestTable LIMIT 10;");
+    EXPECT(result->error().code == SQL::SQLErrorCode::NoError);
+    EXPECT(result->has_results());
+    auto rows = result->results();
+    EXPECT_EQ(rows.size(), 10u);
+}
+
+TEST_CASE(select_with_limit_and_offset)
+{
+    ScopeGuard guard([]() { unlink(db_name); });
+    auto database = SQL::Database::construct(db_name);
+    EXPECT(!database->open().is_error());
+    create_table(database);
+    for (auto count = 0; count < 100; count++) {
+        auto result = execute(database,
+            String::formatted("INSERT INTO TestSchema.TestTable ( TextColumn, IntColumn ) VALUES ( 'Test_{}', {} );", count, count));
+        EXPECT(result->error().code == SQL::SQLErrorCode::NoError);
+        EXPECT(result->inserted() == 1);
+    }
+    auto result = execute(database, "SELECT TextColumn, IntColumn FROM TestSchema.TestTable LIMIT 10 OFFSET 10;");
+    EXPECT(result->error().code == SQL::SQLErrorCode::NoError);
+    EXPECT(result->has_results());
+    auto rows = result->results();
+    EXPECT_EQ(rows.size(), 10u);
+}
+
+TEST_CASE(select_with_order_limit_and_offset)
+{
+    ScopeGuard guard([]() { unlink(db_name); });
+    auto database = SQL::Database::construct(db_name);
+    EXPECT(!database->open().is_error());
+    create_table(database);
+    for (auto count = 0; count < 100; count++) {
+        auto result = execute(database,
+            String::formatted("INSERT INTO TestSchema.TestTable ( TextColumn, IntColumn ) VALUES ( 'Test_{}', {} );", count, count));
+        EXPECT(result->error().code == SQL::SQLErrorCode::NoError);
+        EXPECT(result->inserted() == 1);
+    }
+    auto result = execute(database, "SELECT TextColumn, IntColumn FROM TestSchema.TestTable ORDER BY IntColumn LIMIT 10 OFFSET 10;");
+    EXPECT(result->error().code == SQL::SQLErrorCode::NoError);
+    EXPECT(result->has_results());
+    auto rows = result->results();
+    EXPECT_EQ(rows.size(), 10u);
+    EXPECT_EQ(rows[0].row[1].to_int().value(), 10);
+    EXPECT_EQ(rows[1].row[1].to_int().value(), 11);
+    EXPECT_EQ(rows[2].row[1].to_int().value(), 12);
+    EXPECT_EQ(rows[3].row[1].to_int().value(), 13);
+    EXPECT_EQ(rows[4].row[1].to_int().value(), 14);
+    EXPECT_EQ(rows[5].row[1].to_int().value(), 15);
+    EXPECT_EQ(rows[6].row[1].to_int().value(), 16);
+    EXPECT_EQ(rows[7].row[1].to_int().value(), 17);
+    EXPECT_EQ(rows[8].row[1].to_int().value(), 18);
+    EXPECT_EQ(rows[9].row[1].to_int().value(), 19);
+}
+
+TEST_CASE(select_with_limit_out_of_bounds)
+{
+    ScopeGuard guard([]() { unlink(db_name); });
+    auto database = SQL::Database::construct(db_name);
+    EXPECT(!database->open().is_error());
+    create_table(database);
+    for (auto count = 0; count < 100; count++) {
+        auto result = execute(database,
+            String::formatted("INSERT INTO TestSchema.TestTable ( TextColumn, IntColumn ) VALUES ( 'Test_{}', {} );", count, count));
+        EXPECT(result->error().code == SQL::SQLErrorCode::NoError);
+        EXPECT(result->inserted() == 1);
+    }
+    auto result = execute(database, "SELECT TextColumn, IntColumn FROM TestSchema.TestTable LIMIT 500;");
+    EXPECT(result->error().code == SQL::SQLErrorCode::NoError);
+    EXPECT(result->has_results());
+    auto rows = result->results();
+    EXPECT_EQ(rows.size(), 100u);
+}
+
+TEST_CASE(select_with_offset_out_of_bounds)
+{
+    ScopeGuard guard([]() { unlink(db_name); });
+    auto database = SQL::Database::construct(db_name);
+    EXPECT(!database->open().is_error());
+    create_table(database);
+    for (auto count = 0; count < 100; count++) {
+        auto result = execute(database,
+            String::formatted("INSERT INTO TestSchema.TestTable ( TextColumn, IntColumn ) VALUES ( 'Test_{}', {} );", count, count));
+        EXPECT(result->error().code == SQL::SQLErrorCode::NoError);
+        EXPECT(result->inserted() == 1);
+    }
+    auto result = execute(database, "SELECT TextColumn, IntColumn FROM TestSchema.TestTable LIMIT 10 OFFSET 200;");
+    EXPECT(result->error().code == SQL::SQLErrorCode::NoError);
+    EXPECT(result->has_results());
+    auto rows = result->results();
+    EXPECT_EQ(rows.size(), 0u);
+}
+
 }

+ 27 - 1
Userland/Libraries/LibSQL/AST/Select.cpp

@@ -4,6 +4,7 @@
  * SPDX-License-Identifier: BSD-2-Clause
  */
 
+#include <AK/NumericLimits.h>
 #include <LibSQL/AST/AST.h>
 #include <LibSQL/Database.h>
 #include <LibSQL/Meta.h>
@@ -69,7 +70,7 @@ RefPtr<SQLResult> Select::execute(ExecutionContext& context) const
             rows.remove(0);
             auto table_rows_or_error = context.database->select_all(*table);
             if (table_rows_or_error.is_error())
-                return SQLResult::construct(SQLCommand::Create, SQLErrorCode::InternalError, table_rows_or_error.error());
+                return SQLResult::construct(SQLCommand::Select, SQLErrorCode::InternalError, table_rows_or_error.error());
             for (auto& table_row : table_rows_or_error.value()) {
                 auto new_row = cartesian_row;
                 new_row.extend(table_row);
@@ -114,6 +115,31 @@ RefPtr<SQLResult> Select::execute(ExecutionContext& context) const
         }
         context.result->insert(tuple, sort_key);
     }
+
+    if (m_limit_clause != nullptr) {
+        size_t limit_value = NumericLimits<size_t>::max();
+        auto limit = m_limit_clause->limit_expression()->evaluate(context);
+        if (!limit.is_null()) {
+            auto limit_value_maybe = limit.to_u32();
+            if (!limit_value_maybe.has_value()) {
+                return SQLResult::construct(SQLCommand::Select, SQLErrorCode::SyntaxError, "LIMIT clause must evaluate to an integer value");
+            }
+            limit_value = limit_value_maybe.value();
+        }
+        size_t offset_value = 0;
+        if (m_limit_clause->offset_expression() != nullptr) {
+            auto offset = m_limit_clause->offset_expression()->evaluate(context);
+            if (!offset.is_null()) {
+                auto offset_value_maybe = offset.to_u32();
+                if (!offset_value_maybe.has_value()) {
+                    return SQLResult::construct(SQLCommand::Select, SQLErrorCode::SyntaxError, "OFFSET clause must evaluate to an integer value");
+                }
+                offset_value = offset_value_maybe.value();
+            }
+        }
+        context.result->limit(offset_value, limit_value);
+    }
+
     return context.result;
 }
 

+ 1 - 0
Userland/Libraries/LibSQL/CMakeLists.txt

@@ -21,6 +21,7 @@ set(SOURCES
     Row.cpp
     Serializer.cpp
     SQLClient.cpp
+    SQLResult.cpp
     TreeNode.cpp
     Tuple.cpp
     Value.cpp

+ 31 - 0
Userland/Libraries/LibSQL/SQLResult.cpp

@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2022, Jan de Visser <jan@de-visser.net>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <LibSQL/SQLResult.h>
+
+namespace SQL {
+
+void SQLResult::insert(Tuple const& row, Tuple const& sort_key)
+{
+    m_has_results = true;
+    m_result_set.insert_row(row, sort_key);
+}
+
+void SQLResult::limit(size_t offset, size_t limit)
+{
+    if (offset > 0) {
+        if (offset > m_result_set.size()) {
+            m_result_set.clear();
+            return;
+        }
+        m_result_set.remove(0, offset);
+    }
+    if (m_result_set.size() > limit) {
+        m_result_set.remove(limit, m_result_set.size() - limit);
+    }
+}
+
+}

+ 2 - 6
Userland/Libraries/LibSQL/SQLResult.h

@@ -111,12 +111,8 @@ class SQLResult : public Core::Object {
     C_OBJECT(SQLResult)
 
 public:
-    void insert(Tuple const& row, Tuple const& sort_key)
-    {
-        m_has_results = true;
-        m_result_set.insert_row(row, sort_key);
-    }
-
+    void insert(Tuple const& row, Tuple const& sort_key);
+    void limit(size_t offset, size_t limit);
     SQLCommand command() const { return m_command; }
     int updated() const { return m_update_count; }
     int inserted() const { return m_insert_count; }