mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-01-07 19:57:45 +03:00
LibSQL: Introduce SELECT ... LIMIT xxx OFFSET yyy
What it says on the tin.
This commit is contained in:
parent
7fc901d1b3
commit
6e9f06fc9f
Notes:
sideshowbarker
2024-07-18 04:38:32 +09:00
Author: https://github.com/JanDeVisser Commit: https://github.com/SerenityOS/serenity/commit/6e9f06fc9f4 Pull-request: https://github.com/SerenityOS/serenity/pull/11835 Reviewed-by: https://github.com/trflynn89 ✅
@ -510,4 +510,109 @@ TEST_CASE(select_with_order_by_column_not_in_result)
|
|||||||
EXPECT_EQ(rows[4].row[0].to_string(), "Test_1");
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
* SPDX-License-Identifier: BSD-2-Clause
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <AK/NumericLimits.h>
|
||||||
#include <LibSQL/AST/AST.h>
|
#include <LibSQL/AST/AST.h>
|
||||||
#include <LibSQL/Database.h>
|
#include <LibSQL/Database.h>
|
||||||
#include <LibSQL/Meta.h>
|
#include <LibSQL/Meta.h>
|
||||||
@ -69,7 +70,7 @@ RefPtr<SQLResult> Select::execute(ExecutionContext& context) const
|
|||||||
rows.remove(0);
|
rows.remove(0);
|
||||||
auto table_rows_or_error = context.database->select_all(*table);
|
auto table_rows_or_error = context.database->select_all(*table);
|
||||||
if (table_rows_or_error.is_error())
|
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()) {
|
for (auto& table_row : table_rows_or_error.value()) {
|
||||||
auto new_row = cartesian_row;
|
auto new_row = cartesian_row;
|
||||||
new_row.extend(table_row);
|
new_row.extend(table_row);
|
||||||
@ -114,6 +115,31 @@ RefPtr<SQLResult> Select::execute(ExecutionContext& context) const
|
|||||||
}
|
}
|
||||||
context.result->insert(tuple, sort_key);
|
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;
|
return context.result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,6 +21,7 @@ set(SOURCES
|
|||||||
Row.cpp
|
Row.cpp
|
||||||
Serializer.cpp
|
Serializer.cpp
|
||||||
SQLClient.cpp
|
SQLClient.cpp
|
||||||
|
SQLResult.cpp
|
||||||
TreeNode.cpp
|
TreeNode.cpp
|
||||||
Tuple.cpp
|
Tuple.cpp
|
||||||
Value.cpp
|
Value.cpp
|
||||||
|
31
Userland/Libraries/LibSQL/SQLResult.cpp
Normal file
31
Userland/Libraries/LibSQL/SQLResult.cpp
Normal file
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -111,12 +111,8 @@ class SQLResult : public Core::Object {
|
|||||||
C_OBJECT(SQLResult)
|
C_OBJECT(SQLResult)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
void insert(Tuple const& row, Tuple const& sort_key)
|
void insert(Tuple const& row, Tuple const& sort_key);
|
||||||
{
|
void limit(size_t offset, size_t limit);
|
||||||
m_has_results = true;
|
|
||||||
m_result_set.insert_row(row, sort_key);
|
|
||||||
}
|
|
||||||
|
|
||||||
SQLCommand command() const { return m_command; }
|
SQLCommand command() const { return m_command; }
|
||||||
int updated() const { return m_update_count; }
|
int updated() const { return m_update_count; }
|
||||||
int inserted() const { return m_insert_count; }
|
int inserted() const { return m_insert_count; }
|
||||||
|
Loading…
Reference in New Issue
Block a user