Everywhere: Remove LibSQL, SQLServer, and the sql REPL :^)

It is now entirely unused and replaced by sqlite3.
This commit is contained in:
Timothy Flynn 2024-06-04 16:40:48 -04:00 committed by Tim Flynn
parent 30e745ffa7
commit 8362c073f3
Notes: sideshowbarker 2024-07-17 00:37:23 +09:00
100 changed files with 8 additions and 14561 deletions

3
.github/CODEOWNERS vendored
View file

@ -12,7 +12,6 @@
/Userland/Libraries/LibJS/Runtime/Intl @trflynn89
/Userland/Libraries/LibLocale @trflynn89
/Userland/Libraries/LibRegex @alimpfard
/Userland/Libraries/LibSQL @GMTA @trflynn89
/Userland/Libraries/LibTLS @alimpfard
/Userland/Libraries/LibTimeZone @trflynn89
/Userland/Libraries/LibUnicode @trflynn89
@ -22,11 +21,9 @@
/Userland/Libraries/LibWeb/WebDriver @trflynn89
/Userland/Libraries/LibXML @alimpfard
/Userland/Services/RequestServer @alimpfard
/Userland/Services/SQLServer @trflynn89
/Userland/Services/WebDriver @trflynn89
/Userland/Utilities/gzip.cpp @timschumi
/Userland/Utilities/lzcat.cpp @timschumi
/Userland/Utilities/sql.cpp @trflynn89
/Userland/Utilities/tar.cpp @timschumi
/Userland/Utilities/unzip.cpp @timschumi
/Userland/Utilities/wasm.cpp @alimpfard

View file

@ -442,14 +442,6 @@
# cmakedefine01 SPICE_AGENT_DEBUG
#endif
#ifndef SQL_DEBUG
# cmakedefine01 SQL_DEBUG
#endif
#ifndef SQLSERVER_DEBUG
# cmakedefine01 SQLSERVER_DEBUG
#endif
#ifndef SYNTAX_HIGHLIGHTING_DEBUG
# cmakedefine01 SYNTAX_HIGHLIGHTING_DEBUG
#endif

View file

@ -71,7 +71,7 @@ target_sources(ladybird PUBLIC FILE_SET ladybird TYPE HEADERS
BASE_DIRS ${LADYBIRD_SOURCE_DIR}
FILES ${LADYBIRD_HEADERS}
)
target_link_libraries(ladybird PRIVATE AK LibCore LibFileSystem LibGfx LibImageDecoderClient LibIPC LibJS LibMain LibSQL LibWeb LibWebView LibProtocol LibURL)
target_link_libraries(ladybird PRIVATE AK LibCore LibFileSystem LibGfx LibImageDecoderClient LibIPC LibJS LibMain LibWeb LibWebView LibProtocol LibURL)
target_include_directories(ladybird PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
target_include_directories(ladybird PRIVATE ${LADYBIRD_SOURCE_DIR}/Userland/)
@ -99,7 +99,7 @@ add_executable(headless-browser
target_include_directories(headless-browser PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
target_include_directories(headless-browser PRIVATE ${LADYBIRD_SOURCE_DIR}/Userland/)
target_link_libraries(headless-browser PRIVATE AK LibCore LibWeb LibWebView LibWebSocket LibCrypto LibFileSystem LibHTTP LibImageDecoderClient LibJS LibGfx LibMain LibSQL LibTLS LibIPC LibDiff LibProtocol LibURL)
target_link_libraries(headless-browser PRIVATE AK LibCore LibWeb LibWebView LibWebSocket LibCrypto LibFileSystem LibHTTP LibImageDecoderClient LibJS LibGfx LibMain LibTLS LibIPC LibDiff LibProtocol LibURL)
add_custom_target(run
COMMAND "${CMAKE_COMMAND}" -E env "LADYBIRD_SOURCE_DIR=${LADYBIRD_SOURCE_DIR}" "$<TARGET_FILE:ladybird>" $ENV{LAGOM_ARGS}
@ -114,12 +114,11 @@ add_custom_target(debug-ladybird
add_subdirectory(ImageDecoder)
add_subdirectory(RequestServer)
add_subdirectory(SQLServer)
add_subdirectory(WebContent)
add_subdirectory(WebDriver)
add_subdirectory(WebWorker)
set(ladybird_helper_processes ImageDecoder RequestServer SQLServer WebContent WebWorker)
set(ladybird_helper_processes ImageDecoder RequestServer WebContent WebWorker)
add_dependencies(ladybird ${ladybird_helper_processes})
add_dependencies(headless-browser ${ladybird_helper_processes})

View file

@ -175,18 +175,6 @@ ErrorOr<NonnullRefPtr<Protocol::RequestClient>> launch_request_server_process(Re
return launch_generic_server_process<Protocol::RequestClient>("RequestServer"sv, candidate_request_server_paths, move(arguments), RegisterWithProcessManager::Yes, Ladybird::EnableCallgrindProfiling::No);
}
ErrorOr<NonnullRefPtr<SQL::SQLClient>> launch_sql_server_process(ReadonlySpan<ByteString> candidate_sql_server_paths)
{
Vector<ByteString> arguments;
if (auto server = mach_server_name(); server.has_value()) {
arguments.append("--mach-server-name"sv);
arguments.append(server.value());
}
return launch_singleton_server_process<SQL::SQLClient>("SQLServer"sv, candidate_sql_server_paths, arguments, RegisterWithProcessManager::Yes);
}
ErrorOr<IPC::File> connect_new_request_server_client(Protocol::RequestClient& client)
{
auto new_socket = client.send_sync_but_allow_failure<Messages::RequestServer::ConnectNewClient>();

View file

@ -13,7 +13,6 @@
#include <AK/StringView.h>
#include <LibImageDecoderClient/Client.h>
#include <LibProtocol/RequestClient.h>
#include <LibSQL/SQLClient.h>
#include <LibWeb/Worker/WebWorkerClient.h>
#include <LibWebView/ViewImplementation.h>
#include <LibWebView/WebContentClient.h>
@ -27,6 +26,5 @@ ErrorOr<NonnullRefPtr<WebView::WebContentClient>> launch_web_content_process(
ErrorOr<NonnullRefPtr<ImageDecoderClient::Client>> launch_image_decoder_process(ReadonlySpan<ByteString> candidate_image_decoder_paths);
ErrorOr<NonnullRefPtr<Web::HTML::WebWorkerClient>> launch_web_worker_process(ReadonlySpan<ByteString> candidate_web_worker_paths, NonnullRefPtr<Protocol::RequestClient>);
ErrorOr<NonnullRefPtr<Protocol::RequestClient>> launch_request_server_process(ReadonlySpan<ByteString> candidate_request_server_paths, StringView serenity_resource_root, Vector<ByteString> const& certificates);
ErrorOr<NonnullRefPtr<SQL::SQLClient>> launch_sql_server_process(ReadonlySpan<ByteString> candidate_sql_server_paths);
ErrorOr<IPC::File> connect_new_request_server_client(Protocol::RequestClient&);

View file

@ -1,14 +0,0 @@
set(SQL_SERVER_SOURCE_DIR ${LADYBIRD_SOURCE_DIR}/Userland/Services/SQLServer)
set(SQL_SERVER_SOURCES
${SQL_SERVER_SOURCE_DIR}/ConnectionFromClient.cpp
${SQL_SERVER_SOURCE_DIR}/DatabaseConnection.cpp
${SQL_SERVER_SOURCE_DIR}/SQLStatement.cpp
main.cpp
)
add_executable(SQLServer ${SQL_SERVER_SOURCES})
target_include_directories(SQLServer PRIVATE ${LADYBIRD_SOURCE_DIR}/Userland/Services/)
target_include_directories(SQLServer PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/..)
target_link_libraries(SQLServer PRIVATE LibCore LibFileSystem LibIPC LibSQL LibMain)

View file

@ -1,60 +0,0 @@
/*
* Copyright (c) 2022-2024, Tim Flynn <trflynn89@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/ByteString.h>
#include <LibCore/ArgsParser.h>
#include <LibCore/Directory.h>
#include <LibCore/EventLoop.h>
#include <LibCore/StandardPaths.h>
#include <LibIPC/MultiServer.h>
#include <LibMain/Main.h>
#include <SQLServer/ConnectionFromClient.h>
#if defined(AK_OS_MACOS)
# include <LibCore/Platform/ProcessStatisticsMach.h>
#endif
ErrorOr<int> serenity_main(Main::Arguments arguments)
{
AK::set_rich_debug_enabled(true);
StringView pid_file;
StringView mach_server_name;
Core::ArgsParser args_parser;
args_parser.add_option(pid_file, "Path to the PID file for the SQLServer singleton process", "pid-file", 'p', "pid_file");
args_parser.add_option(mach_server_name, "Mach server name", "mach-server-name", 0, "mach_server_name");
args_parser.parse(arguments);
VERIFY(!pid_file.is_empty());
auto database_path = ByteString::formatted("{}/Ladybird", Core::StandardPaths::data_directory());
TRY(Core::Directory::create(database_path, Core::Directory::CreateDirectories::Yes));
Core::EventLoop loop;
#if defined(AK_OS_MACOS)
if (!mach_server_name.is_empty())
Core::Platform::register_with_mach_server(mach_server_name);
#endif
auto server = TRY(IPC::MultiServer<SQLServer::ConnectionFromClient>::try_create());
u64 connection_count { 0 };
server->on_new_client = [&](auto& client) {
client.set_database_path(database_path);
++connection_count;
client.on_disconnect = [&]() {
if (--connection_count == 0) {
MUST(Core::System::unlink(pid_file));
loop.quit(0);
}
};
};
return loop.exec();
}

View file

@ -70,7 +70,7 @@ else()
add_executable(WebContent main.cpp)
endif()
target_link_libraries(WebContent PRIVATE webcontent LibSQL LibURL)
target_link_libraries(WebContent PRIVATE webcontent LibURL)
target_sources(webcontent PUBLIC FILE_SET ladybird TYPE HEADERS
BASE_DIRS ${LADYBIRD_SOURCE_DIR}

View file

@ -20,7 +20,7 @@ add_library(webworker STATIC ${WEBWORKER_SOURCES})
target_include_directories(webworker PRIVATE ${LADYBIRD_SOURCE_DIR}/Userland/Services/)
target_include_directories(webworker PRIVATE ${LADYBIRD_SOURCE_DIR}/Userland/)
target_include_directories(webworker PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/..)
target_link_libraries(webworker PUBLIC LibCore LibFileSystem LibGfx LibIPC LibJS LibProtocol LibWeb LibWebView LibLocale LibImageDecoderClient LibMain LibSQL LibURL)
target_link_libraries(webworker PUBLIC LibCore LibFileSystem LibGfx LibIPC LibJS LibProtocol LibWeb LibWebView LibLocale LibImageDecoderClient LibMain LibURL)
add_executable(WebWorker main.cpp)
target_include_directories(WebWorker PRIVATE ${LADYBIRD_SOURCE_DIR}/Userland/)

View file

@ -174,8 +174,6 @@ set(SOCKET_DEBUG ON)
set(SOLITAIRE_DEBUG ON)
set(SPAM_DEBUG ON)
set(SPICE_AGENT_DEBUG ON)
set(SQL_DEBUG ON)
set(SQLSERVER_DEBUG ON)
set(STORAGE_DEVICE_DEBUG ON)
set(SYNTAX_HIGHLIGHTING_DEBUG ON)
set(SYSCALL_1_DEBUG ON)

View file

@ -435,7 +435,6 @@ if (BUILD_LAGOM)
Protocol
Regex
RIFF
SQL
Syntax
TextCodec
Threading
@ -506,7 +505,6 @@ if (BUILD_LAGOM)
lagom_utility(lzcat SOURCES ../../Userland/Utilities/lzcat.cpp LIBS LibCompress LibMain)
lagom_utility(sql SOURCES ../../Userland/Utilities/sql.cpp LIBS LibFileSystem LibIPC LibLine LibMain LibSQL)
lagom_utility(tar SOURCES ../../Userland/Utilities/tar.cpp LIBS LibArchive LibCompress LibFileSystem LibMain)
lagom_utility(test262-runner SOURCES ../../Tests/LibJS/test262-runner.cpp LIBS LibJS LibFileSystem)
@ -556,7 +554,6 @@ if (BUILD_LAGOM)
LibCompress
LibGfx
LibLocale
LibSQL
LibTest
LibTextCodec
LibTTF

View file

@ -1,17 +0,0 @@
/*
* Copyright (c) 2021, Luke Wilde <lukew@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibSQL/AST/Lexer.h>
#include <LibSQL/AST/Parser.h>
#include <stdio.h>
extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size)
{
AK::set_debug_enabled(false);
auto parser = SQL::AST::Parser(SQL::AST::Lexer({ data, size }));
[[maybe_unused]] auto statement = parser.next_statement();
return 0;
}

View file

@ -42,7 +42,6 @@ set(FUZZER_TARGETS
SHA256
SHA384
SHA512
SQLParser
Tar
TextDecoder
TGALoader
@ -108,7 +107,6 @@ set(FUZZER_DEPENDENCIES_SHA1 LibCrypto)
set(FUZZER_DEPENDENCIES_SHA256 LibCrypto)
set(FUZZER_DEPENDENCIES_SHA384 LibCrypto)
set(FUZZER_DEPENDENCIES_SHA512 LibCrypto)
set(FUZZER_DEPENDENCIES_SQLParser LibSQL)
set(FUZZER_DEPENDENCIES_Tar LibArchive)
set(FUZZER_DEPENDENCIES_TextDecoder LibTextCodec)
set(FUZZER_DEPENDENCIES_TGALoader LibGfx)

View file

@ -10,7 +10,7 @@ get_lagom_executables() {
# If the Lagom binary directory is missing, this creates an empty list instead of erroring.
# Known false positives need to be filtered manually; please add new ones.
find "${LADYBIRD_SOURCE_DIR}/Build/lagom" -mindepth 1 -type f -executable -not -name '*.so*' \
-not \( -name 'SQLServer' -o -name 'a.out' -o -name 'CMake*.bin' \) \
-not \( -name 'a.out' -o -name 'CMake*.bin' \) \
-printf '%f\n' 2>/dev/null || true
}

View file

@ -327,8 +327,6 @@ write_cmake_config("ak_debug_gen") {
"SOLITAIRE_DEBUG=",
"SPAM_DEBUG=",
"SPICE_AGENT_DEBUG=",
"SQLSERVER_DEBUG=",
"SQL_DEBUG=",
"SYNTAX_HIGHLIGHTING_DEBUG=",
"SYSCALL_1_DEBUG=",
"SYSTEMSERVER_DEBUG=",

View file

@ -54,7 +54,6 @@ config("ladybird_config") {
ladybird_helper_processes = [
"ImageDecoder",
"RequestServer",
"SQLServer",
"WebContent",
"WebWorker",
]
@ -72,7 +71,6 @@ executable("ladybird_executable") {
"//Userland/Libraries/LibJS",
"//Userland/Libraries/LibMain",
"//Userland/Libraries/LibProtocol",
"//Userland/Libraries/LibSQL",
"//Userland/Libraries/LibURL",
"//Userland/Libraries/LibWeb",
"//Userland/Libraries/LibWebView",
@ -188,7 +186,6 @@ executable("headless-browser") {
"//Userland/Libraries/LibJS",
"//Userland/Libraries/LibMain",
"//Userland/Libraries/LibProtocol",
"//Userland/Libraries/LibSQL",
"//Userland/Libraries/LibTLS",
"//Userland/Libraries/LibURL",
"//Userland/Libraries/LibWeb",
@ -343,7 +340,6 @@ if (current_os != "mac") {
":ladybird_executable",
"ImageDecoder",
"RequestServer",
"SQLServer",
"WebContent",
"WebDriver",
"WebWorker",
@ -354,7 +350,6 @@ if (current_os != "mac") {
"$root_out_dir/bin/headless-browser",
"$root_out_dir/libexec/ImageDecoder",
"$root_out_dir/libexec/RequestServer",
"$root_out_dir/libexec/SQLServer",
"$root_out_dir/libexec/WebContent",
"$root_out_dir/libexec/WebWorker",
]
@ -381,7 +376,6 @@ if (current_os != "mac") {
"//Userland/Libraries/LibProtocol",
"//Userland/Libraries/LibRIFF",
"//Userland/Libraries/LibRegex",
"//Userland/Libraries/LibSQL",
"//Userland/Libraries/LibSyntax",
"//Userland/Libraries/LibTLS",
"//Userland/Libraries/LibTextCodec",
@ -414,7 +408,6 @@ if (current_os != "mac") {
"$root_out_dir/lib/liblagom-protocol.dylib",
"$root_out_dir/lib/liblagom-regex.dylib",
"$root_out_dir/lib/liblagom-riff.dylib",
"$root_out_dir/lib/liblagom-sql.dylib",
"$root_out_dir/lib/liblagom-syntax.dylib",
"$root_out_dir/lib/liblagom-textcodec.dylib",
"$root_out_dir/lib/liblagom-threading.dylib",

View file

@ -1,21 +0,0 @@
executable("SQLServer") {
configs += [ "//Ladybird:ladybird_config" ]
include_dirs = [
"//Userland/Libraries",
"//Userland/Services",
]
deps = [
"//AK",
"//Userland/Libraries/LibCore",
"//Userland/Libraries/LibIPC",
"//Userland/Libraries/LibMain",
"//Userland/Libraries/LibSQL",
]
sources = [
"//Userland/Services/SQLServer/ConnectionFromClient.cpp",
"//Userland/Services/SQLServer/DatabaseConnection.cpp",
"//Userland/Services/SQLServer/SQLStatement.cpp",
"main.cpp",
]
output_dir = "$root_out_dir/libexec"
}

View file

@ -47,7 +47,6 @@ executable("WebContent") {
"//Userland/Libraries/LibJS",
"//Userland/Libraries/LibMain",
"//Userland/Libraries/LibProtocol",
"//Userland/Libraries/LibSQL",
"//Userland/Libraries/LibURL",
"//Userland/Libraries/LibWeb",
"//Userland/Libraries/LibWebSocket",

View file

@ -15,7 +15,6 @@ executable("WebWorker") {
"//Userland/Libraries/LibLocale",
"//Userland/Libraries/LibMain",
"//Userland/Libraries/LibProtocol",
"//Userland/Libraries/LibSQL",
"//Userland/Libraries/LibURL",
"//Userland/Libraries/LibWeb",
"//Userland/Libraries/LibWeb:WebWorkerClientEndpoint",

View file

@ -1,73 +0,0 @@
import("//Meta/gn/build/compiled_action.gni")
compiled_action("SQLClientEndpoint") {
tool = "//Meta/Lagom/Tools/CodeGenerators/IPCCompiler"
inputs = [ "//Userland/Services/SQLServer/SQLClient.ipc" ]
outputs = [ "$root_gen_dir/SQLServer/SQLClientEndpoint.h" ]
args = [
rebase_path(inputs[0], root_build_dir),
"-o",
rebase_path(outputs[0], root_build_dir),
]
}
compiled_action("SQLServerEndpoint") {
tool = "//Meta/Lagom/Tools/CodeGenerators/IPCCompiler"
inputs = [ "//Userland/Services/SQLServer/SQLServer.ipc" ]
outputs = [ "$root_gen_dir/SQLServer/SQLServerEndpoint.h" ]
args = [
rebase_path(inputs[0], root_build_dir),
"-o",
rebase_path(outputs[0], root_build_dir),
]
}
shared_library("LibSQL") {
output_name = "sql"
include_dirs = [
"//Userland/Libraries",
"//Userland",
]
sources = [
"AST/CreateSchema.cpp",
"AST/CreateTable.cpp",
"AST/Delete.cpp",
"AST/Describe.cpp",
"AST/Expression.cpp",
"AST/Insert.cpp",
"AST/Lexer.cpp",
"AST/Parser.cpp",
"AST/Select.cpp",
"AST/Statement.cpp",
"AST/SyntaxHighlighter.cpp",
"AST/Token.cpp",
"AST/Update.cpp",
"BTree.cpp",
"BTreeIterator.cpp",
"Database.cpp",
"Heap.cpp",
"Index.cpp",
"Key.cpp",
"Meta.cpp",
"Result.cpp",
"ResultSet.cpp",
"Row.cpp",
"SQLClient.cpp",
"Serializer.cpp",
"TreeNode.cpp",
"Tuple.cpp",
"Value.cpp",
]
sources += get_target_outputs(":SQLClientEndpoint") +
get_target_outputs(":SQLServerEndpoint")
deps = [
":SQLClientEndpoint",
":SQLServerEndpoint",
"//AK",
"//Userland/Libraries/LibCore",
"//Userland/Libraries/LibFileSystem",
"//Userland/Libraries/LibIPC",
"//Userland/Libraries/LibRegex",
"//Userland/Libraries/LibSyntax",
]
}

View file

@ -136,7 +136,6 @@ shared_library("LibWebView") {
"//Userland/Libraries/LibIPC",
"//Userland/Libraries/LibJS",
"//Userland/Libraries/LibProtocol",
"//Userland/Libraries/LibSQL",
"//Userland/Libraries/LibURL",
"//Userland/Libraries/LibWeb",
]

View file

@ -11,7 +11,7 @@
We aim to build a complete, usable browser for the modern web.
Ladybird uses a multi-process architecture with a main UI process, several WebContent renderer processes,
an ImageDecoder process, a RequestServer process, and a SQLServer process for holding cookies.
an ImageDecoder process, and a RequestServer process.
Image decoding and network connections are done out of process to be more robust against malicious content.
Each tab has its own renderer process, which is sandboxed from the rest of the system.

View file

@ -7,7 +7,6 @@ add_subdirectory(LibGfx)
add_subdirectory(LibJS)
add_subdirectory(LibLocale)
add_subdirectory(LibRegex)
add_subdirectory(LibSQL)
add_subdirectory(LibTest)
add_subdirectory(LibTextCodec)
add_subdirectory(LibThreading)

View file

@ -1,13 +0,0 @@
set(TEST_SOURCES
TestSqlBtreeIndex.cpp
TestSqlDatabase.cpp
TestSqlExpressionParser.cpp
TestSqlHeap.cpp
TestSqlStatementExecution.cpp
TestSqlStatementParser.cpp
TestSqlValueAndTuple.cpp
)
foreach(source IN LISTS TEST_SOURCES)
serenity_test("${source}" LibSQL LIBS LibSQL LibIPC)
endforeach()

View file

@ -1,319 +0,0 @@
/*
* Copyright (c) 2021, Jan de Visser <jan@de-visser.net>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <unistd.h>
#include <AK/ScopeGuard.h>
#include <LibSQL/BTree.h>
#include <LibSQL/Heap.h>
#include <LibSQL/Key.h>
#include <LibSQL/Meta.h>
#include <LibSQL/TupleDescriptor.h>
#include <LibSQL/Value.h>
#include <LibTest/TestCase.h>
constexpr static int keys[] = {
39,
87,
77,
42,
98,
40,
53,
8,
37,
12,
90,
72,
73,
11,
88,
22,
10,
82,
25,
61,
97,
18,
60,
68,
21,
3,
58,
29,
13,
17,
89,
81,
16,
64,
5,
41,
36,
91,
38,
24,
32,
50,
34,
94,
49,
47,
1,
6,
44,
76,
};
constexpr static u32 pointers[] = {
92,
4,
50,
47,
68,
73,
24,
28,
50,
93,
60,
36,
92,
72,
53,
26,
91,
84,
25,
43,
88,
12,
62,
35,
96,
27,
96,
27,
99,
30,
21,
89,
54,
60,
37,
68,
35,
55,
80,
2,
33,
26,
93,
70,
45,
44,
3,
66,
75,
4,
};
NonnullRefPtr<SQL::BTree> setup_btree(SQL::Serializer&);
void insert_and_get_to_and_from_btree(int);
void insert_into_and_scan_btree(int);
NonnullRefPtr<SQL::BTree> setup_btree(SQL::Serializer& serializer)
{
NonnullRefPtr<SQL::TupleDescriptor> tuple_descriptor = adopt_ref(*new SQL::TupleDescriptor);
tuple_descriptor->append({ "schema", "table", "key_value", SQL::SQLType::Integer, SQL::Order::Ascending });
auto root_pointer = serializer.heap().user_value(0);
if (!root_pointer) {
root_pointer = serializer.heap().request_new_block_index();
serializer.heap().set_user_value(0, root_pointer);
}
auto btree = MUST(SQL::BTree::create(serializer, tuple_descriptor, true, root_pointer));
btree->on_new_root = [&]() {
serializer.heap().set_user_value(0, btree->root());
};
return btree;
}
void insert_and_get_to_and_from_btree(int num_keys)
{
ScopeGuard guard([]() { unlink("/tmp/test.db"); });
{
auto heap = MUST(SQL::Heap::create("/tmp/test.db"));
TRY_OR_FAIL(heap->open());
SQL::Serializer serializer(heap);
auto btree = setup_btree(serializer);
for (auto ix = 0; ix < num_keys; ix++) {
SQL::Key k(btree->descriptor());
k[0] = keys[ix];
k.set_block_index(pointers[ix]);
btree->insert(k);
}
#ifdef LIST_TREE
btree->list_tree();
#endif
}
{
auto heap = MUST(SQL::Heap::create("/tmp/test.db"));
TRY_OR_FAIL(heap->open());
SQL::Serializer serializer(heap);
auto btree = setup_btree(serializer);
for (auto ix = 0; ix < num_keys; ix++) {
SQL::Key k(btree->descriptor());
k[0] = keys[ix];
auto pointer_opt = btree->get(k);
VERIFY(pointer_opt.has_value());
EXPECT_EQ(pointer_opt.value(), pointers[ix]);
}
}
}
void insert_into_and_scan_btree(int num_keys)
{
ScopeGuard guard([]() { unlink("/tmp/test.db"); });
{
auto heap = MUST(SQL::Heap::create("/tmp/test.db"));
TRY_OR_FAIL(heap->open());
SQL::Serializer serializer(heap);
auto btree = setup_btree(serializer);
for (auto ix = 0; ix < num_keys; ix++) {
SQL::Key k(btree->descriptor());
k[0] = keys[ix];
k.set_block_index(pointers[ix]);
btree->insert(k);
}
#ifdef LIST_TREE
btree->list_tree();
#endif
}
{
auto heap = MUST(SQL::Heap::create("/tmp/test.db"));
TRY_OR_FAIL(heap->open());
SQL::Serializer serializer(heap);
auto btree = setup_btree(serializer);
int count = 0;
SQL::Tuple prev;
for (auto iter = btree->begin(); !iter.is_end(); iter++, count++) {
auto key = (*iter);
if (prev.size())
EXPECT(prev < key);
auto key_value = key[0].to_int<i32>();
for (auto ix = 0; ix < num_keys; ix++) {
if (keys[ix] == key_value) {
EXPECT_EQ(key.block_index(), pointers[ix]);
break;
}
}
prev = key;
}
EXPECT_EQ(count, num_keys);
}
}
TEST_CASE(btree_one_key)
{
insert_and_get_to_and_from_btree(1);
}
TEST_CASE(btree_four_keys)
{
insert_and_get_to_and_from_btree(4);
}
TEST_CASE(btree_five_keys)
{
insert_and_get_to_and_from_btree(5);
}
TEST_CASE(btree_10_keys)
{
insert_and_get_to_and_from_btree(10);
}
TEST_CASE(btree_13_keys)
{
insert_and_get_to_and_from_btree(13);
}
TEST_CASE(btree_20_keys)
{
insert_and_get_to_and_from_btree(20);
}
TEST_CASE(btree_25_keys)
{
insert_and_get_to_and_from_btree(25);
}
TEST_CASE(btree_30_keys)
{
insert_and_get_to_and_from_btree(30);
}
TEST_CASE(btree_35_keys)
{
insert_and_get_to_and_from_btree(35);
}
TEST_CASE(btree_40_keys)
{
insert_and_get_to_and_from_btree(40);
}
TEST_CASE(btree_45_keys)
{
insert_and_get_to_and_from_btree(45);
}
TEST_CASE(btree_50_keys)
{
insert_and_get_to_and_from_btree(50);
}
TEST_CASE(btree_scan_one_key)
{
insert_into_and_scan_btree(1);
}
TEST_CASE(btree_scan_four_keys)
{
insert_into_and_scan_btree(4);
}
TEST_CASE(btree_scan_five_keys)
{
insert_into_and_scan_btree(5);
}
TEST_CASE(btree_scan_10_keys)
{
insert_into_and_scan_btree(10);
}
TEST_CASE(btree_scan_15_keys)
{
insert_into_and_scan_btree(15);
}
TEST_CASE(btree_scan_30_keys)
{
insert_into_and_scan_btree(15);
}
TEST_CASE(btree_scan_50_keys)
{
insert_into_and_scan_btree(50);
}

View file

@ -1,237 +0,0 @@
/*
* Copyright (c) 2021, Jan de Visser <jan@de-visser.net>
* Copyright (c) 2023, Jelle Raaijmakers <jelle@gmta.nl>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <unistd.h>
#include <AK/ScopeGuard.h>
#include <AK/StringBuilder.h>
#include <LibCore/System.h>
#include <LibSQL/BTree.h>
#include <LibSQL/Database.h>
#include <LibSQL/Heap.h>
#include <LibSQL/Meta.h>
#include <LibSQL/Row.h>
#include <LibSQL/Value.h>
#include <LibTest/TestCase.h>
static NonnullRefPtr<SQL::SchemaDef> setup_schema(SQL::Database& db)
{
auto schema = MUST(SQL::SchemaDef::create("TestSchema"));
MUST(db.add_schema(schema));
return schema;
}
// FIXME: using the return value for SQL::TableDef to insert a row results in a segfault
static NonnullRefPtr<SQL::TableDef> setup_table(SQL::Database& db)
{
auto schema = setup_schema(db);
auto table = MUST(SQL::TableDef::create(schema, "TestTable"));
table->append_column("TextColumn", SQL::SQLType::Text);
table->append_column("IntColumn", SQL::SQLType::Integer);
EXPECT_EQ(table->num_columns(), 2u);
MUST(db.add_table(table));
return table;
}
static void insert_into_table(SQL::Database& db, int count)
{
auto table = MUST(db.get_table("TestSchema", "TestTable"));
for (int ix = 0; ix < count; ix++) {
SQL::Row row(*table);
StringBuilder builder;
builder.appendff("Test{}", ix);
row["TextColumn"] = builder.to_byte_string();
row["IntColumn"] = ix;
TRY_OR_FAIL(db.insert(row));
}
}
static void verify_table_contents(SQL::Database& db, int expected_count)
{
auto table = MUST(db.get_table("TestSchema", "TestTable"));
int sum = 0;
int count = 0;
auto rows = TRY_OR_FAIL(db.select_all(*table));
for (auto& row : rows) {
StringBuilder builder;
builder.appendff("Test{}", row["IntColumn"].to_int<i32>().value());
EXPECT_EQ(row["TextColumn"].to_byte_string(), builder.to_byte_string());
count++;
sum += row["IntColumn"].to_int<i32>().value();
}
EXPECT_EQ(count, expected_count);
EXPECT_EQ(sum, (expected_count * (expected_count - 1)) / 2);
}
static void commit(SQL::Database& db)
{
TRY_OR_FAIL(db.commit());
}
static void insert_and_verify(int count)
{
ScopeGuard guard([]() { unlink("/tmp/test.db"); });
{
auto db = MUST(SQL::Database::create("/tmp/test.db"));
MUST(db->open());
(void)setup_table(db);
commit(db);
}
{
auto db = MUST(SQL::Database::create("/tmp/test.db"));
MUST(db->open());
insert_into_table(db, count);
commit(db);
}
{
auto db = MUST(SQL::Database::create("/tmp/test.db"));
MUST(db->open());
verify_table_contents(db, count);
}
}
TEST_CASE(create_heap)
{
ScopeGuard guard([]() { unlink("/tmp/test.db"); });
auto heap = MUST(SQL::Heap::create("/tmp/test.db"));
TRY_OR_FAIL(heap->open());
EXPECT_EQ(heap->version(), SQL::Heap::VERSION);
}
TEST_CASE(create_from_dev_random)
{
auto heap = MUST(SQL::Heap::create("/dev/random"));
auto should_be_error = heap->open();
EXPECT(should_be_error.is_error());
}
TEST_CASE(create_from_unreadable_file)
{
auto heap = MUST(SQL::Heap::create("/etc/shadow"));
auto should_be_error = heap->open();
EXPECT(should_be_error.is_error());
}
TEST_CASE(create_in_non_existing_dir)
{
auto heap = MUST(SQL::Heap::create("/tmp/bogus/test.db"));
auto should_be_error = heap->open();
EXPECT(should_be_error.is_error());
}
TEST_CASE(create_database)
{
ScopeGuard guard([]() { unlink("/tmp/test.db"); });
auto db = MUST(SQL::Database::create("/tmp/test.db"));
MUST(db->open());
commit(db);
}
TEST_CASE(add_schema_to_database)
{
ScopeGuard guard([]() { unlink("/tmp/test.db"); });
auto db = MUST(SQL::Database::create("/tmp/test.db"));
MUST(db->open());
(void)setup_schema(db);
commit(db);
}
TEST_CASE(get_schema_from_database)
{
ScopeGuard guard([]() { unlink("/tmp/test.db"); });
{
auto db = MUST(SQL::Database::create("/tmp/test.db"));
MUST(db->open());
(void)setup_schema(db);
commit(db);
}
{
auto db = MUST(SQL::Database::create("/tmp/test.db"));
MUST(db->open());
auto schema = MUST(db->get_schema("TestSchema"));
}
}
TEST_CASE(add_table_to_database)
{
ScopeGuard guard([]() { unlink("/tmp/test.db"); });
auto db = MUST(SQL::Database::create("/tmp/test.db"));
MUST(db->open());
(void)setup_table(db);
commit(db);
}
TEST_CASE(get_table_from_database)
{
ScopeGuard guard([]() { unlink("/tmp/test.db"); });
{
auto db = MUST(SQL::Database::create("/tmp/test.db"));
MUST(db->open());
(void)setup_table(db);
commit(db);
}
{
auto db = MUST(SQL::Database::create("/tmp/test.db"));
MUST(db->open());
auto table = MUST(db->get_table("TestSchema", "TestTable"));
EXPECT_EQ(table->name(), "TestTable");
EXPECT_EQ(table->num_columns(), 2u);
}
}
TEST_CASE(insert_one_into_and_select_from_table)
{
insert_and_verify(1);
}
TEST_CASE(insert_two_into_table)
{
insert_and_verify(2);
}
TEST_CASE(insert_10_into_table)
{
insert_and_verify(10);
}
TEST_CASE(insert_100_into_table)
{
insert_and_verify(100);
}
TEST_CASE(reuse_row_storage)
{
ScopeGuard guard([]() { unlink("/tmp/test.db"); });
auto db = MUST(SQL::Database::create("/tmp/test.db"));
MUST(db->open());
(void)setup_table(db);
auto table = MUST(db->get_table("TestSchema", "TestTable"));
// Insert row
SQL::Row row(*table);
row["TextColumn"] = "text value";
row["IntColumn"] = 12345;
TRY_OR_FAIL(db->insert(row));
TRY_OR_FAIL(db->commit());
auto original_size_in_bytes = MUST(db->file_size_in_bytes());
// Remove row
TRY_OR_FAIL(db->remove(row));
TRY_OR_FAIL(db->commit());
auto size_in_bytes_after_removal = MUST(db->file_size_in_bytes());
EXPECT(size_in_bytes_after_removal <= original_size_in_bytes);
// Insert same row again
TRY_OR_FAIL(db->insert(row));
TRY_OR_FAIL(db->commit());
auto size_in_bytes_after_reinsertion = MUST(db->file_size_in_bytes());
EXPECT(size_in_bytes_after_reinsertion <= original_size_in_bytes);
}

View file

@ -1,604 +0,0 @@
/*
* Copyright (c) 2021, Tim Flynn <trflynn89@serenityos.org>
* Copyright (c) 2021, Jan de Visser <jan@de-visser.net>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibTest/TestCase.h>
#include <AK/ByteString.h>
#include <AK/HashMap.h>
#include <AK/Result.h>
#include <AK/StringBuilder.h>
#include <AK/StringView.h>
#include <AK/TypeCasts.h>
#include <LibSQL/AST/Lexer.h>
#include <LibSQL/AST/Parser.h>
namespace {
class ExpressionParser : public SQL::AST::Parser {
public:
explicit ExpressionParser(SQL::AST::Lexer lexer)
: SQL::AST::Parser(move(lexer))
{
}
NonnullRefPtr<SQL::AST::Expression> parse()
{
return SQL::AST::Parser::parse_expression();
}
};
using ParseResult = AK::Result<NonnullRefPtr<SQL::AST::Expression>, ByteString>;
ParseResult parse(StringView sql)
{
auto parser = ExpressionParser(SQL::AST::Lexer(sql));
auto expression = parser.parse();
if (parser.has_errors()) {
return parser.errors()[0].to_byte_string();
}
return expression;
}
}
TEST_CASE(numeric_literal)
{
// FIXME Right now the "1a" test fails (meaning the parse succeeds).
// This is obviously inconsistent.
// See the FIXME in lexer.cpp, method consume_exponent() about
// solutions.
// EXPECT(parse("1e"sv).is_error());
// EXPECT(parse("1a"sv).is_error());
// EXPECT(parse("0x"sv).is_error());
auto validate = [](StringView sql, double expected_value) {
auto expression = TRY_OR_FAIL(parse(sql));
EXPECT(is<SQL::AST::NumericLiteral>(*expression));
auto const& literal = static_cast<const SQL::AST::NumericLiteral&>(*expression);
EXPECT_EQ(literal.value(), expected_value);
};
validate("123"sv, 123);
validate("3.14"sv, 3.14);
validate("0xA"sv, 10);
validate("0xff"sv, 255);
validate("0x100"sv, 256);
validate("1e3"sv, 1000);
}
TEST_CASE(string_literal)
{
EXPECT(parse("'"sv).is_error());
EXPECT(parse("'unterminated"sv).is_error());
auto validate = [](StringView sql, StringView expected_value) {
auto expression = TRY_OR_FAIL(parse(sql));
EXPECT(is<SQL::AST::StringLiteral>(*expression));
auto const& literal = static_cast<const SQL::AST::StringLiteral&>(*expression);
EXPECT_EQ(literal.value(), expected_value);
};
validate("''"sv, ""sv);
validate("'hello friends'"sv, "hello friends"sv);
validate("'hello ''friends'''"sv, "hello 'friends'"sv);
}
TEST_CASE(blob_literal)
{
EXPECT(parse("x'"sv).is_error());
EXPECT(parse("x'unterminated"sv).is_error());
EXPECT(parse("x'NOTHEX'"sv).is_error());
auto validate = [](StringView sql, StringView expected_value) {
auto expression = TRY_OR_FAIL(parse(sql));
EXPECT(is<SQL::AST::BlobLiteral>(*expression));
auto const& literal = static_cast<const SQL::AST::BlobLiteral&>(*expression);
EXPECT_EQ(literal.value(), expected_value);
};
validate("x''"sv, ""sv);
validate("x'DEADC0DE'"sv, "DEADC0DE"sv);
}
TEST_CASE(boolean_literal)
{
auto validate = [](StringView sql, bool expected_value) {
auto expression = TRY_OR_FAIL(parse(sql));
EXPECT(is<SQL::AST::BooleanLiteral>(*expression));
auto const& literal = static_cast<SQL::AST::BooleanLiteral const&>(*expression);
EXPECT_EQ(literal.value(), expected_value);
};
validate("TRUE"sv, true);
validate("FALSE"sv, false);
}
TEST_CASE(null_literal)
{
auto validate = [](StringView sql) {
auto expression = TRY_OR_FAIL(parse(sql));
EXPECT(is<SQL::AST::NullLiteral>(*expression));
};
validate("NULL"sv);
}
TEST_CASE(bind_parameter)
{
auto validate = [](StringView sql) {
auto expression = TRY_OR_FAIL(parse(sql));
EXPECT(is<SQL::AST::Placeholder>(*expression));
};
validate("?"sv);
}
TEST_CASE(column_name)
{
EXPECT(parse(".column_name"sv).is_error());
EXPECT(parse("table_name."sv).is_error());
EXPECT(parse("schema_name.table_name."sv).is_error());
EXPECT(parse("\"unterminated"sv).is_error());
auto validate = [](StringView sql, StringView expected_schema, StringView expected_table, StringView expected_column) {
auto expression = TRY_OR_FAIL(parse(sql));
EXPECT(is<SQL::AST::ColumnNameExpression>(*expression));
auto const& column = static_cast<const SQL::AST::ColumnNameExpression&>(*expression);
EXPECT_EQ(column.schema_name(), expected_schema);
EXPECT_EQ(column.table_name(), expected_table);
EXPECT_EQ(column.column_name(), expected_column);
};
validate("column_name"sv, {}, {}, "COLUMN_NAME"sv);
validate("table_name.column_name"sv, {}, "TABLE_NAME"sv, "COLUMN_NAME"sv);
validate("schema_name.table_name.column_name"sv, "SCHEMA_NAME"sv, "TABLE_NAME"sv, "COLUMN_NAME"sv);
validate("\"Column_Name\""sv, {}, {}, "Column_Name"sv);
validate("\"Column\n_Name\""sv, {}, {}, "Column\n_Name"sv);
}
TEST_CASE(unary_operator)
{
EXPECT(parse("-"sv).is_error());
EXPECT(parse("--"sv).is_error());
EXPECT(parse("+"sv).is_error());
EXPECT(parse("++"sv).is_error());
EXPECT(parse("~"sv).is_error());
EXPECT(parse("~~"sv).is_error());
EXPECT(parse("NOT"sv).is_error());
auto validate = [](StringView sql, SQL::AST::UnaryOperator expected_operator) {
auto expression = TRY_OR_FAIL(parse(sql));
EXPECT(is<SQL::AST::UnaryOperatorExpression>(*expression));
auto const& unary = static_cast<const SQL::AST::UnaryOperatorExpression&>(*expression);
EXPECT_EQ(unary.type(), expected_operator);
auto const& secondary_expression = unary.expression();
EXPECT(!is<SQL::AST::ErrorExpression>(*secondary_expression));
};
validate("-15"sv, SQL::AST::UnaryOperator::Minus);
validate("+15"sv, SQL::AST::UnaryOperator::Plus);
validate("~15"sv, SQL::AST::UnaryOperator::BitwiseNot);
validate("NOT 15"sv, SQL::AST::UnaryOperator::Not);
}
TEST_CASE(binary_operator)
{
HashMap<StringView, SQL::AST::BinaryOperator> operators {
{ "||"sv, SQL::AST::BinaryOperator::Concatenate },
{ "*"sv, SQL::AST::BinaryOperator::Multiplication },
{ "/"sv, SQL::AST::BinaryOperator::Division },
{ "%"sv, SQL::AST::BinaryOperator::Modulo },
{ "+"sv, SQL::AST::BinaryOperator::Plus },
{ "-"sv, SQL::AST::BinaryOperator::Minus },
{ "<<"sv, SQL::AST::BinaryOperator::ShiftLeft },
{ ">>"sv, SQL::AST::BinaryOperator::ShiftRight },
{ "&"sv, SQL::AST::BinaryOperator::BitwiseAnd },
{ "|"sv, SQL::AST::BinaryOperator::BitwiseOr },
{ "<"sv, SQL::AST::BinaryOperator::LessThan },
{ "<="sv, SQL::AST::BinaryOperator::LessThanEquals },
{ ">"sv, SQL::AST::BinaryOperator::GreaterThan },
{ ">="sv, SQL::AST::BinaryOperator::GreaterThanEquals },
{ "="sv, SQL::AST::BinaryOperator::Equals },
{ "=="sv, SQL::AST::BinaryOperator::Equals },
{ "!="sv, SQL::AST::BinaryOperator::NotEquals },
{ "<>"sv, SQL::AST::BinaryOperator::NotEquals },
{ "AND"sv, SQL::AST::BinaryOperator::And },
{ "OR"sv, SQL::AST::BinaryOperator::Or },
};
for (auto op : operators) {
EXPECT(parse(op.key).is_error());
StringBuilder builder;
builder.append("1 "sv);
builder.append(op.key);
EXPECT(parse(builder.to_byte_string()).is_error());
builder.clear();
if (op.key != "+" && op.key != "-") { // "+1" and "-1" are fine (unary operator).
builder.append(op.key);
builder.append(" 1"sv);
EXPECT(parse(builder.to_byte_string()).is_error());
}
}
auto validate = [](StringView sql, SQL::AST::BinaryOperator expected_operator) {
auto expression = TRY_OR_FAIL(parse(sql));
EXPECT(is<SQL::AST::BinaryOperatorExpression>(*expression));
auto const& binary = static_cast<const SQL::AST::BinaryOperatorExpression&>(*expression);
EXPECT(!is<SQL::AST::ErrorExpression>(*binary.lhs()));
EXPECT(!is<SQL::AST::ErrorExpression>(*binary.rhs()));
EXPECT_EQ(binary.type(), expected_operator);
};
for (auto op : operators) {
StringBuilder builder;
builder.append("1 "sv);
builder.append(op.key);
builder.append(" 1"sv);
validate(builder.to_byte_string(), op.value);
}
}
TEST_CASE(chained_expression)
{
EXPECT(parse("()"sv).is_error());
EXPECT(parse("(,)"sv).is_error());
EXPECT(parse("(15,)"sv).is_error());
auto validate = [](StringView sql, size_t expected_chain_size) {
auto expression = TRY_OR_FAIL(parse(sql));
EXPECT(is<SQL::AST::ChainedExpression>(*expression));
auto const& chain = static_cast<const SQL::AST::ChainedExpression&>(*expression).expressions();
EXPECT_EQ(chain.size(), expected_chain_size);
for (auto const& chained_expression : chain)
EXPECT(!is<SQL::AST::ErrorExpression>(chained_expression));
};
validate("(15)"sv, 1);
validate("(15, 16)"sv, 2);
validate("(15, 16, column_name)"sv, 3);
}
TEST_CASE(cast_expression)
{
EXPECT(parse("CAST"sv).is_error());
EXPECT(parse("CAST ("sv).is_error());
EXPECT(parse("CAST ()"sv).is_error());
EXPECT(parse("CAST (15)"sv).is_error());
EXPECT(parse("CAST (15 AS"sv).is_error());
EXPECT(parse("CAST (15 AS)"sv).is_error());
EXPECT(parse("CAST (15 AS int"sv).is_error());
auto validate = [](StringView sql, StringView expected_type_name) {
auto expression = TRY_OR_FAIL(parse(sql));
EXPECT(is<SQL::AST::CastExpression>(*expression));
auto const& cast = static_cast<const SQL::AST::CastExpression&>(*expression);
EXPECT(!is<SQL::AST::ErrorExpression>(*cast.expression()));
auto const& type_name = cast.type_name();
EXPECT_EQ(type_name->name(), expected_type_name);
};
validate("CAST (15 AS int)"sv, "INT"sv);
// FIXME The syntax in the test below fails on both sqlite3 and psql (PostgreSQL).
// Also fails here because null is interpreted as the NULL keyword and not the
// identifier null (which is not a type)
// validate("CAST ('NULL' AS null)"sv, "null"sv);
validate("CAST (15 AS varchar(255))"sv, "VARCHAR"sv);
}
TEST_CASE(case_expression)
{
EXPECT(parse("CASE"sv).is_error());
EXPECT(parse("CASE END"sv).is_error());
EXPECT(parse("CASE 15"sv).is_error());
EXPECT(parse("CASE 15 END"sv).is_error());
EXPECT(parse("CASE WHEN"sv).is_error());
EXPECT(parse("CASE WHEN THEN"sv).is_error());
EXPECT(parse("CASE WHEN 15 THEN 16"sv).is_error());
EXPECT(parse("CASE WHEN 15 THEN 16 ELSE"sv).is_error());
EXPECT(parse("CASE WHEN 15 THEN 16 ELSE END"sv).is_error());
auto validate = [](StringView sql, bool expect_case_expression, size_t expected_when_then_size, bool expect_else_expression) {
auto expression = TRY_OR_FAIL(parse(sql));
EXPECT(is<SQL::AST::CaseExpression>(*expression));
auto const& case_ = static_cast<const SQL::AST::CaseExpression&>(*expression);
auto const& case_expression = case_.case_expression();
EXPECT_EQ(case_expression.is_null(), !expect_case_expression);
if (case_expression)
EXPECT(!is<SQL::AST::ErrorExpression>(*case_expression));
auto const& when_then_clauses = case_.when_then_clauses();
EXPECT_EQ(when_then_clauses.size(), expected_when_then_size);
for (auto const& when_then_clause : when_then_clauses) {
EXPECT(!is<SQL::AST::ErrorExpression>(*when_then_clause.when));
EXPECT(!is<SQL::AST::ErrorExpression>(*when_then_clause.then));
}
auto const& else_expression = case_.else_expression();
EXPECT_EQ(else_expression.is_null(), !expect_else_expression);
if (else_expression)
EXPECT(!is<SQL::AST::ErrorExpression>(*else_expression));
};
validate("CASE WHEN 16 THEN 17 END"sv, false, 1, false);
validate("CASE WHEN 16 THEN 17 WHEN 18 THEN 19 END"sv, false, 2, false);
validate("CASE WHEN 16 THEN 17 WHEN 18 THEN 19 ELSE 20 END"sv, false, 2, true);
validate("CASE 15 WHEN 16 THEN 17 END"sv, true, 1, false);
validate("CASE 15 WHEN 16 THEN 17 WHEN 18 THEN 19 END"sv, true, 2, false);
validate("CASE 15 WHEN 16 THEN 17 WHEN 18 THEN 19 ELSE 20 END"sv, true, 2, true);
}
TEST_CASE(exists_expression)
{
EXPECT(parse("EXISTS"sv).is_error());
EXPECT(parse("EXISTS ("sv).is_error());
EXPECT(parse("EXISTS (SELECT"sv).is_error());
EXPECT(parse("EXISTS (SELECT)"sv).is_error());
EXPECT(parse("EXISTS (SELECT * FROM table_name"sv).is_error());
EXPECT(parse("NOT EXISTS"sv).is_error());
EXPECT(parse("NOT EXISTS ("sv).is_error());
EXPECT(parse("NOT EXISTS (SELECT"sv).is_error());
EXPECT(parse("NOT EXISTS (SELECT)"sv).is_error());
EXPECT(parse("NOT EXISTS (SELECT * FROM table_name"sv).is_error());
EXPECT(parse("("sv).is_error());
EXPECT(parse("(SELECT"sv).is_error());
EXPECT(parse("(SELECT)"sv).is_error());
EXPECT(parse("(SELECT * FROM table_name"sv).is_error());
auto validate = [](StringView sql, bool expected_invert_expression) {
auto expression = TRY_OR_FAIL(parse(sql));
EXPECT(is<SQL::AST::ExistsExpression>(*expression));
auto const& exists = static_cast<const SQL::AST::ExistsExpression&>(*expression);
EXPECT_EQ(exists.invert_expression(), expected_invert_expression);
};
validate("EXISTS (SELECT * FROM table_name)"sv, false);
validate("NOT EXISTS (SELECT * FROM table_name)"sv, true);
validate("(SELECT * FROM table_name)"sv, false);
}
TEST_CASE(collate_expression)
{
EXPECT(parse("COLLATE"sv).is_error());
EXPECT(parse("COLLATE name"sv).is_error());
EXPECT(parse("15 COLLATE"sv).is_error());
auto validate = [](StringView sql, StringView expected_collation_name) {
auto expression = TRY_OR_FAIL(parse(sql));
EXPECT(is<SQL::AST::CollateExpression>(*expression));
auto const& collate = static_cast<const SQL::AST::CollateExpression&>(*expression);
EXPECT(!is<SQL::AST::ErrorExpression>(*collate.expression()));
EXPECT_EQ(collate.collation_name(), expected_collation_name);
};
validate("15 COLLATE fifteen"sv, "FIFTEEN"sv);
validate("(15, 16) COLLATE \"chain\""sv, "chain"sv);
}
TEST_CASE(is_expression)
{
EXPECT(parse("IS"sv).is_error());
EXPECT(parse("IS 1"sv).is_error());
EXPECT(parse("1 IS"sv).is_error());
EXPECT(parse("IS NOT"sv).is_error());
EXPECT(parse("IS NOT 1"sv).is_error());
EXPECT(parse("1 IS NOT"sv).is_error());
auto validate = [](StringView sql, bool expected_invert_expression) {
auto expression = TRY_OR_FAIL(parse(sql));
EXPECT(is<SQL::AST::IsExpression>(*expression));
auto const& is_ = static_cast<const SQL::AST::IsExpression&>(*expression);
EXPECT(!is<SQL::AST::ErrorExpression>(*is_.lhs()));
EXPECT(!is<SQL::AST::ErrorExpression>(*is_.rhs()));
EXPECT_EQ(is_.invert_expression(), expected_invert_expression);
};
validate("1 IS NULL"sv, false);
validate("1 IS NOT NULL"sv, true);
}
TEST_CASE(match_expression)
{
HashMap<StringView, SQL::AST::MatchOperator> operators {
{ "LIKE"sv, SQL::AST::MatchOperator::Like },
{ "GLOB"sv, SQL::AST::MatchOperator::Glob },
{ "MATCH"sv, SQL::AST::MatchOperator::Match },
{ "REGEXP"sv, SQL::AST::MatchOperator::Regexp },
};
for (auto op : operators) {
EXPECT(parse(op.key).is_error());
StringBuilder builder;
builder.append("1 "sv);
builder.append(op.key);
EXPECT(parse(builder.to_byte_string()).is_error());
builder.clear();
builder.append(op.key);
builder.append(" 1"sv);
EXPECT(parse(builder.to_byte_string()).is_error());
}
auto validate = [](StringView sql, SQL::AST::MatchOperator expected_operator, bool expected_invert_expression, bool expect_escape) {
auto expression = TRY_OR_FAIL(parse(sql));
EXPECT(is<SQL::AST::MatchExpression>(*expression));
auto const& match = static_cast<const SQL::AST::MatchExpression&>(*expression);
EXPECT(!is<SQL::AST::ErrorExpression>(*match.lhs()));
EXPECT(!is<SQL::AST::ErrorExpression>(*match.rhs()));
EXPECT_EQ(match.type(), expected_operator);
EXPECT_EQ(match.invert_expression(), expected_invert_expression);
EXPECT(match.escape() || !expect_escape);
};
for (auto op : operators) {
StringBuilder builder;
builder.append("1 "sv);
builder.append(op.key);
builder.append(" 1"sv);
validate(builder.to_byte_string(), op.value, false, false);
builder.clear();
builder.append("1 NOT "sv);
builder.append(op.key);
builder.append(" 1"sv);
validate(builder.to_byte_string(), op.value, true, false);
builder.clear();
builder.append("1 NOT "sv);
builder.append(op.key);
builder.append(" 1 ESCAPE '+'"sv);
validate(builder.to_byte_string(), op.value, true, true);
}
}
TEST_CASE(null_expression)
{
EXPECT(parse("ISNULL"sv).is_error());
EXPECT(parse("NOTNULL"sv).is_error());
EXPECT(parse("15 NOT"sv).is_error());
auto validate = [](StringView sql, bool expected_invert_expression) {
auto expression = TRY_OR_FAIL(parse(sql));
EXPECT(is<SQL::AST::NullExpression>(*expression));
auto const& null = static_cast<const SQL::AST::NullExpression&>(*expression);
EXPECT_EQ(null.invert_expression(), expected_invert_expression);
};
validate("15 ISNULL"sv, false);
validate("15 NOTNULL"sv, true);
validate("15 NOT NULL"sv, true);
}
TEST_CASE(between_expression)
{
EXPECT(parse("BETWEEN"sv).is_error());
EXPECT(parse("NOT BETWEEN"sv).is_error());
EXPECT(parse("BETWEEN 10 AND 20"sv).is_error());
EXPECT(parse("NOT BETWEEN 10 AND 20"sv).is_error());
EXPECT(parse("15 BETWEEN 10"sv).is_error());
EXPECT(parse("15 BETWEEN 10 AND"sv).is_error());
EXPECT(parse("15 BETWEEN AND 20"sv).is_error());
EXPECT(parse("15 BETWEEN 10 OR 20"sv).is_error());
auto validate = [](StringView sql, bool expected_invert_expression) {
auto expression = TRY_OR_FAIL(parse(sql));
EXPECT(is<SQL::AST::BetweenExpression>(*expression));
auto const& between = static_cast<const SQL::AST::BetweenExpression&>(*expression);
EXPECT(!is<SQL::AST::ErrorExpression>(*between.expression()));
EXPECT(!is<SQL::AST::ErrorExpression>(*between.lhs()));
EXPECT(!is<SQL::AST::ErrorExpression>(*between.rhs()));
EXPECT_EQ(between.invert_expression(), expected_invert_expression);
};
validate("15 BETWEEN 10 AND 20"sv, false);
validate("15 NOT BETWEEN 10 AND 20"sv, true);
}
TEST_CASE(in_table_expression)
{
EXPECT(parse("IN"sv).is_error());
EXPECT(parse("IN table_name"sv).is_error());
EXPECT(parse("NOT IN"sv).is_error());
EXPECT(parse("NOT IN table_name"sv).is_error());
auto validate = [](StringView sql, StringView expected_schema, StringView expected_table, bool expected_invert_expression) {
auto expression = TRY_OR_FAIL(parse(sql));
EXPECT(is<SQL::AST::InTableExpression>(*expression));
auto const& in = static_cast<const SQL::AST::InTableExpression&>(*expression);
EXPECT(!is<SQL::AST::ErrorExpression>(*in.expression()));
EXPECT_EQ(in.schema_name(), expected_schema);
EXPECT_EQ(in.table_name(), expected_table);
EXPECT_EQ(in.invert_expression(), expected_invert_expression);
};
validate("15 IN table_name"sv, {}, "TABLE_NAME"sv, false);
validate("15 IN schema_name.table_name"sv, "SCHEMA_NAME"sv, "TABLE_NAME"sv, false);
validate("15 NOT IN table_name"sv, {}, "TABLE_NAME"sv, true);
validate("15 NOT IN schema_name.table_name"sv, "SCHEMA_NAME"sv, "TABLE_NAME"sv, true);
}
TEST_CASE(in_chained_expression)
{
EXPECT(parse("IN ()"sv).is_error());
EXPECT(parse("NOT IN ()"sv).is_error());
auto validate = [](StringView sql, size_t expected_chain_size, bool expected_invert_expression) {
auto expression = TRY_OR_FAIL(parse(sql));
EXPECT(is<SQL::AST::InChainedExpression>(*expression));
auto const& in = static_cast<const SQL::AST::InChainedExpression&>(*expression);
EXPECT(!is<SQL::AST::ErrorExpression>(*in.expression()));
EXPECT_EQ(in.expression_chain()->expressions().size(), expected_chain_size);
EXPECT_EQ(in.invert_expression(), expected_invert_expression);
for (auto const& chained_expression : in.expression_chain()->expressions())
EXPECT(!is<SQL::AST::ErrorExpression>(chained_expression));
};
validate("15 IN ()"sv, 0, false);
validate("15 IN (15)"sv, 1, false);
validate("15 IN (15, 16)"sv, 2, false);
validate("15 NOT IN ()"sv, 0, true);
validate("15 NOT IN (15)"sv, 1, true);
validate("15 NOT IN (15, 16)"sv, 2, true);
}
TEST_CASE(in_selection_expression)
{
EXPECT(parse("IN (SELECT)"sv).is_error());
EXPECT(parse("IN (SELECT * FROM table_name, SELECT * FROM table_name);"sv).is_error());
EXPECT(parse("NOT IN (SELECT)"sv).is_error());
EXPECT(parse("NOT IN (SELECT * FROM table_name, SELECT * FROM table_name);"sv).is_error());
auto validate = [](StringView sql, bool expected_invert_expression) {
auto expression = TRY_OR_FAIL(parse(sql));
EXPECT(is<SQL::AST::InSelectionExpression>(*expression));
auto const& in = static_cast<const SQL::AST::InSelectionExpression&>(*expression);
EXPECT(!is<SQL::AST::ErrorExpression>(*in.expression()));
EXPECT_EQ(in.invert_expression(), expected_invert_expression);
};
validate("15 IN (SELECT * FROM table_name)"sv, false);
validate("15 NOT IN (SELECT * FROM table_name)"sv, true);
}
TEST_CASE(expression_tree_depth_limit)
{
auto too_deep_expression = ByteString::formatted("{:+^{}}1", "", SQL::AST::Limits::maximum_expression_tree_depth);
EXPECT(!parse(too_deep_expression.substring_view(1)).is_error());
EXPECT(parse(too_deep_expression).is_error());
}

View file

@ -1,194 +0,0 @@
/*
* Copyright (c) 2023, Jelle Raaijmakers <jelle@gmta.nl>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/ScopeGuard.h>
#include <AK/StringBuilder.h>
#include <LibCore/System.h>
#include <LibSQL/Heap.h>
#include <LibTest/TestCase.h>
static constexpr auto db_path = "/tmp/test.db"sv;
static NonnullRefPtr<SQL::Heap> create_heap()
{
auto heap = MUST(SQL::Heap::create(db_path));
MUST(heap->open());
return heap;
}
TEST_CASE(heap_write_large_storage_without_flush)
{
ScopeGuard guard([]() { MUST(Core::System::unlink(db_path)); });
auto heap = create_heap();
auto storage_block_id = heap->request_new_block_index();
// Write large storage spanning multiple blocks
StringBuilder builder;
MUST(builder.try_append_repeated('x', SQL::Block::DATA_SIZE * 4));
auto long_string = builder.string_view();
TRY_OR_FAIL(heap->write_storage(storage_block_id, long_string.bytes()));
// Read back
auto stored_long_string = TRY_OR_FAIL(heap->read_storage(storage_block_id));
EXPECT_EQ(long_string.bytes(), stored_long_string.bytes());
}
TEST_CASE(heap_write_large_storage_with_flush)
{
ScopeGuard guard([]() { MUST(Core::System::unlink(db_path)); });
auto heap = create_heap();
auto storage_block_id = heap->request_new_block_index();
// Write large storage spanning multiple blocks
StringBuilder builder;
MUST(builder.try_append_repeated('x', SQL::Block::DATA_SIZE * 4));
auto long_string = builder.string_view();
TRY_OR_FAIL(heap->write_storage(storage_block_id, long_string.bytes()));
MUST(heap->flush());
// Read back
auto stored_long_string = TRY_OR_FAIL(heap->read_storage(storage_block_id));
EXPECT_EQ(long_string.bytes(), stored_long_string.bytes());
}
TEST_CASE(heap_overwrite_large_storage)
{
ScopeGuard guard([]() { MUST(Core::System::unlink(db_path)); });
auto heap = create_heap();
auto storage_block_id = heap->request_new_block_index();
// Write large storage spanning multiple blocks
StringBuilder builder;
MUST(builder.try_append_repeated('x', SQL::Block::DATA_SIZE * 4));
auto long_string = builder.string_view();
TRY_OR_FAIL(heap->write_storage(storage_block_id, long_string.bytes()));
MUST(heap->flush());
auto heap_size = MUST(heap->file_size_in_bytes());
// Let's write it again and check whether the Heap reused the same extended blocks
TRY_OR_FAIL(heap->write_storage(storage_block_id, long_string.bytes()));
MUST(heap->flush());
auto new_heap_size = MUST(heap->file_size_in_bytes());
EXPECT_EQ(heap_size, new_heap_size);
// Write a smaller string and read back - heap size should be at most the previous size
builder.clear();
MUST(builder.try_append_repeated('y', SQL::Block::DATA_SIZE * 2));
auto shorter_string = builder.string_view();
TRY_OR_FAIL(heap->write_storage(storage_block_id, shorter_string.bytes()));
MUST(heap->flush());
new_heap_size = MUST(heap->file_size_in_bytes());
EXPECT(new_heap_size <= heap_size);
auto stored_shorter_string = TRY_OR_FAIL(heap->read_storage(storage_block_id));
EXPECT_EQ(shorter_string.bytes(), stored_shorter_string.bytes());
// Write a longer string and read back - heap size is expected to grow
builder.clear();
MUST(builder.try_append_repeated('z', SQL::Block::DATA_SIZE * 6));
auto longest_string = builder.string_view();
TRY_OR_FAIL(heap->write_storage(storage_block_id, longest_string.bytes()));
MUST(heap->flush());
new_heap_size = MUST(heap->file_size_in_bytes());
EXPECT(new_heap_size > heap_size);
auto stored_longest_string = TRY_OR_FAIL(heap->read_storage(storage_block_id));
EXPECT_EQ(longest_string.bytes(), stored_longest_string.bytes());
}
TEST_CASE(heap_reuse_freed_blocks_after_storage_trim)
{
ScopeGuard guard([]() { MUST(Core::System::unlink(db_path)); });
auto heap = create_heap();
// First, write storage spanning 4 blocks
auto first_index = heap->request_new_block_index();
StringBuilder builder;
MUST(builder.try_append_repeated('x', SQL::Block::DATA_SIZE * 4));
auto long_string = builder.string_view();
TRY_OR_FAIL(heap->write_storage(first_index, long_string.bytes()));
MUST(heap->flush());
auto original_heap_size = MUST(heap->file_size_in_bytes());
// Then, overwrite the first storage and reduce it to 2 blocks
builder.clear();
MUST(builder.try_append_repeated('x', SQL::Block::DATA_SIZE * 2));
long_string = builder.string_view();
TRY_OR_FAIL(heap->write_storage(first_index, long_string.bytes()));
MUST(heap->flush());
auto heap_size_after_reduction = MUST(heap->file_size_in_bytes());
EXPECT(heap_size_after_reduction <= original_heap_size);
// Now add the second storage spanning 2 blocks - heap should not have grown compared to the original storage
auto second_index = heap->request_new_block_index();
TRY_OR_FAIL(heap->write_storage(second_index, long_string.bytes()));
MUST(heap->flush());
auto heap_size_after_second_storage = MUST(heap->file_size_in_bytes());
EXPECT(heap_size_after_second_storage <= original_heap_size);
}
TEST_CASE(heap_reuse_freed_blocks_after_reopening_file)
{
ScopeGuard guard([]() { MUST(Core::System::unlink(db_path)); });
size_t original_heap_size = 0;
StringBuilder builder;
MUST(builder.try_append_repeated('x', SQL::Block::DATA_SIZE * 4));
auto long_string = builder.string_view();
{
auto heap = create_heap();
// First, write storage spanning 4 blocks
auto first_index = heap->request_new_block_index();
TRY_OR_FAIL(heap->write_storage(first_index, long_string.bytes()));
MUST(heap->flush());
original_heap_size = MUST(heap->file_size_in_bytes());
// Then, overwrite the first storage and reduce it to 2 blocks
builder.clear();
MUST(builder.try_append_repeated('x', SQL::Block::DATA_SIZE * 2));
long_string = builder.string_view();
TRY_OR_FAIL(heap->write_storage(first_index, long_string.bytes()));
MUST(heap->flush());
auto heap_size_after_reduction = MUST(heap->file_size_in_bytes());
EXPECT(heap_size_after_reduction <= original_heap_size);
}
// Reopen the database file; we expect the heap to support reading back free blocks somehow.
// Add the second storage spanning 2 blocks - heap should not have grown compared to the original storage.
{
auto heap = create_heap();
auto second_index = heap->request_new_block_index();
TRY_OR_FAIL(heap->write_storage(second_index, long_string.bytes()));
MUST(heap->flush());
auto heap_size_after_second_storage = MUST(heap->file_size_in_bytes());
EXPECT(heap_size_after_second_storage <= original_heap_size);
}
}
TEST_CASE(heap_free_storage)
{
ScopeGuard guard([]() { MUST(Core::System::unlink(db_path)); });
auto heap = create_heap();
auto storage_block_id = heap->request_new_block_index();
// Write large storage spanning multiple blocks
StringBuilder builder;
MUST(builder.try_append_repeated('x', SQL::Block::DATA_SIZE * 4));
auto long_string = builder.string_view();
TRY_OR_FAIL(heap->write_storage(storage_block_id, long_string.bytes()));
MUST(heap->flush());
auto heap_size = MUST(heap->file_size_in_bytes());
// Free the storage
TRY_OR_FAIL(heap->free_storage(storage_block_id));
// Again, write some large storage spanning multiple blocks
storage_block_id = heap->request_new_block_index();
TRY_OR_FAIL(heap->write_storage(storage_block_id, long_string.bytes()));
MUST(heap->flush());
auto new_heap_size = MUST(heap->file_size_in_bytes());
EXPECT(new_heap_size <= heap_size);
}

File diff suppressed because it is too large Load diff

View file

@ -1,785 +0,0 @@
/*
* Copyright (c) 2021, Tim Flynn <trflynn89@serenityos.org>
* Copyright (c) 2021, Jan de Visser <jan@de-visser.net>
* Copyright (c) 2021, Mahmoud Mandour <ma.mandourr@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibTest/TestCase.h>
#include <AK/ByteString.h>
#include <AK/Optional.h>
#include <AK/Result.h>
#include <AK/StringView.h>
#include <AK/TypeCasts.h>
#include <AK/Vector.h>
#include <LibSQL/AST/Lexer.h>
#include <LibSQL/AST/Parser.h>
namespace {
using ParseResult = AK::Result<NonnullRefPtr<SQL::AST::Statement>, ByteString>;
ParseResult parse(StringView sql)
{
auto parser = SQL::AST::Parser(SQL::AST::Lexer(sql));
auto statement = parser.next_statement();
if (parser.has_errors()) {
return parser.errors()[0].to_byte_string();
}
return statement;
}
}
TEST_CASE(create_table)
{
EXPECT(parse("CREATE TABLE"sv).is_error());
EXPECT(parse("CREATE TABLE test"sv).is_error());
EXPECT(parse("CREATE TABLE test ()"sv).is_error());
EXPECT(parse("CREATE TABLE test ();"sv).is_error());
EXPECT(parse("CREATE TABLE test ( column1 "sv).is_error());
EXPECT(parse("CREATE TABLE test ( column1 )"sv).is_error());
EXPECT(parse("CREATE TABLE IF test ( column1 );"sv).is_error());
EXPECT(parse("CREATE TABLE IF NOT test ( column1 );"sv).is_error());
EXPECT(parse("CREATE TABLE AS;"sv).is_error());
EXPECT(parse("CREATE TABLE AS SELECT;"sv).is_error());
EXPECT(parse("CREATE TABLE test ( column1 varchar()"sv).is_error());
EXPECT(parse("CREATE TABLE test ( column1 varchar(abc)"sv).is_error());
EXPECT(parse("CREATE TABLE test ( column1 varchar(123 )"sv).is_error());
EXPECT(parse("CREATE TABLE test ( column1 varchar(123, )"sv).is_error());
EXPECT(parse("CREATE TABLE test ( column1 varchar(123, ) )"sv).is_error());
EXPECT(parse("CREATE TABLE test ( column1 varchar(.) )"sv).is_error());
EXPECT(parse("CREATE TABLE test ( column1 varchar(.abc) )"sv).is_error());
EXPECT(parse("CREATE TABLE test ( column1 varchar(0x) )"sv).is_error());
EXPECT(parse("CREATE TABLE test ( column1 varchar(0xzzz) )"sv).is_error());
EXPECT(parse("CREATE TABLE test ( column1 int ) AS SELECT * FROM table_name;"sv).is_error());
EXPECT(parse("CREATE TABLE test AS SELECT * FROM table_name ( column1 int ) ;"sv).is_error());
struct Column {
StringView name;
StringView type;
Vector<double> signed_numbers {};
};
auto validate = [](StringView sql, StringView expected_schema, StringView expected_table, Vector<Column> expected_columns, bool expected_is_temporary = false, bool expected_is_error_if_table_exists = true) {
auto statement = TRY_OR_FAIL(parse(sql));
EXPECT(is<SQL::AST::CreateTable>(*statement));
auto const& table = static_cast<const SQL::AST::CreateTable&>(*statement);
EXPECT_EQ(table.schema_name(), expected_schema);
EXPECT_EQ(table.table_name(), expected_table);
EXPECT_EQ(table.is_temporary(), expected_is_temporary);
EXPECT_EQ(table.is_error_if_table_exists(), expected_is_error_if_table_exists);
bool expect_select_statement = expected_columns.is_empty();
EXPECT_EQ(table.has_selection(), expect_select_statement);
EXPECT_EQ(table.has_columns(), !expect_select_statement);
auto const& select_statement = table.select_statement();
EXPECT_EQ(select_statement.is_null(), !expect_select_statement);
auto const& columns = table.columns();
EXPECT_EQ(columns.size(), expected_columns.size());
for (size_t i = 0; i < columns.size(); ++i) {
auto const& column = columns[i];
auto const& expected_column = expected_columns[i];
EXPECT_EQ(column->name(), expected_column.name);
auto const& type_name = column->type_name();
EXPECT_EQ(type_name->name(), expected_column.type);
auto const& signed_numbers = type_name->signed_numbers();
EXPECT_EQ(signed_numbers.size(), expected_column.signed_numbers.size());
for (size_t j = 0; j < signed_numbers.size(); ++j) {
double signed_number = signed_numbers[j]->value();
double expected_signed_number = expected_column.signed_numbers[j];
EXPECT_EQ(signed_number, expected_signed_number);
}
}
};
validate("CREATE TABLE test ( column1 );"sv, {}, "TEST"sv, { { "COLUMN1"sv, "BLOB"sv } });
validate("Create Table test ( column1 );"sv, {}, "TEST"sv, { { "COLUMN1"sv, "BLOB"sv } });
validate(R"(CREATE TABLE "test" ( "column1" );)"sv, {}, "test"sv, { { "column1"sv, "BLOB"sv } });
validate(R"(CREATE TABLE "te""st" ( "co""lumn1" );)"sv, {}, "te\"st"sv, { { "co\"lumn1"sv, "BLOB"sv } });
validate("CREATE TABLE schema_name.test ( column1 );"sv, "SCHEMA_NAME"sv, "TEST"sv, { { "COLUMN1"sv, "BLOB"sv } });
validate("CREATE TABLE \"schema\".test ( column1 );"sv, "schema"sv, "TEST"sv, { { "COLUMN1"sv, "BLOB"sv } });
validate("CREATE TEMP TABLE test ( column1 );"sv, {}, "TEST"sv, { { "COLUMN1"sv, "BLOB"sv } }, true, true);
validate("CREATE TEMPORARY TABLE test ( column1 );"sv, {}, "TEST"sv, { { "COLUMN1"sv, "BLOB"sv } }, true, true);
validate("CREATE TABLE IF NOT EXISTS test ( column1 );"sv, {}, "TEST"sv, { { "COLUMN1"sv, "BLOB"sv } }, false, false);
validate("CREATE TABLE test AS SELECT * FROM table_name;"sv, {}, "TEST"sv, {});
validate("CREATE TABLE test ( column1 int );"sv, {}, "TEST"sv, { { "COLUMN1"sv, "INT"sv } });
validate("CREATE TABLE test ( column1 varchar );"sv, {}, "TEST"sv, { { "COLUMN1"sv, "VARCHAR"sv } });
validate("CREATE TABLE test ( column1 varchar(255) );"sv, {}, "TEST"sv, { { "COLUMN1"sv, "VARCHAR"sv, { 255 } } });
validate("CREATE TABLE test ( column1 varchar(255, 123) );"sv, {}, "TEST"sv, { { "COLUMN1"sv, "VARCHAR"sv, { 255, 123 } } });
validate("CREATE TABLE test ( column1 varchar(255, -123) );"sv, {}, "TEST"sv, { { "COLUMN1"sv, "VARCHAR"sv, { 255, -123 } } });
validate("CREATE TABLE test ( column1 varchar(0xff) );"sv, {}, "TEST"sv, { { "COLUMN1"sv, "VARCHAR"sv, { 255 } } });
validate("CREATE TABLE test ( column1 varchar(3.14) );"sv, {}, "TEST"sv, { { "COLUMN1"sv, "VARCHAR"sv, { 3.14 } } });
validate("CREATE TABLE test ( column1 varchar(1e3) );"sv, {}, "TEST"sv, { { "COLUMN1"sv, "VARCHAR"sv, { 1000 } } });
}
TEST_CASE(alter_table)
{
// This test case only contains common error cases of the AlterTable subclasses.
EXPECT(parse("ALTER"sv).is_error());
EXPECT(parse("ALTER TABLE"sv).is_error());
EXPECT(parse("ALTER TABLE table_name"sv).is_error());
EXPECT(parse("ALTER TABLE table_name;"sv).is_error());
}
TEST_CASE(alter_table_rename_table)
{
EXPECT(parse("ALTER TABLE table_name RENAME"sv).is_error());
EXPECT(parse("ALTER TABLE table_name RENAME TO"sv).is_error());
EXPECT(parse("ALTER TABLE table_name RENAME TO new_table"sv).is_error());
auto validate = [](StringView sql, StringView expected_schema, StringView expected_table, StringView expected_new_table) {
auto statement = TRY_OR_FAIL(parse(sql));
EXPECT(is<SQL::AST::RenameTable>(*statement));
auto const& alter = static_cast<const SQL::AST::RenameTable&>(*statement);
EXPECT_EQ(alter.schema_name(), expected_schema);
EXPECT_EQ(alter.table_name(), expected_table);
EXPECT_EQ(alter.new_table_name(), expected_new_table);
};
validate("ALTER TABLE table_name RENAME TO new_table;"sv, {}, "TABLE_NAME"sv, "NEW_TABLE"sv);
validate("ALTER TABLE schema_name.table_name RENAME TO new_table;"sv, "SCHEMA_NAME"sv, "TABLE_NAME"sv, "NEW_TABLE"sv);
}
TEST_CASE(alter_table_rename_column)
{
EXPECT(parse("ALTER TABLE table_name RENAME"sv).is_error());
EXPECT(parse("ALTER TABLE table_name RENAME COLUMN"sv).is_error());
EXPECT(parse("ALTER TABLE table_name RENAME COLUMN column_name"sv).is_error());
EXPECT(parse("ALTER TABLE table_name RENAME COLUMN column_name TO"sv).is_error());
EXPECT(parse("ALTER TABLE table_name RENAME COLUMN column_name TO new_column"sv).is_error());
EXPECT(parse("ALTER TABLE table_name RENAME column_name"sv).is_error());
EXPECT(parse("ALTER TABLE table_name RENAME column_name TO"sv).is_error());
EXPECT(parse("ALTER TABLE table_name RENAME column_name TO new_column"sv).is_error());
auto validate = [](StringView sql, StringView expected_schema, StringView expected_table, StringView expected_column, StringView expected_new_column) {
auto statement = TRY_OR_FAIL(parse(sql));
EXPECT(is<SQL::AST::RenameColumn>(*statement));
auto const& alter = static_cast<const SQL::AST::RenameColumn&>(*statement);
EXPECT_EQ(alter.schema_name(), expected_schema);
EXPECT_EQ(alter.table_name(), expected_table);
EXPECT_EQ(alter.column_name(), expected_column);
EXPECT_EQ(alter.new_column_name(), expected_new_column);
};
validate("ALTER TABLE table_name RENAME column_name TO new_column;"sv, {}, "TABLE_NAME"sv, "COLUMN_NAME"sv, "NEW_COLUMN"sv);
validate("ALTER TABLE table_name RENAME COLUMN column_name TO new_column;"sv, {}, "TABLE_NAME"sv, "COLUMN_NAME"sv, "NEW_COLUMN"sv);
validate("ALTER TABLE schema_name.table_name RENAME column_name TO new_column;"sv, "SCHEMA_NAME"sv, "TABLE_NAME"sv, "COLUMN_NAME"sv, "NEW_COLUMN"sv);
validate("ALTER TABLE schema_name.table_name RENAME COLUMN column_name TO new_column;"sv, "SCHEMA_NAME"sv, "TABLE_NAME"sv, "COLUMN_NAME"sv, "NEW_COLUMN"sv);
}
TEST_CASE(alter_table_add_column)
{
EXPECT(parse("ALTER TABLE table_name ADD"sv).is_error());
EXPECT(parse("ALTER TABLE table_name ADD COLUMN"sv).is_error());
EXPECT(parse("ALTER TABLE table_name ADD COLUMN column_name"sv).is_error());
struct Column {
StringView name;
StringView type;
Vector<double> signed_numbers {};
};
auto validate = [](StringView sql, StringView expected_schema, StringView expected_table, Column expected_column) {
auto statement = TRY_OR_FAIL(parse(sql));
EXPECT(is<SQL::AST::AddColumn>(*statement));
auto const& alter = static_cast<const SQL::AST::AddColumn&>(*statement);
EXPECT_EQ(alter.schema_name(), expected_schema);
EXPECT_EQ(alter.table_name(), expected_table);
auto const& column = alter.column();
EXPECT_EQ(column->name(), expected_column.name);
auto const& type_name = column->type_name();
EXPECT_EQ(type_name->name(), expected_column.type);
auto const& signed_numbers = type_name->signed_numbers();
EXPECT_EQ(signed_numbers.size(), expected_column.signed_numbers.size());
for (size_t j = 0; j < signed_numbers.size(); ++j) {
double signed_number = signed_numbers[j]->value();
double expected_signed_number = expected_column.signed_numbers[j];
EXPECT_EQ(signed_number, expected_signed_number);
}
};
validate("ALTER TABLE test ADD column1;"sv, {}, "TEST"sv, { "COLUMN1"sv, "BLOB"sv });
validate("ALTER TABLE test ADD column1 int;"sv, {}, "TEST"sv, { "COLUMN1"sv, "INT"sv });
validate("ALTER TABLE test ADD column1 varchar;"sv, {}, "TEST"sv, { "COLUMN1"sv, "VARCHAR"sv });
validate("ALTER TABLE test ADD column1 varchar(255);"sv, {}, "TEST"sv, { "COLUMN1"sv, "VARCHAR"sv, { 255 } });
validate("ALTER TABLE test ADD column1 varchar(255, 123);"sv, {}, "TEST"sv, { "COLUMN1"sv, "VARCHAR"sv, { 255, 123 } });
validate("ALTER TABLE schema_name.test ADD COLUMN column1;"sv, "SCHEMA_NAME"sv, "TEST"sv, { "COLUMN1"sv, "BLOB"sv });
validate("ALTER TABLE schema_name.test ADD COLUMN column1 int;"sv, "SCHEMA_NAME"sv, "TEST"sv, { "COLUMN1"sv, "INT"sv });
validate("ALTER TABLE schema_name.test ADD COLUMN column1 varchar;"sv, "SCHEMA_NAME"sv, "TEST"sv, { "COLUMN1"sv, "VARCHAR"sv });
validate("ALTER TABLE schema_name.test ADD COLUMN column1 varchar(255);"sv, "SCHEMA_NAME"sv, "TEST"sv, { "COLUMN1"sv, "VARCHAR"sv, { 255 } });
validate("ALTER TABLE schema_name.test ADD COLUMN column1 varchar(255, 123);"sv, "SCHEMA_NAME"sv, "TEST"sv, { "COLUMN1"sv, "VARCHAR"sv, { 255, 123 } });
}
TEST_CASE(alter_table_drop_column)
{
EXPECT(parse("ALTER TABLE table_name DROP"sv).is_error());
EXPECT(parse("ALTER TABLE table_name DROP COLUMN"sv).is_error());
EXPECT(parse("ALTER TABLE table_name DROP column_name"sv).is_error());
EXPECT(parse("ALTER TABLE table_name DROP COLUMN column_name"sv).is_error());
auto validate = [](StringView sql, StringView expected_schema, StringView expected_table, StringView expected_column) {
auto statement = TRY_OR_FAIL(parse(sql));
EXPECT(is<SQL::AST::DropColumn>(*statement));
auto const& alter = static_cast<const SQL::AST::DropColumn&>(*statement);
EXPECT_EQ(alter.schema_name(), expected_schema);
EXPECT_EQ(alter.table_name(), expected_table);
EXPECT_EQ(alter.column_name(), expected_column);
};
validate("ALTER TABLE table_name DROP column_name;"sv, {}, "TABLE_NAME"sv, "COLUMN_NAME"sv);
validate("ALTER TABLE table_name DROP COLUMN column_name;"sv, {}, "TABLE_NAME"sv, "COLUMN_NAME"sv);
validate("ALTER TABLE schema_name.table_name DROP column_name;"sv, "SCHEMA_NAME"sv, "TABLE_NAME"sv, "COLUMN_NAME"sv);
validate("ALTER TABLE schema_name.table_name DROP COLUMN column_name;"sv, "SCHEMA_NAME"sv, "TABLE_NAME"sv, "COLUMN_NAME"sv);
}
TEST_CASE(drop_table)
{
EXPECT(parse("DROP"sv).is_error());
EXPECT(parse("DROP TABLE"sv).is_error());
EXPECT(parse("DROP TABLE test"sv).is_error());
EXPECT(parse("DROP TABLE IF test;"sv).is_error());
auto validate = [](StringView sql, StringView expected_schema, StringView expected_table, bool expected_is_error_if_table_does_not_exist = true) {
auto statement = TRY_OR_FAIL(parse(sql));
EXPECT(is<SQL::AST::DropTable>(*statement));
auto const& table = static_cast<const SQL::AST::DropTable&>(*statement);
EXPECT_EQ(table.schema_name(), expected_schema);
EXPECT_EQ(table.table_name(), expected_table);
EXPECT_EQ(table.is_error_if_table_does_not_exist(), expected_is_error_if_table_does_not_exist);
};
validate("DROP TABLE test;"sv, {}, "TEST"sv);
validate("DROP TABLE schema_name.test;"sv, "SCHEMA_NAME"sv, "TEST"sv);
validate("DROP TABLE IF EXISTS test;"sv, {}, "TEST"sv, false);
}
TEST_CASE(insert)
{
EXPECT(parse("INSERT"sv).is_error());
EXPECT(parse("INSERT INTO"sv).is_error());
EXPECT(parse("INSERT INTO table_name"sv).is_error());
EXPECT(parse("INSERT INTO table_name (column_name)"sv).is_error());
EXPECT(parse("INSERT INTO table_name (column_name, ) DEFAULT VALUES;"sv).is_error());
EXPECT(parse("INSERT INTO table_name VALUES"sv).is_error());
EXPECT(parse("INSERT INTO table_name VALUES ();"sv).is_error());
EXPECT(parse("INSERT INTO table_name VALUES (1)"sv).is_error());
EXPECT(parse("INSERT INTO table_name VALUES SELECT"sv).is_error());
EXPECT(parse("INSERT INTO table_name VALUES EXISTS"sv).is_error());
EXPECT(parse("INSERT INTO table_name VALUES NOT"sv).is_error());
EXPECT(parse("INSERT INTO table_name VALUES EXISTS (SELECT 1)"sv).is_error());
EXPECT(parse("INSERT INTO table_name VALUES (SELECT)"sv).is_error());
EXPECT(parse("INSERT INTO table_name VALUES (EXISTS SELECT)"sv).is_error());
EXPECT(parse("INSERT INTO table_name VALUES ((SELECT))"sv).is_error());
EXPECT(parse("INSERT INTO table_name VALUES (EXISTS (SELECT))"sv).is_error());
EXPECT(parse("INSERT INTO table_name SELECT"sv).is_error());
EXPECT(parse("INSERT INTO table_name SELECT * from table_name"sv).is_error());
EXPECT(parse("INSERT OR INTO table_name DEFAULT VALUES;"sv).is_error());
EXPECT(parse("INSERT OR foo INTO table_name DEFAULT VALUES;"sv).is_error());
auto validate = [](StringView sql, SQL::AST::ConflictResolution expected_conflict_resolution, StringView expected_schema, StringView expected_table, StringView expected_alias, Vector<StringView> expected_column_names, Vector<size_t> expected_chain_sizes, bool expect_select_statement) {
auto statement = TRY_OR_FAIL(parse(sql));
EXPECT(is<SQL::AST::Insert>(*statement));
auto const& insert = static_cast<const SQL::AST::Insert&>(*statement);
EXPECT_EQ(insert.conflict_resolution(), expected_conflict_resolution);
EXPECT_EQ(insert.schema_name(), expected_schema);
EXPECT_EQ(insert.table_name(), expected_table);
EXPECT_EQ(insert.alias(), expected_alias);
auto const& column_names = insert.column_names();
EXPECT_EQ(column_names.size(), expected_column_names.size());
for (size_t i = 0; i < column_names.size(); ++i)
EXPECT_EQ(column_names[i], expected_column_names[i]);
EXPECT_EQ(insert.has_expressions(), !expected_chain_sizes.is_empty());
if (insert.has_expressions()) {
auto const& chained_expressions = insert.chained_expressions();
EXPECT_EQ(chained_expressions.size(), expected_chain_sizes.size());
for (size_t i = 0; i < chained_expressions.size(); ++i) {
auto const& chained_expression = chained_expressions[i];
auto const& expressions = chained_expression->expressions();
EXPECT_EQ(expressions.size(), expected_chain_sizes[i]);
for (auto const& expression : expressions)
EXPECT(!is<SQL::AST::ErrorExpression>(expression));
}
}
EXPECT_EQ(insert.has_selection(), expect_select_statement);
EXPECT_EQ(insert.default_values(), expected_chain_sizes.is_empty() && !expect_select_statement);
};
validate("INSERT OR ABORT INTO table_name DEFAULT VALUES;"sv, SQL::AST::ConflictResolution::Abort, {}, "TABLE_NAME"sv, {}, {}, {}, false);
validate("INSERT OR FAIL INTO table_name DEFAULT VALUES;"sv, SQL::AST::ConflictResolution::Fail, {}, "TABLE_NAME"sv, {}, {}, {}, false);
validate("INSERT OR IGNORE INTO table_name DEFAULT VALUES;"sv, SQL::AST::ConflictResolution::Ignore, {}, "TABLE_NAME"sv, {}, {}, {}, false);
validate("INSERT OR REPLACE INTO table_name DEFAULT VALUES;"sv, SQL::AST::ConflictResolution::Replace, {}, "TABLE_NAME"sv, {}, {}, {}, false);
validate("INSERT OR ROLLBACK INTO table_name DEFAULT VALUES;"sv, SQL::AST::ConflictResolution::Rollback, {}, "TABLE_NAME"sv, {}, {}, {}, false);
auto resolution = SQL::AST::ConflictResolution::Abort;
validate("INSERT INTO table_name DEFAULT VALUES;"sv, resolution, {}, "TABLE_NAME"sv, {}, {}, {}, false);
validate("INSERT INTO schema_name.table_name DEFAULT VALUES;"sv, resolution, "SCHEMA_NAME"sv, "TABLE_NAME"sv, {}, {}, {}, false);
validate("INSERT INTO table_name AS foo DEFAULT VALUES;"sv, resolution, {}, "TABLE_NAME"sv, "FOO"sv, {}, {}, false);
validate("INSERT INTO table_name (column_name) DEFAULT VALUES;"sv, resolution, {}, "TABLE_NAME"sv, {}, { "COLUMN_NAME"sv }, {}, false);
validate("INSERT INTO table_name (column1, column2) DEFAULT VALUES;"sv, resolution, {}, "TABLE_NAME"sv, {}, { "COLUMN1"sv, "COLUMN2"sv }, {}, false);
validate("INSERT INTO table_name VALUES (1);"sv, resolution, {}, "TABLE_NAME"sv, {}, {}, { 1 }, false);
validate("INSERT INTO table_name VALUES (1, 2);"sv, resolution, {}, "TABLE_NAME"sv, {}, {}, { 2 }, false);
validate("INSERT INTO table_name VALUES (1, 2), (3, 4, 5);"sv, resolution, {}, "TABLE_NAME"sv, {}, {}, { 2, 3 }, false);
validate("INSERT INTO table_name VALUES ((SELECT 1));"sv, resolution, {}, "TABLE_NAME"sv, {}, {}, { 1 }, false);
validate("INSERT INTO table_name VALUES (EXISTS (SELECT 1));"sv, resolution, {}, "TABLE_NAME"sv, {}, {}, { 1 }, false);
validate("INSERT INTO table_name VALUES (NOT EXISTS (SELECT 1));"sv, resolution, {}, "TABLE_NAME"sv, {}, {}, { 1 }, false);
validate("INSERT INTO table_name VALUES ((SELECT 1), (SELECT 1));"sv, resolution, {}, "TABLE_NAME"sv, {}, {}, { 2 }, false);
validate("INSERT INTO table_name VALUES ((SELECT 1), (SELECT 1)), ((SELECT 1), (SELECT 1), (SELECT 1));"sv, resolution, {}, "TABLE_NAME"sv, {}, {}, { 2, 3 }, false);
validate("INSERT INTO table_name SELECT * FROM table_name;"sv, resolution, {}, "TABLE_NAME"sv, {}, {}, {}, true);
}
TEST_CASE(update)
{
EXPECT(parse("UPDATE"sv).is_error());
EXPECT(parse("UPDATE table_name"sv).is_error());
EXPECT(parse("UPDATE table_name SET"sv).is_error());
EXPECT(parse("UPDATE table_name SET column_name"sv).is_error());
EXPECT(parse("UPDATE table_name SET column_name=4"sv).is_error());
EXPECT(parse("UPDATE table_name SET column_name=4, ;"sv).is_error());
EXPECT(parse("UPDATE table_name SET (column_name)=4"sv).is_error());
EXPECT(parse("UPDATE table_name SET (column_name)=EXISTS"sv).is_error());
EXPECT(parse("UPDATE table_name SET (column_name)=SELECT"sv).is_error());
EXPECT(parse("UPDATE table_name SET (column_name)=(SELECT)"sv).is_error());
EXPECT(parse("UPDATE table_name SET (column_name)=NOT (SELECT 1)"sv).is_error());
EXPECT(parse("UPDATE table_name SET (column_name)=4, ;"sv).is_error());
EXPECT(parse("UPDATE table_name SET (column_name, )=4;"sv).is_error());
EXPECT(parse("UPDATE table_name SET column_name=4 FROM"sv).is_error());
EXPECT(parse("UPDATE table_name SET column_name=4 FROM table_name"sv).is_error());
EXPECT(parse("UPDATE table_name SET column_name=4 WHERE"sv).is_error());
EXPECT(parse("UPDATE table_name SET column_name=4 WHERE EXISTS"sv).is_error());
EXPECT(parse("UPDATE table_name SET column_name=4 WHERE NOT"sv).is_error());
EXPECT(parse("UPDATE table_name SET column_name=4 WHERE NOT EXISTS"sv).is_error());
EXPECT(parse("UPDATE table_name SET column_name=4 WHERE SELECT"sv).is_error());
EXPECT(parse("UPDATE table_name SET column_name=4 WHERE (SELECT)"sv).is_error());
EXPECT(parse("UPDATE table_name SET column_name=4 WHERE NOT (SELECT)"sv).is_error());
EXPECT(parse("UPDATE table_name SET column_name=4 WHERE 1==1"sv).is_error());
EXPECT(parse("UPDATE table_name SET column_name=4 RETURNING"sv).is_error());
EXPECT(parse("UPDATE table_name SET column_name=4 RETURNING *"sv).is_error());
EXPECT(parse("UPDATE table_name SET column_name=4 RETURNING column_name"sv).is_error());
EXPECT(parse("UPDATE table_name SET column_name=4 RETURNING column_name AS"sv).is_error());
EXPECT(parse("UPDATE OR table_name SET column_name=4;"sv).is_error());
EXPECT(parse("UPDATE OR foo table_name SET column_name=4;"sv).is_error());
auto validate = [](StringView sql, SQL::AST::ConflictResolution expected_conflict_resolution, StringView expected_schema, StringView expected_table, StringView expected_alias, Vector<Vector<ByteString>> expected_update_columns, bool expect_where_clause, bool expect_returning_clause, Vector<StringView> expected_returned_column_aliases) {
auto statement = TRY_OR_FAIL(parse(sql));
EXPECT(is<SQL::AST::Update>(*statement));
auto const& update = static_cast<const SQL::AST::Update&>(*statement);
EXPECT_EQ(update.conflict_resolution(), expected_conflict_resolution);
auto const& qualified_table_name = update.qualified_table_name();
EXPECT_EQ(qualified_table_name->schema_name(), expected_schema);
EXPECT_EQ(qualified_table_name->table_name(), expected_table);
EXPECT_EQ(qualified_table_name->alias(), expected_alias);
auto const& update_columns = update.update_columns();
EXPECT_EQ(update_columns.size(), expected_update_columns.size());
for (size_t i = 0; i < update_columns.size(); ++i) {
auto const& update_column = update_columns[i];
auto const& expected_update_column = expected_update_columns[i];
EXPECT_EQ(update_column.column_names.size(), expected_update_column.size());
EXPECT(!is<SQL::AST::ErrorExpression>(*update_column.expression));
for (size_t j = 0; j < update_column.column_names.size(); ++j)
EXPECT_EQ(update_column.column_names[j], expected_update_column[j]);
}
auto const& where_clause = update.where_clause();
EXPECT_EQ(where_clause.is_null(), !expect_where_clause);
if (where_clause)
EXPECT(!is<SQL::AST::ErrorExpression>(*where_clause));
auto const& returning_clause = update.returning_clause();
EXPECT_EQ(returning_clause.is_null(), !expect_returning_clause);
if (returning_clause) {
EXPECT_EQ(returning_clause->columns().size(), expected_returned_column_aliases.size());
for (size_t i = 0; i < returning_clause->columns().size(); ++i) {
auto const& column = returning_clause->columns()[i];
auto const& expected_column_alias = expected_returned_column_aliases[i];
EXPECT(!is<SQL::AST::ErrorExpression>(*column.expression));
EXPECT_EQ(column.column_alias, expected_column_alias);
}
}
};
Vector<Vector<ByteString>> update_columns { { "COLUMN_NAME" } };
validate("UPDATE OR ABORT table_name SET column_name=1;"sv, SQL::AST::ConflictResolution::Abort, {}, "TABLE_NAME"sv, {}, update_columns, false, false, {});
validate("UPDATE OR FAIL table_name SET column_name=1;"sv, SQL::AST::ConflictResolution::Fail, {}, "TABLE_NAME"sv, {}, update_columns, false, false, {});
validate("UPDATE OR IGNORE table_name SET column_name=1;"sv, SQL::AST::ConflictResolution::Ignore, {}, "TABLE_NAME"sv, {}, update_columns, false, false, {});
validate("UPDATE OR REPLACE table_name SET column_name=1;"sv, SQL::AST::ConflictResolution::Replace, {}, "TABLE_NAME"sv, {}, update_columns, false, false, {});
validate("UPDATE OR ROLLBACK table_name SET column_name=1;"sv, SQL::AST::ConflictResolution::Rollback, {}, "TABLE_NAME"sv, {}, update_columns, false, false, {});
auto resolution = SQL::AST::ConflictResolution::Abort;
validate("UPDATE table_name SET column_name=1;"sv, resolution, {}, "TABLE_NAME"sv, {}, update_columns, false, false, {});
validate("UPDATE schema_name.table_name SET column_name=1;"sv, resolution, "SCHEMA_NAME"sv, "TABLE_NAME"sv, {}, update_columns, false, false, {});
validate("UPDATE table_name AS foo SET column_name=1;"sv, resolution, {}, "TABLE_NAME"sv, "FOO"sv, update_columns, false, false, {});
validate("UPDATE table_name SET column_name=1;"sv, resolution, {}, "TABLE_NAME"sv, {}, { { "COLUMN_NAME"sv } }, false, false, {});
validate("UPDATE table_name SET column_name=(SELECT 1);"sv, resolution, {}, "TABLE_NAME"sv, {}, { { "COLUMN_NAME"sv } }, false, false, {});
validate("UPDATE table_name SET column_name=EXISTS (SELECT 1);"sv, resolution, {}, "TABLE_NAME"sv, {}, { { "COLUMN_NAME"sv } }, false, false, {});
validate("UPDATE table_name SET column_name=NOT EXISTS (SELECT 1);"sv, resolution, {}, "TABLE_NAME"sv, {}, { { "COLUMN_NAME"sv } }, false, false, {});
validate("UPDATE table_name SET column1=1, column2=2;"sv, resolution, {}, "TABLE_NAME"sv, {}, { { "COLUMN1"sv }, { "COLUMN2"sv } }, false, false, {});
validate("UPDATE table_name SET (column1, column2)=1, column3=2;"sv, resolution, {}, "TABLE_NAME"sv, {}, { { "COLUMN1"sv, "COLUMN2"sv }, { "COLUMN3"sv } }, false, false, {});
validate("UPDATE table_name SET column_name=1 WHERE 1==1;"sv, resolution, {}, "TABLE_NAME"sv, {}, update_columns, true, false, {});
validate("UPDATE table_name SET column_name=1 WHERE (SELECT 1);"sv, resolution, {}, "TABLE_NAME"sv, {}, { { "COLUMN_NAME"sv } }, true, false, {});
validate("UPDATE table_name SET column_name=1 WHERE EXISTS (SELECT 1);"sv, resolution, {}, "TABLE_NAME"sv, {}, { { "COLUMN_NAME"sv } }, true, false, {});
validate("UPDATE table_name SET column_name=1 WHERE NOT EXISTS (SELECT 1);"sv, resolution, {}, "TABLE_NAME"sv, {}, { { "COLUMN_NAME"sv } }, true, false, {});
validate("UPDATE table_name SET column_name=1 RETURNING *;"sv, resolution, {}, "TABLE_NAME"sv, {}, update_columns, false, true, {});
validate("UPDATE table_name SET column_name=1 RETURNING column_name;"sv, resolution, {}, "TABLE_NAME"sv, {}, update_columns, false, true, { {} });
validate("UPDATE table_name SET column_name=1 RETURNING column_name AS alias;"sv, resolution, {}, "TABLE_NAME"sv, {}, update_columns, false, true, { "ALIAS"sv });
validate("UPDATE table_name SET column_name=1 RETURNING column1 AS alias1, column2 AS alias2;"sv, resolution, {}, "TABLE_NAME"sv, {}, update_columns, false, true, { "ALIAS1"sv, "ALIAS2"sv });
}
TEST_CASE(delete_)
{
EXPECT(parse("DELETE"sv).is_error());
EXPECT(parse("DELETE FROM"sv).is_error());
EXPECT(parse("DELETE FROM table_name"sv).is_error());
EXPECT(parse("DELETE FROM table_name WHERE"sv).is_error());
EXPECT(parse("DELETE FROM table_name WHERE EXISTS"sv).is_error());
EXPECT(parse("DELETE FROM table_name WHERE NOT"sv).is_error());
EXPECT(parse("DELETE FROM table_name WHERE NOT (SELECT 1)"sv).is_error());
EXPECT(parse("DELETE FROM table_name WHERE NOT EXISTS"sv).is_error());
EXPECT(parse("DELETE FROM table_name WHERE SELECT"sv).is_error());
EXPECT(parse("DELETE FROM table_name WHERE (SELECT)"sv).is_error());
EXPECT(parse("DELETE FROM table_name WHERE 15"sv).is_error());
EXPECT(parse("DELETE FROM table_name WHERE 15 RETURNING"sv).is_error());
EXPECT(parse("DELETE FROM table_name WHERE 15 RETURNING *"sv).is_error());
EXPECT(parse("DELETE FROM table_name WHERE 15 RETURNING column_name"sv).is_error());
EXPECT(parse("DELETE FROM table_name WHERE 15 RETURNING column_name AS;"sv).is_error());
EXPECT(parse("DELETE FROM table_name WHERE (');"sv).is_error());
auto validate = [](StringView sql, StringView expected_schema, StringView expected_table, StringView expected_alias, bool expect_where_clause, bool expect_returning_clause, Vector<StringView> expected_returned_column_aliases) {
auto statement = TRY_OR_FAIL(parse(sql));
EXPECT(is<SQL::AST::Delete>(*statement));
auto const& delete_ = static_cast<const SQL::AST::Delete&>(*statement);
auto const& qualified_table_name = delete_.qualified_table_name();
EXPECT_EQ(qualified_table_name->schema_name(), expected_schema);
EXPECT_EQ(qualified_table_name->table_name(), expected_table);
EXPECT_EQ(qualified_table_name->alias(), expected_alias);
auto const& where_clause = delete_.where_clause();
EXPECT_EQ(where_clause.is_null(), !expect_where_clause);
if (where_clause)
EXPECT(!is<SQL::AST::ErrorExpression>(*where_clause));
auto const& returning_clause = delete_.returning_clause();
EXPECT_EQ(returning_clause.is_null(), !expect_returning_clause);
if (returning_clause) {
EXPECT_EQ(returning_clause->columns().size(), expected_returned_column_aliases.size());
for (size_t i = 0; i < returning_clause->columns().size(); ++i) {
auto const& column = returning_clause->columns()[i];
auto const& expected_column_alias = expected_returned_column_aliases[i];
EXPECT(!is<SQL::AST::ErrorExpression>(*column.expression));
EXPECT_EQ(column.column_alias, expected_column_alias);
}
}
};
validate("DELETE FROM table_name;"sv, {}, "TABLE_NAME"sv, {}, false, false, {});
validate("DELETE FROM schema_name.table_name;"sv, "SCHEMA_NAME"sv, "TABLE_NAME"sv, {}, false, false, {});
validate("DELETE FROM schema_name.table_name AS alias;"sv, "SCHEMA_NAME"sv, "TABLE_NAME"sv, "ALIAS"sv, false, false, {});
validate("DELETE FROM table_name WHERE (1 == 1);"sv, {}, "TABLE_NAME"sv, {}, true, false, {});
validate("DELETE FROM table_name WHERE EXISTS (SELECT 1);"sv, {}, "TABLE_NAME"sv, {}, true, false, {});
validate("DELETE FROM table_name WHERE NOT EXISTS (SELECT 1);"sv, {}, "TABLE_NAME"sv, {}, true, false, {});
validate("DELETE FROM table_name WHERE (SELECT 1);"sv, {}, "TABLE_NAME"sv, {}, true, false, {});
validate("DELETE FROM table_name RETURNING *;"sv, {}, "TABLE_NAME"sv, {}, false, true, {});
validate("DELETE FROM table_name RETURNING column_name;"sv, {}, "TABLE_NAME"sv, {}, false, true, { {} });
validate("DELETE FROM table_name RETURNING column_name AS alias;"sv, {}, "TABLE_NAME"sv, {}, false, true, { "ALIAS"sv });
validate("DELETE FROM table_name RETURNING column1 AS alias1, column2 AS alias2;"sv, {}, "TABLE_NAME"sv, {}, false, true, { "ALIAS1"sv, "ALIAS2"sv });
}
TEST_CASE(select)
{
EXPECT(parse("SELECT"sv).is_error());
EXPECT(parse("SELECT;"sv).is_error());
EXPECT(parse("SELECT DISTINCT;"sv).is_error());
EXPECT(parse("SELECT ALL;"sv).is_error());
EXPECT(parse("SELECT *"sv).is_error());
EXPECT(parse("SELECT * FROM;"sv).is_error());
EXPECT(parse("SELECT table_name. FROM table_name;"sv).is_error());
EXPECT(parse("SELECT column_name AS FROM table_name;"sv).is_error());
EXPECT(parse("SELECT * FROM ("sv).is_error());
EXPECT(parse("SELECT * FROM ()"sv).is_error());
EXPECT(parse("SELECT * FROM ();"sv).is_error());
EXPECT(parse("SELECT * FROM (table_name1)"sv).is_error());
EXPECT(parse("SELECT * FROM (table_name1, )"sv).is_error());
EXPECT(parse("SELECT * FROM (table_name1, table_name2)"sv).is_error());
EXPECT(parse("SELECT * FROM table_name"sv).is_error());
EXPECT(parse("SELECT * FROM table_name AS;"sv).is_error());
EXPECT(parse("SELECT * FROM table_name WHERE;"sv).is_error());
EXPECT(parse("SELECT * FROM table_name WHERE 1 ==1"sv).is_error());
EXPECT(parse("SELECT * FROM table_name GROUP;"sv).is_error());
EXPECT(parse("SELECT * FROM table_name GROUP BY;"sv).is_error());
EXPECT(parse("SELECT * FROM table_name GROUP BY column_name"sv).is_error());
EXPECT(parse("SELECT * FROM table_name ORDER:"sv).is_error());
EXPECT(parse("SELECT * FROM table_name ORDER BY column_name"sv).is_error());
EXPECT(parse("SELECT * FROM table_name ORDER BY column_name COLLATE:"sv).is_error());
EXPECT(parse("SELECT * FROM table_name ORDER BY column_name COLLATE collation"sv).is_error());
EXPECT(parse("SELECT * FROM table_name ORDER BY column_name NULLS;"sv).is_error());
EXPECT(parse("SELECT * FROM table_name ORDER BY column_name NULLS SECOND;"sv).is_error());
EXPECT(parse("SELECT * FROM table_name LIMIT;"sv).is_error());
EXPECT(parse("SELECT * FROM table_name LIMIT 12"sv).is_error());
EXPECT(parse("SELECT * FROM table_name LIMIT 12 OFFSET;"sv).is_error());
EXPECT(parse("SELECT * FROM table_name LIMIT 12 OFFSET 15"sv).is_error());
EXPECT(parse("SELECT * FROM table_name LIMIT 15, 16;"sv).is_error());
struct Type {
SQL::AST::ResultType type;
StringView table_name_or_column_alias {};
};
struct From {
StringView schema_name;
StringView table_name;
StringView table_alias;
};
struct Ordering {
ByteString collation_name;
SQL::Order order;
SQL::Nulls nulls;
};
auto validate = [](StringView sql, Vector<Type> expected_columns, Vector<From> expected_from_list, bool expect_where_clause, size_t expected_group_by_size, bool expect_having_clause, Vector<Ordering> expected_ordering, bool expect_limit_clause, bool expect_offset_clause) {
auto statement = TRY_OR_FAIL(parse(sql));
EXPECT(is<SQL::AST::Select>(*statement));
auto const& select = static_cast<const SQL::AST::Select&>(*statement);
auto const& result_column_list = select.result_column_list();
EXPECT_EQ(result_column_list.size(), expected_columns.size());
for (size_t i = 0; i < result_column_list.size(); ++i) {
auto const& result_column = result_column_list[i];
auto const& expected_column = expected_columns[i];
EXPECT_EQ(result_column->type(), expected_column.type);
switch (result_column->type()) {
case SQL::AST::ResultType::All:
EXPECT(expected_column.table_name_or_column_alias.is_null());
break;
case SQL::AST::ResultType::Table:
EXPECT_EQ(result_column->table_name(), expected_column.table_name_or_column_alias);
break;
case SQL::AST::ResultType::Expression:
EXPECT_EQ(result_column->column_alias(), expected_column.table_name_or_column_alias);
break;
}
}
auto const& table_or_subquery_list = select.table_or_subquery_list();
EXPECT_EQ(table_or_subquery_list.size(), expected_from_list.size());
for (size_t i = 0; i < table_or_subquery_list.size(); ++i) {
auto const& result_from = table_or_subquery_list[i];
auto const& expected_from = expected_from_list[i];
EXPECT_EQ(result_from->schema_name(), expected_from.schema_name);
EXPECT_EQ(result_from->table_name(), expected_from.table_name);
EXPECT_EQ(result_from->table_alias(), expected_from.table_alias);
}
auto const& where_clause = select.where_clause();
EXPECT_EQ(where_clause.is_null(), !expect_where_clause);
if (where_clause)
EXPECT(!is<SQL::AST::ErrorExpression>(*where_clause));
auto const& group_by_clause = select.group_by_clause();
EXPECT_EQ(group_by_clause.is_null(), (expected_group_by_size == 0));
if (group_by_clause) {
auto const& group_by_list = group_by_clause->group_by_list();
EXPECT_EQ(group_by_list.size(), expected_group_by_size);
for (size_t i = 0; i < group_by_list.size(); ++i)
EXPECT(!is<SQL::AST::ErrorExpression>(group_by_list[i]));
auto const& having_clause = group_by_clause->having_clause();
EXPECT_EQ(having_clause.is_null(), !expect_having_clause);
if (having_clause)
EXPECT(!is<SQL::AST::ErrorExpression>(*having_clause));
}
auto const& ordering_term_list = select.ordering_term_list();
EXPECT_EQ(ordering_term_list.size(), expected_ordering.size());
for (size_t i = 0; i < ordering_term_list.size(); ++i) {
auto const& result_order = ordering_term_list[i];
auto const& expected_order = expected_ordering[i];
EXPECT(!is<SQL::AST::ErrorExpression>(*result_order->expression()));
EXPECT_EQ(result_order->collation_name(), expected_order.collation_name);
EXPECT_EQ(result_order->order(), expected_order.order);
EXPECT_EQ(result_order->nulls(), expected_order.nulls);
}
auto const& limit_clause = select.limit_clause();
EXPECT_EQ(limit_clause.is_null(), !expect_limit_clause);
if (limit_clause) {
auto const& limit_expression = limit_clause->limit_expression();
EXPECT(!is<SQL::AST::ErrorExpression>(*limit_expression));
auto const& offset_expression = limit_clause->offset_expression();
EXPECT_EQ(offset_expression.is_null(), !expect_offset_clause);
if (offset_expression)
EXPECT(!is<SQL::AST::ErrorExpression>(*offset_expression));
}
};
Vector<Type> all { { SQL::AST::ResultType::All } };
Vector<From> from { { {}, "TABLE_NAME"sv, {} } };
validate("SELECT * FROM table_name;"sv, { { SQL::AST::ResultType::All } }, from, false, 0, false, {}, false, false);
validate("SELECT table_name.* FROM table_name;"sv, { { SQL::AST::ResultType::Table, "TABLE_NAME"sv } }, from, false, 0, false, {}, false, false);
validate("SELECT column_name AS alias FROM table_name;"sv, { { SQL::AST::ResultType::Expression, "ALIAS"sv } }, from, false, 0, false, {}, false, false);
validate("SELECT table_name.column_name AS alias FROM table_name;"sv, { { SQL::AST::ResultType::Expression, "ALIAS"sv } }, from, false, 0, false, {}, false, false);
validate("SELECT schema_name.table_name.column_name AS alias FROM table_name;"sv, { { SQL::AST::ResultType::Expression, "ALIAS"sv } }, from, false, 0, false, {}, false, false);
validate("SELECT column_name AS alias, *, table_name.* FROM table_name;"sv, { { SQL::AST::ResultType::Expression, "ALIAS"sv }, { SQL::AST::ResultType::All }, { SQL::AST::ResultType::Table, "TABLE_NAME"sv } }, from, false, 0, false, {}, false, false);
validate("SELECT * FROM table_name;"sv, all, { { {}, "TABLE_NAME"sv, {} } }, false, 0, false, {}, false, false);
validate("SELECT * FROM schema_name.table_name;"sv, all, { { "SCHEMA_NAME"sv, "TABLE_NAME"sv, {} } }, false, 0, false, {}, false, false);
validate("SELECT * FROM schema_name.table_name AS alias;"sv, all, { { "SCHEMA_NAME"sv, "TABLE_NAME"sv, "ALIAS"sv } }, false, 0, false, {}, false, false);
validate("SELECT * FROM schema_name.table_name AS alias, table_name2, table_name3 AS table_name4;"sv, all, { { "SCHEMA_NAME"sv, "TABLE_NAME"sv, "ALIAS"sv }, { {}, "TABLE_NAME2"sv, {} }, { {}, "TABLE_NAME3"sv, "TABLE_NAME4"sv } }, false, 0, false, {}, false, false);
validate("SELECT * FROM table_name WHERE column_name IS NOT NULL;"sv, all, from, true, 0, false, {}, false, false);
validate("SELECT * FROM table_name GROUP BY column_name;"sv, all, from, false, 1, false, {}, false, false);
validate("SELECT * FROM table_name GROUP BY column1, column2, column3;"sv, all, from, false, 3, false, {}, false, false);
validate("SELECT * FROM table_name GROUP BY column_name HAVING 'abc';"sv, all, from, false, 1, true, {}, false, false);
validate("SELECT * FROM table_name ORDER BY column_name;"sv, all, from, false, 0, false, { { {}, SQL::Order::Ascending, SQL::Nulls::First } }, false, false);
validate("SELECT * FROM table_name ORDER BY column_name COLLATE collation;"sv, all, from, false, 0, false, { { "COLLATION"sv, SQL::Order::Ascending, SQL::Nulls::First } }, false, false);
validate("SELECT * FROM table_name ORDER BY column_name ASC;"sv, all, from, false, 0, false, { { {}, SQL::Order::Ascending, SQL::Nulls::First } }, false, false);
validate("SELECT * FROM table_name ORDER BY column_name DESC;"sv, all, from, false, 0, false, { { {}, SQL::Order::Descending, SQL::Nulls::Last } }, false, false);
validate("SELECT * FROM table_name ORDER BY column_name ASC NULLS LAST;"sv, all, from, false, 0, false, { { {}, SQL::Order::Ascending, SQL::Nulls::Last } }, false, false);
validate("SELECT * FROM table_name ORDER BY column_name DESC NULLS FIRST;"sv, all, from, false, 0, false, { { {}, SQL::Order::Descending, SQL::Nulls::First } }, false, false);
validate("SELECT * FROM table_name ORDER BY column1, column2 DESC, column3 NULLS LAST;"sv, all, from, false, 0, false, { { {}, SQL::Order::Ascending, SQL::Nulls::First }, { {}, SQL::Order::Descending, SQL::Nulls::Last }, { {}, SQL::Order::Ascending, SQL::Nulls::Last } }, false, false);
validate("SELECT * FROM table_name LIMIT 15;"sv, all, from, false, 0, false, {}, true, false);
validate("SELECT * FROM table_name LIMIT 15 OFFSET 16;"sv, all, from, false, 0, false, {}, true, true);
}
TEST_CASE(common_table_expression)
{
EXPECT(parse("WITH"sv).is_error());
EXPECT(parse("WITH;"sv).is_error());
EXPECT(parse("WITH DELETE FROM table_name;"sv).is_error());
EXPECT(parse("WITH table_name DELETE FROM table_name;"sv).is_error());
EXPECT(parse("WITH table_name AS DELETE FROM table_name;"sv).is_error());
EXPECT(parse("WITH RECURSIVE table_name DELETE FROM table_name;"sv).is_error());
EXPECT(parse("WITH RECURSIVE table_name AS DELETE FROM table_name;"sv).is_error());
// Below are otherwise valid common-table-expressions, but attached to statements which do not allow them.
EXPECT(parse("WITH table_name AS (SELECT * AS TABLE) CREATE TABLE test ( column1 );"sv).is_error());
EXPECT(parse("WITH table_name AS (SELECT * FROM table_name) DROP TABLE test;"sv).is_error());
struct SelectedTableList {
struct SelectedTable {
StringView table_name {};
Vector<StringView> column_names {};
};
bool recursive { false };
Vector<SelectedTable> selected_tables {};
};
auto validate = [](StringView sql, SelectedTableList expected_selected_tables) {
auto statement = TRY_OR_FAIL(parse(sql));
EXPECT(is<SQL::AST::Delete>(*statement));
auto const& delete_ = static_cast<const SQL::AST::Delete&>(*statement);
auto const& common_table_expression_list = delete_.common_table_expression_list();
EXPECT(!common_table_expression_list.is_null());
EXPECT_EQ(common_table_expression_list->recursive(), expected_selected_tables.recursive);
auto const& common_table_expressions = common_table_expression_list->common_table_expressions();
EXPECT_EQ(common_table_expressions.size(), expected_selected_tables.selected_tables.size());
for (size_t i = 0; i < common_table_expressions.size(); ++i) {
auto const& common_table_expression = common_table_expressions[i];
auto const& expected_common_table_expression = expected_selected_tables.selected_tables[i];
EXPECT_EQ(common_table_expression->table_name(), expected_common_table_expression.table_name);
EXPECT_EQ(common_table_expression->column_names().size(), expected_common_table_expression.column_names.size());
for (size_t j = 0; j < common_table_expression->column_names().size(); ++j)
EXPECT_EQ(common_table_expression->column_names()[j], expected_common_table_expression.column_names[j]);
}
};
validate("WITH table_name AS (SELECT * FROM table_name) DELETE FROM table_name;"sv, { false, { { "TABLE_NAME"sv } } });
validate("WITH table_name (column_name) AS (SELECT * FROM table_name) DELETE FROM table_name;"sv, { false, { { "TABLE_NAME"sv, { "COLUMN_NAME"sv } } } });
validate("WITH table_name (column1, column2) AS (SELECT * FROM table_name) DELETE FROM table_name;"sv, { false, { { "TABLE_NAME"sv, { "COLUMN1"sv, "COLUMN2"sv } } } });
validate("WITH RECURSIVE table_name AS (SELECT * FROM table_name) DELETE FROM table_name;"sv, { true, { { "TABLE_NAME"sv, {} } } });
}
TEST_CASE(nested_subquery_limit)
{
auto subquery = ByteString::formatted("{:(^{}}table_name{:)^{}}", "", SQL::AST::Limits::maximum_subquery_depth - 1, "", SQL::AST::Limits::maximum_subquery_depth - 1);
EXPECT(!parse(ByteString::formatted("SELECT * FROM {};"sv, subquery)).is_error());
EXPECT(parse(ByteString::formatted("SELECT * FROM ({});"sv, subquery)).is_error());
}
TEST_CASE(bound_parameter_limit)
{
auto subquery = ByteString::repeated("?, "sv, SQL::AST::Limits::maximum_bound_parameters);
EXPECT(!parse(ByteString::formatted("INSERT INTO table_name VALUES ({}42);"sv, subquery)).is_error());
EXPECT(parse(ByteString::formatted("INSERT INTO table_name VALUES ({}?);"sv, subquery)).is_error());
}
TEST_CASE(describe_table)
{
EXPECT(parse("DESCRIBE"sv).is_error());
EXPECT(parse("DESCRIBE;"sv).is_error());
EXPECT(parse("DESCRIBE TABLE;"sv).is_error());
EXPECT(parse("DESCRIBE table_name;"sv).is_error());
auto validate = [](StringView sql, StringView expected_schema, StringView expected_table) {
auto statement = TRY_OR_FAIL(parse(sql));
EXPECT(is<SQL::AST::DescribeTable>(*statement));
auto const& describe_table_statement = static_cast<const SQL::AST::DescribeTable&>(*statement);
EXPECT_EQ(describe_table_statement.qualified_table_name()->schema_name(), expected_schema);
EXPECT_EQ(describe_table_statement.qualified_table_name()->table_name(), expected_table);
};
validate("DESCRIBE TABLE TableName;"sv, {}, "TABLENAME"sv);
validate("DESCRIBE TABLE SchemaName.TableName;"sv, "SCHEMANAME"sv, "TABLENAME"sv);
}

File diff suppressed because it is too large Load diff

View file

@ -4,5 +4,5 @@ set(TEST_SOURCES
)
foreach(source IN LISTS TEST_SOURCES)
serenity_test("${source}" LibSQL LIBS LibSQL LibIPC)
serenity_test("${source}" Test)
endforeach()

View file

@ -24,7 +24,6 @@ add_subdirectory(LibProtocol)
add_subdirectory(LibRegex)
add_subdirectory(LibRIFF)
add_subdirectory(LibSanitizer)
add_subdirectory(LibSQL)
add_subdirectory(LibSyntax)
add_subdirectory(LibTest)
add_subdirectory(LibTextCodec)

File diff suppressed because it is too large Load diff

View file

@ -1,25 +0,0 @@
/*
* Copyright (c) 2021, Jan de Visser <jan@de-visser.net>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibSQL/AST/AST.h>
#include <LibSQL/Database.h>
#include <LibSQL/Meta.h>
namespace SQL::AST {
ResultOr<ResultSet> CreateSchema::execute(ExecutionContext& context) const
{
auto schema_def = TRY(SchemaDef::create(m_schema_name));
if (auto result = context.database->add_schema(*schema_def); result.is_error()) {
if (result.error().error() != SQLErrorCode::SchemaExists || m_is_error_if_schema_exists)
return result.release_error();
}
return ResultSet { SQLCommand::Create };
}
}

View file

@ -1,42 +0,0 @@
/*
* Copyright (c) 2021, Jan de Visser <jan@de-visser.net>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibSQL/AST/AST.h>
#include <LibSQL/Database.h>
namespace SQL::AST {
ResultOr<ResultSet> CreateTable::execute(ExecutionContext& context) const
{
auto schema_def = TRY(context.database->get_schema(m_schema_name));
auto table_def = TRY(TableDef::create(schema_def, m_table_name));
for (auto const& column : m_columns) {
SQLType type;
if (column->type_name()->name().is_one_of("VARCHAR"sv, "TEXT"sv))
type = SQLType::Text;
else if (column->type_name()->name().is_one_of("INT"sv, "INTEGER"sv))
type = SQLType::Integer;
else if (column->type_name()->name().is_one_of("FLOAT"sv, "NUMBER"sv))
type = SQLType::Float;
else if (column->type_name()->name().is_one_of("BOOL"sv, "BOOLEAN"sv))
type = SQLType::Boolean;
else
return Result { SQLCommand::Create, SQLErrorCode::InvalidType, column->type_name()->name() };
table_def->append_column(column->name(), type);
}
if (auto result = context.database->add_table(*table_def); result.is_error()) {
if (result.error().error() != SQLErrorCode::TableExists || m_is_error_if_table_exists)
return result.release_error();
}
return ResultSet { SQLCommand::Create };
}
}

View file

@ -1,40 +0,0 @@
/*
* Copyright (c) 2022, Tim Flynn <trflynn89@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibSQL/AST/AST.h>
#include <LibSQL/Database.h>
#include <LibSQL/Meta.h>
#include <LibSQL/Row.h>
namespace SQL::AST {
ResultOr<ResultSet> Delete::execute(ExecutionContext& context) const
{
auto const& schema_name = m_qualified_table_name->schema_name();
auto const& table_name = m_qualified_table_name->table_name();
auto table_def = TRY(context.database->get_table(schema_name, table_name));
ResultSet result { SQLCommand::Delete };
for (auto& table_row : TRY(context.database->select_all(*table_def))) {
context.current_row = &table_row;
if (auto const& where_clause = this->where_clause()) {
auto where_result = TRY(where_clause->evaluate(context)).to_bool();
if (!where_result.has_value() || !where_result.value())
continue;
}
TRY(context.database->remove(table_row));
// FIXME: Implement the RETURNING clause.
result.insert_row(table_row, {});
}
return result;
}
}

View file

@ -1,38 +0,0 @@
/*
* Copyright (c) 2021, Mahmoud Mandour <ma.mandourr@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibSQL/AST/AST.h>
#include <LibSQL/Database.h>
#include <LibSQL/Meta.h>
#include <LibSQL/ResultSet.h>
#include <LibSQL/Row.h>
namespace SQL::AST {
ResultOr<ResultSet> DescribeTable::execute(ExecutionContext& context) const
{
auto const& schema_name = m_qualified_table_name->schema_name();
auto const& table_name = m_qualified_table_name->table_name();
auto table_def = TRY(context.database->get_table(schema_name, table_name));
auto describe_table_def = MUST(context.database->get_table("master"sv, "internal_describe_table"sv));
auto descriptor = describe_table_def->to_tuple_descriptor();
ResultSet result { SQLCommand::Describe };
TRY(result.try_ensure_capacity(table_def->columns().size()));
for (auto& column : table_def->columns()) {
Tuple tuple(descriptor);
tuple[0] = column->name();
tuple[1] = SQLType_name(column->type());
result.insert_row(tuple, Tuple {});
}
return result;
}
}

View file

@ -1,244 +0,0 @@
/*
* Copyright (c) 2021, Jan de Visser <jan@de-visser.net>
* Copyright (c) 2022, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/StringView.h>
#include <LibRegex/Regex.h>
#include <LibSQL/AST/AST.h>
#include <LibSQL/Database.h>
namespace SQL::AST {
static constexpr auto s_posix_basic_metacharacters = ".^$*[]+\\"sv;
ResultOr<Value> NumericLiteral::evaluate(ExecutionContext&) const
{
return Value { value() };
}
ResultOr<Value> StringLiteral::evaluate(ExecutionContext&) const
{
return Value { value() };
}
ResultOr<Value> BooleanLiteral::evaluate(ExecutionContext&) const
{
return Value { value() };
}
ResultOr<Value> NullLiteral::evaluate(ExecutionContext&) const
{
return Value {};
}
ResultOr<Value> Placeholder::evaluate(ExecutionContext& context) const
{
if (parameter_index() >= context.placeholder_values.size())
return Result { SQLCommand::Unknown, SQLErrorCode::InvalidNumberOfPlaceholderValues };
return context.placeholder_values[parameter_index()];
}
ResultOr<Value> NestedExpression::evaluate(ExecutionContext& context) const
{
return expression()->evaluate(context);
}
ResultOr<Value> ChainedExpression::evaluate(ExecutionContext& context) const
{
Vector<Value> values;
TRY(values.try_ensure_capacity(expressions().size()));
for (auto& expression : expressions())
values.unchecked_append(TRY(expression->evaluate(context)));
return Value::create_tuple(move(values));
}
ResultOr<Value> BinaryOperatorExpression::evaluate(ExecutionContext& context) const
{
Value lhs_value = TRY(lhs()->evaluate(context));
Value rhs_value = TRY(rhs()->evaluate(context));
switch (type()) {
case BinaryOperator::Concatenate: {
if (lhs_value.type() != SQLType::Text)
return Result { SQLCommand::Unknown, SQLErrorCode::BooleanOperatorTypeMismatch, BinaryOperator_name(type()) };
AK::StringBuilder builder;
builder.append(lhs_value.to_byte_string());
builder.append(rhs_value.to_byte_string());
return Value(builder.to_byte_string());
}
case BinaryOperator::Multiplication:
return lhs_value.multiply(rhs_value);
case BinaryOperator::Division:
return lhs_value.divide(rhs_value);
case BinaryOperator::Modulo:
return lhs_value.modulo(rhs_value);
case BinaryOperator::Plus:
return lhs_value.add(rhs_value);
case BinaryOperator::Minus:
return lhs_value.subtract(rhs_value);
case BinaryOperator::ShiftLeft:
return lhs_value.shift_left(rhs_value);
case BinaryOperator::ShiftRight:
return lhs_value.shift_right(rhs_value);
case BinaryOperator::BitwiseAnd:
return lhs_value.bitwise_and(rhs_value);
case BinaryOperator::BitwiseOr:
return lhs_value.bitwise_or(rhs_value);
case BinaryOperator::LessThan:
return Value(lhs_value.compare(rhs_value) < 0);
case BinaryOperator::LessThanEquals:
return Value(lhs_value.compare(rhs_value) <= 0);
case BinaryOperator::GreaterThan:
return Value(lhs_value.compare(rhs_value) > 0);
case BinaryOperator::GreaterThanEquals:
return Value(lhs_value.compare(rhs_value) >= 0);
case BinaryOperator::Equals:
return Value(lhs_value.compare(rhs_value) == 0);
case BinaryOperator::NotEquals:
return Value(lhs_value.compare(rhs_value) != 0);
case BinaryOperator::And: {
auto lhs_bool_maybe = lhs_value.to_bool();
auto rhs_bool_maybe = rhs_value.to_bool();
if (!lhs_bool_maybe.has_value() || !rhs_bool_maybe.has_value())
return Result { SQLCommand::Unknown, SQLErrorCode::BooleanOperatorTypeMismatch, BinaryOperator_name(type()) };
return Value(lhs_bool_maybe.release_value() && rhs_bool_maybe.release_value());
}
case BinaryOperator::Or: {
auto lhs_bool_maybe = lhs_value.to_bool();
auto rhs_bool_maybe = rhs_value.to_bool();
if (!lhs_bool_maybe.has_value() || !rhs_bool_maybe.has_value())
return Result { SQLCommand::Unknown, SQLErrorCode::BooleanOperatorTypeMismatch, BinaryOperator_name(type()) };
return Value(lhs_bool_maybe.release_value() || rhs_bool_maybe.release_value());
}
default:
VERIFY_NOT_REACHED();
}
}
ResultOr<Value> UnaryOperatorExpression::evaluate(ExecutionContext& context) const
{
Value expression_value = TRY(NestedExpression::evaluate(context));
switch (type()) {
case UnaryOperator::Plus:
if (expression_value.type() == SQLType::Integer || expression_value.type() == SQLType::Float)
return expression_value;
return Result { SQLCommand::Unknown, SQLErrorCode::NumericOperatorTypeMismatch, UnaryOperator_name(type()) };
case UnaryOperator::Minus:
return expression_value.negate();
case UnaryOperator::Not:
if (expression_value.type() == SQLType::Boolean) {
expression_value = !expression_value.to_bool().value();
return expression_value;
}
return Result { SQLCommand::Unknown, SQLErrorCode::BooleanOperatorTypeMismatch, UnaryOperator_name(type()) };
case UnaryOperator::BitwiseNot:
return expression_value.bitwise_not();
default:
VERIFY_NOT_REACHED();
}
}
ResultOr<Value> ColumnNameExpression::evaluate(ExecutionContext& context) const
{
if (!context.current_row)
return Result { SQLCommand::Unknown, SQLErrorCode::SyntaxError, column_name() };
auto& descriptor = *context.current_row->descriptor();
VERIFY(context.current_row->size() == descriptor.size());
Optional<size_t> index_in_row;
for (auto ix = 0u; ix < context.current_row->size(); ix++) {
auto& column_descriptor = descriptor[ix];
if (!table_name().is_empty() && column_descriptor.table != table_name())
continue;
if (column_descriptor.name == column_name()) {
if (index_in_row.has_value())
return Result { SQLCommand::Unknown, SQLErrorCode::AmbiguousColumnName, column_name() };
index_in_row = ix;
}
}
if (index_in_row.has_value())
return (*context.current_row)[index_in_row.value()];
return Result { SQLCommand::Unknown, SQLErrorCode::ColumnDoesNotExist, column_name() };
}
ResultOr<Value> MatchExpression::evaluate(ExecutionContext& context) const
{
switch (type()) {
case MatchOperator::Like: {
Value lhs_value = TRY(lhs()->evaluate(context));
Value rhs_value = TRY(rhs()->evaluate(context));
char escape_char = '\0';
if (escape()) {
auto escape_str = TRY(escape()->evaluate(context)).to_byte_string();
if (escape_str.length() != 1)
return Result { SQLCommand::Unknown, SQLErrorCode::SyntaxError, "ESCAPE should be a single character" };
escape_char = escape_str[0];
}
// Compile the pattern into a simple regex.
// https://sqlite.org/lang_expr.html#the_like_glob_regexp_and_match_operators
bool escaped = false;
AK::StringBuilder builder;
builder.append('^');
for (auto c : rhs_value.to_byte_string()) {
if (escape() && c == escape_char && !escaped) {
escaped = true;
} else if (s_posix_basic_metacharacters.contains(c)) {
escaped = false;
builder.append('\\');
builder.append(c);
} else if (c == '_' && !escaped) {
builder.append('.');
} else if (c == '%' && !escaped) {
builder.append(".*"sv);
} else {
escaped = false;
builder.append(c);
}
}
builder.append('$');
// FIXME: We should probably cache this regex.
auto regex = Regex<PosixBasic>(builder.to_byte_string());
auto result = regex.match(lhs_value.to_byte_string(), PosixFlags::Insensitive | PosixFlags::Unicode);
return Value(invert_expression() ? !result.success : result.success);
}
case MatchOperator::Regexp: {
Value lhs_value = TRY(lhs()->evaluate(context));
Value rhs_value = TRY(rhs()->evaluate(context));
auto regex = Regex<PosixExtended>(rhs_value.to_byte_string());
auto err = regex.parser_result.error;
if (err != regex::Error::NoError) {
StringBuilder builder;
builder.append("Regular expression: "sv);
builder.append(get_error_string(err));
return Result { SQLCommand::Unknown, SQLErrorCode::SyntaxError, builder.to_byte_string() };
}
auto result = regex.match(lhs_value.to_byte_string(), PosixFlags::Insensitive | PosixFlags::Unicode);
return Value(invert_expression() ? !result.success : result.success);
}
case MatchOperator::Glob:
return Result { SQLCommand::Unknown, SQLErrorCode::NotYetImplemented, "GLOB expression is not yet implemented"sv };
case MatchOperator::Match:
return Result { SQLCommand::Unknown, SQLErrorCode::NotYetImplemented, "MATCH expression is not yet implemented"sv };
default:
VERIFY_NOT_REACHED();
}
}
}

View file

@ -1,61 +0,0 @@
/*
* Copyright (c) 2021, Jan de Visser <jan@de-visser.net>
* Copyright (c) 2021, Mahmoud Mandour <ma.mandourr@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibSQL/AST/AST.h>
#include <LibSQL/Database.h>
#include <LibSQL/Meta.h>
#include <LibSQL/Row.h>
namespace SQL::AST {
ResultOr<ResultSet> Insert::execute(ExecutionContext& context) const
{
auto table_def = TRY(context.database->get_table(m_schema_name, m_table_name));
Row row(table_def);
for (auto& column : m_column_names) {
if (!row.has(column))
return Result { SQLCommand::Insert, SQLErrorCode::ColumnDoesNotExist, column };
}
ResultSet result { SQLCommand::Insert };
TRY(result.try_ensure_capacity(m_chained_expressions.size()));
for (auto& row_expr : m_chained_expressions) {
for (auto& column_def : table_def->columns()) {
if (!m_column_names.contains_slow(column_def->name()))
row[column_def->name()] = column_def->default_value();
}
auto row_value = TRY(row_expr->evaluate(context));
VERIFY(row_value.type() == SQLType::Tuple);
auto values = row_value.to_vector().release_value();
if (m_column_names.is_empty() && values.size() != row.size())
return Result { SQLCommand::Insert, SQLErrorCode::InvalidNumberOfValues, ByteString::empty() };
for (auto ix = 0u; ix < values.size(); ix++) {
auto& tuple_descriptor = *row.descriptor();
// In case of having column names, this must succeed since we checked for every column name for existence in the table.
auto element_index = m_column_names.is_empty() ? ix : tuple_descriptor.find_if([&](auto element) { return element.name == m_column_names[ix]; }).index();
auto element_type = tuple_descriptor[element_index].type;
if (!values[ix].is_type_compatible_with(element_type))
return Result { SQLCommand::Insert, SQLErrorCode::InvalidValueType, table_def->columns()[element_index]->name() };
row[element_index] = move(values[ix]);
}
TRY(context.database->insert(row));
result.insert_row(row, {});
}
return result;
}
}

View file

@ -1,410 +0,0 @@
/*
* Copyright (c) 2021, Tim Flynn <trflynn89@serenityos.org>
* Copyright (c) 2021, Jan de Visser <jan@de-visser.net>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "Lexer.h"
#include <AK/Debug.h>
#include <ctype.h>
namespace SQL::AST {
HashMap<ByteString, TokenType> Lexer::s_keywords;
HashMap<char, TokenType> Lexer::s_one_char_tokens;
HashMap<ByteString, TokenType> Lexer::s_two_char_tokens;
Lexer::Lexer(StringView source)
: m_source(source)
{
if (s_keywords.is_empty()) {
#define __ENUMERATE_SQL_TOKEN(value, type, category) \
if (TokenCategory::category == TokenCategory::Keyword) \
s_keywords.set(value, TokenType::type);
ENUMERATE_SQL_TOKENS
#undef __ENUMERATE_SQL_TOKEN
}
if (s_one_char_tokens.is_empty()) {
#define __ENUMERATE_SQL_TOKEN(value, type, category) \
if (TokenCategory::category != TokenCategory::Keyword && value##sv.length() == 1) \
s_one_char_tokens.set(value[0], TokenType::type);
ENUMERATE_SQL_TOKENS
#undef __ENUMERATE_SQL_TOKEN
}
if (s_two_char_tokens.is_empty()) {
#define __ENUMERATE_SQL_TOKEN(value, type, category) \
if (TokenCategory::category != TokenCategory::Keyword && value##sv.length() == 2) \
s_two_char_tokens.set(value, TokenType::type);
ENUMERATE_SQL_TOKENS
#undef __ENUMERATE_SQL_TOKEN
}
consume();
}
Token Lexer::next()
{
bool found_invalid_comment = consume_whitespace_and_comments();
size_t value_start_line_number = m_line_number;
size_t value_start_column_number = m_line_column;
auto token_type = TokenType::Invalid;
StringBuilder current_token;
if (is_eof()) {
token_type = found_invalid_comment ? TokenType::Invalid : TokenType::Eof;
} else if (is_numeric_literal_start()) {
token_type = TokenType::NumericLiteral;
if (!consume_numeric_literal(current_token))
token_type = TokenType::Invalid;
} else if (is_string_literal_start()) {
token_type = TokenType::StringLiteral;
if (!consume_string_literal(current_token))
token_type = TokenType::Invalid;
} else if (is_quoted_identifier_start()) {
token_type = TokenType::Identifier;
if (!consume_quoted_identifier(current_token))
token_type = TokenType::Invalid;
} else if (is_blob_literal_start()) {
token_type = TokenType::BlobLiteral;
if (!consume_blob_literal(current_token))
token_type = TokenType::Invalid;
} else if (is_identifier_start()) {
do {
current_token.append((char)toupper(m_current_char));
consume();
} while (is_identifier_middle());
if (auto it = s_keywords.find(current_token.string_view()); it != s_keywords.end()) {
token_type = it->value;
} else {
token_type = TokenType::Identifier;
}
} else {
bool found_two_char_token = false;
if (m_position < m_source.length()) {
if (auto it = s_two_char_tokens.find(m_source.substring_view(m_position - 1, 2)); it != s_two_char_tokens.end()) {
found_two_char_token = true;
token_type = it->value;
consume(&current_token);
consume(&current_token);
}
}
bool found_one_char_token = false;
if (!found_two_char_token) {
if (auto it = s_one_char_tokens.find(m_current_char); it != s_one_char_tokens.end()) {
found_one_char_token = true;
token_type = it->value;
consume(&current_token);
}
}
if (!found_two_char_token && !found_one_char_token) {
token_type = TokenType::Invalid;
consume(&current_token);
}
}
Token token(token_type, current_token.to_byte_string(),
{ value_start_line_number, value_start_column_number },
{ m_line_number, m_line_column });
if constexpr (SQL_DEBUG) {
dbgln("------------------------------");
dbgln("Token: {}", token.name());
dbgln("Value: {}", token.value());
dbgln("Line: {}, Column: {}", token.start_position().line, token.start_position().column);
dbgln("------------------------------");
}
return token;
}
void Lexer::consume(StringBuilder* current_token)
{
auto did_reach_eof = [this] {
if (m_position != m_source.length())
return false;
m_eof = true;
m_current_char = '\0';
++m_line_column;
++m_position;
return true;
};
if (current_token)
current_token->append(m_current_char);
if (m_position > m_source.length())
return;
if (did_reach_eof())
return;
if (is_line_break()) {
++m_line_number;
m_line_column = 1;
} else {
++m_line_column;
}
m_current_char = m_source[m_position++];
}
bool Lexer::consume_whitespace_and_comments()
{
bool found_invalid_comment = false;
while (true) {
if (isspace(m_current_char)) {
do {
consume();
} while (isspace(m_current_char));
} else if (is_line_comment_start()) {
consume();
do {
consume();
} while (!is_eof() && !is_line_break());
} else if (is_block_comment_start()) {
consume();
do {
consume();
} while (!is_eof() && !is_block_comment_end());
if (is_eof())
found_invalid_comment = true;
consume(); // consume *
if (is_eof())
found_invalid_comment = true;
consume(); // consume /
} else {
break;
}
}
return found_invalid_comment;
}
bool Lexer::consume_numeric_literal(StringBuilder& current_token)
{
// https://sqlite.org/syntax/numeric-literal.html
bool is_valid_numeric_literal = true;
if (m_current_char == '0') {
consume(&current_token);
if (m_current_char == '.') {
consume(&current_token);
while (isdigit(m_current_char))
consume(&current_token);
if (m_current_char == 'e' || m_current_char == 'E')
is_valid_numeric_literal = consume_exponent(current_token);
} else if (m_current_char == 'e' || m_current_char == 'E') {
is_valid_numeric_literal = consume_exponent(current_token);
} else if (m_current_char == 'x' || m_current_char == 'X') {
is_valid_numeric_literal = consume_hexadecimal_number(current_token);
} else if (isdigit(m_current_char)) {
do {
consume(&current_token);
} while (isdigit(m_current_char));
}
} else {
do {
consume(&current_token);
} while (isdigit(m_current_char));
if (m_current_char == '.') {
consume(&current_token);
while (isdigit(m_current_char))
consume(&current_token);
}
if (m_current_char == 'e' || m_current_char == 'E')
is_valid_numeric_literal = consume_exponent(current_token);
}
return is_valid_numeric_literal;
}
bool Lexer::consume_string_literal(StringBuilder& current_token)
{
// https://sqlite.org/lang_expr.html - See "3. Literal Values (Constants)"
bool is_valid_string_literal = true;
// Skip the opening single quote:
consume();
while (!is_eof() && !is_string_literal_end()) {
// If both the current character and the next one are single quotes,
// consume one single quote into the current token, and drop the
// other one on the floor:
if (match('\'', '\''))
consume();
consume(&current_token);
}
if (is_eof())
is_valid_string_literal = false;
// Drop the closing quote on the floor:
consume();
return is_valid_string_literal;
}
bool Lexer::consume_quoted_identifier(StringBuilder& current_token)
{
// I have not found a reference to the syntax for identifiers in the
// SQLite documentation, but PostgreSQL has this:
// https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS
bool is_valid_identifier = true;
// Skip the opening double quote:
consume();
while (!is_eof() && !is_quoted_identifier_end()) {
// If both the current character and the next one are double quotes,
// consume one single quote into the current token, and drop the
// other one on the floor:
if (match('"', '"'))
consume();
consume(&current_token);
}
if (is_eof())
is_valid_identifier = false;
// Drop the closing double quote on the floor:
consume();
return is_valid_identifier;
}
bool Lexer::consume_blob_literal(StringBuilder& current_token)
{
// https://sqlite.org/lang_expr.html - See "3. Literal Values (Constants)"
// Skip starting 'X'/'x' character:
consume();
if (!consume_string_literal(current_token))
return false;
for (auto ix = 0u; ix < current_token.length(); ix++) {
if (!isxdigit(current_token.string_view()[ix]))
return false;
}
return true;
}
bool Lexer::consume_exponent(StringBuilder& current_token)
{
consume(&current_token);
if (m_current_char == '-' || m_current_char == '+')
consume(&current_token);
if (!isdigit(m_current_char))
return false;
// FIXME This code results in the string "1e" being rejected as a
// malformed numeric literal. We do however accept "1a" which
// is inconsistent. We have to decide what we want to do:
// - Be like `SQLite` and reject both "1a" and "1e" because we
// require a space between the two tokens. This is pretty invasive;
// we would have to decide where all spaces are required and fix
// the lexer accordingly.
// - Be like `PostgreSQL` and accept both "1e" and "1a" as two
// separate tokens, and accept "1e3" as a single token. This would
// would require pushing back the "e" we lexed here, terminate the
// numeric literal, and re-process the "e" as the first char of
// a new token.
while (isdigit(m_current_char)) {
consume(&current_token);
}
return true;
}
bool Lexer::consume_hexadecimal_number(StringBuilder& current_token)
{
consume(&current_token);
if (!isxdigit(m_current_char))
return false;
while (isxdigit(m_current_char))
consume(&current_token);
return true;
}
bool Lexer::match(char a, char b) const
{
if (m_position >= m_source.length())
return false;
return m_current_char == a && m_source[m_position] == b;
}
bool Lexer::is_identifier_start() const
{
return isalpha(m_current_char) || m_current_char == '_';
}
bool Lexer::is_identifier_middle() const
{
return is_identifier_start() || isdigit(m_current_char);
}
bool Lexer::is_numeric_literal_start() const
{
return isdigit(m_current_char) || (m_current_char == '.' && m_position < m_source.length() && isdigit(m_source[m_position]));
}
bool Lexer::is_string_literal_start() const
{
return m_current_char == '\'';
}
bool Lexer::is_string_literal_end() const
{
return m_current_char == '\'' && !(m_position < m_source.length() && m_source[m_position] == '\'');
}
bool Lexer::is_quoted_identifier_start() const
{
return m_current_char == '"';
}
bool Lexer::is_quoted_identifier_end() const
{
return m_current_char == '"' && !(m_position < m_source.length() && m_source[m_position] == '"');
}
bool Lexer::is_blob_literal_start() const
{
return match('x', '\'') || match('X', '\'');
}
bool Lexer::is_line_comment_start() const
{
return match('-', '-');
}
bool Lexer::is_block_comment_start() const
{
return match('/', '*');
}
bool Lexer::is_block_comment_end() const
{
return match('*', '/');
}
bool Lexer::is_line_break() const
{
return m_current_char == '\n';
}
bool Lexer::is_eof() const
{
return m_eof;
}
}

View file

@ -1,61 +0,0 @@
/*
* Copyright (c) 2021, Tim Flynn <trflynn89@serenityos.org>
* Copyright (c) 2021, Jan de Visser <jan@de-visser.net>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include "Token.h"
#include <AK/ByteString.h>
#include <AK/HashMap.h>
#include <AK/StringView.h>
namespace SQL::AST {
class Lexer {
public:
explicit Lexer(StringView source);
Token next();
private:
void consume(StringBuilder* = nullptr);
bool consume_whitespace_and_comments();
bool consume_numeric_literal(StringBuilder&);
bool consume_string_literal(StringBuilder&);
bool consume_quoted_identifier(StringBuilder&);
bool consume_blob_literal(StringBuilder&);
bool consume_exponent(StringBuilder&);
bool consume_hexadecimal_number(StringBuilder&);
bool match(char a, char b) const;
bool is_identifier_start() const;
bool is_identifier_middle() const;
bool is_numeric_literal_start() const;
bool is_string_literal_start() const;
bool is_string_literal_end() const;
bool is_quoted_identifier_start() const;
bool is_quoted_identifier_end() const;
bool is_blob_literal_start() const;
bool is_line_comment_start() const;
bool is_block_comment_start() const;
bool is_block_comment_end() const;
bool is_line_break() const;
bool is_eof() const;
static HashMap<ByteString, TokenType> s_keywords;
static HashMap<char, TokenType> s_one_char_tokens;
static HashMap<ByteString, TokenType> s_two_char_tokens;
StringView m_source;
size_t m_line_number { 1 };
size_t m_line_column { 0 };
char m_current_char { 0 };
bool m_eof { false };
size_t m_position { 0 };
};
}

File diff suppressed because it is too large Load diff

View file

@ -1,135 +0,0 @@
/*
* Copyright (c) 2021, Tim Flynn <trflynn89@serenityos.org>
* Copyright (c) 2021, Mahmoud Mandour <ma.mandourr@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/ByteString.h>
#include <AK/StringView.h>
#include <LibSQL/AST/AST.h>
#include <LibSQL/AST/Lexer.h>
#include <LibSQL/AST/Token.h>
namespace SQL::AST {
namespace Limits {
// https://www.sqlite.org/limits.html
constexpr size_t maximum_expression_tree_depth = 1000;
constexpr size_t maximum_subquery_depth = 100;
constexpr size_t maximum_bound_parameters = 1000;
}
class Parser {
struct Error {
ByteString message;
SourcePosition position;
ByteString to_byte_string() const
{
return ByteString::formatted("{} (line: {}, column: {})", message, position.line, position.column);
}
};
public:
explicit Parser(Lexer lexer);
NonnullRefPtr<Statement> next_statement();
bool has_errors() const { return m_parser_state.m_errors.size(); }
Vector<Error> const& errors() const { return m_parser_state.m_errors; }
protected:
NonnullRefPtr<Expression> parse_expression(); // Protected for unit testing.
private:
struct ParserState {
explicit ParserState(Lexer);
Lexer m_lexer;
Token m_token;
Vector<Error> m_errors;
size_t m_current_expression_depth { 0 };
size_t m_current_subquery_depth { 0 };
size_t m_bound_parameters { 0 };
};
NonnullRefPtr<Statement> parse_statement();
NonnullRefPtr<Statement> parse_statement_with_expression_list(RefPtr<CommonTableExpressionList>);
NonnullRefPtr<CreateSchema> parse_create_schema_statement();
NonnullRefPtr<CreateTable> parse_create_table_statement();
NonnullRefPtr<AlterTable> parse_alter_table_statement();
NonnullRefPtr<DropTable> parse_drop_table_statement();
NonnullRefPtr<DescribeTable> parse_describe_table_statement();
NonnullRefPtr<Insert> parse_insert_statement(RefPtr<CommonTableExpressionList>);
NonnullRefPtr<Update> parse_update_statement(RefPtr<CommonTableExpressionList>);
NonnullRefPtr<Delete> parse_delete_statement(RefPtr<CommonTableExpressionList>);
NonnullRefPtr<Select> parse_select_statement(RefPtr<CommonTableExpressionList>);
RefPtr<CommonTableExpressionList> parse_common_table_expression_list();
NonnullRefPtr<Expression> parse_primary_expression();
NonnullRefPtr<Expression> parse_secondary_expression(NonnullRefPtr<Expression> primary);
bool match_secondary_expression() const;
RefPtr<Expression> parse_literal_value_expression();
RefPtr<Expression> parse_bind_parameter_expression();
RefPtr<Expression> parse_column_name_expression(Optional<ByteString> with_parsed_identifier = {}, bool with_parsed_period = false);
RefPtr<Expression> parse_unary_operator_expression();
RefPtr<Expression> parse_binary_operator_expression(NonnullRefPtr<Expression> lhs);
RefPtr<Expression> parse_chained_expression(bool surrounded_by_parentheses = true);
RefPtr<Expression> parse_cast_expression();
RefPtr<Expression> parse_case_expression();
RefPtr<Expression> parse_exists_expression(bool invert_expression);
RefPtr<Expression> parse_collate_expression(NonnullRefPtr<Expression> expression);
RefPtr<Expression> parse_is_expression(NonnullRefPtr<Expression> expression);
RefPtr<Expression> parse_match_expression(NonnullRefPtr<Expression> lhs, bool invert_expression);
RefPtr<Expression> parse_null_expression(NonnullRefPtr<Expression> expression, bool invert_expression);
RefPtr<Expression> parse_between_expression(NonnullRefPtr<Expression> expression, bool invert_expression);
RefPtr<Expression> parse_in_expression(NonnullRefPtr<Expression> expression, bool invert_expression);
NonnullRefPtr<ColumnDefinition> parse_column_definition();
NonnullRefPtr<TypeName> parse_type_name();
NonnullRefPtr<SignedNumber> parse_signed_number();
NonnullRefPtr<CommonTableExpression> parse_common_table_expression();
NonnullRefPtr<QualifiedTableName> parse_qualified_table_name();
NonnullRefPtr<ReturningClause> parse_returning_clause();
NonnullRefPtr<ResultColumn> parse_result_column();
NonnullRefPtr<TableOrSubquery> parse_table_or_subquery();
NonnullRefPtr<OrderingTerm> parse_ordering_term();
void parse_schema_and_table_name(ByteString& schema_name, ByteString& table_name);
ConflictResolution parse_conflict_resolution();
template<typename ParseCallback>
void parse_comma_separated_list(bool surrounded_by_parentheses, ParseCallback&& parse_callback)
{
if (surrounded_by_parentheses)
consume(TokenType::ParenOpen);
while (!has_errors() && !match(TokenType::Eof)) {
parse_callback();
if (!match(TokenType::Comma))
break;
consume(TokenType::Comma);
};
if (surrounded_by_parentheses)
consume(TokenType::ParenClose);
}
Token consume();
Token consume(TokenType type);
bool consume_if(TokenType type);
bool match(TokenType type) const;
void expected(StringView what);
void syntax_error(ByteString message);
SourcePosition position() const;
ParserState m_parser_state;
};
}

View file

@ -1,183 +0,0 @@
/*
* Copyright (c) 2021, Jan de Visser <jan@de-visser.net>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/NumericLimits.h>
#include <LibSQL/AST/AST.h>
#include <LibSQL/Database.h>
#include <LibSQL/Meta.h>
#include <LibSQL/Row.h>
namespace SQL::AST {
static ByteString result_column_name(ResultColumn const& column, size_t column_index)
{
auto fallback_column_name = [column_index]() {
return ByteString::formatted("Column{}", column_index);
};
if (auto const& alias = column.column_alias(); !alias.is_empty())
return alias;
if (column.select_from_expression()) {
if (is<ColumnNameExpression>(*column.expression())) {
auto const& column_name_expression = verify_cast<ColumnNameExpression>(*column.expression());
return column_name_expression.column_name();
}
// FIXME: Generate column names from other result column expressions.
return fallback_column_name();
}
VERIFY(column.select_from_table());
// FIXME: Generate column names from select-from-table result columns.
return fallback_column_name();
}
ResultOr<ResultSet> Select::execute(ExecutionContext& context) const
{
Vector<NonnullRefPtr<ResultColumn const>> columns;
Vector<ByteString> column_names;
auto const& result_column_list = this->result_column_list();
VERIFY(!result_column_list.is_empty());
for (auto& table_descriptor : table_or_subquery_list()) {
if (!table_descriptor->is_table())
return Result { SQLCommand::Select, SQLErrorCode::NotYetImplemented, "Sub-selects are not yet implemented"sv };
auto table_def = TRY(context.database->get_table(table_descriptor->schema_name(), table_descriptor->table_name()));
if (result_column_list.size() == 1 && result_column_list[0]->type() == ResultType::All) {
TRY(columns.try_ensure_capacity(columns.size() + table_def->columns().size()));
TRY(column_names.try_ensure_capacity(column_names.size() + table_def->columns().size()));
for (auto& col : table_def->columns()) {
columns.unchecked_append(
create_ast_node<ResultColumn>(
create_ast_node<ColumnNameExpression>(table_def->parent()->name(), table_def->name(), col->name()),
""));
column_names.unchecked_append(col->name());
}
}
}
if (result_column_list.size() != 1 || result_column_list[0]->type() != ResultType::All) {
TRY(columns.try_ensure_capacity(result_column_list.size()));
TRY(column_names.try_ensure_capacity(result_column_list.size()));
for (size_t i = 0; i < result_column_list.size(); ++i) {
auto const& col = result_column_list[i];
if (col->type() == ResultType::All) {
// FIXME can have '*' for example in conjunction with computed columns
return Result { SQLCommand::Select, SQLErrorCode::SyntaxError, "*"sv };
}
columns.unchecked_append(col);
column_names.unchecked_append(result_column_name(col, i));
}
}
ResultSet result { SQLCommand::Select, move(column_names) };
auto descriptor = adopt_ref(*new TupleDescriptor);
Tuple tuple(descriptor);
Vector<Tuple> rows;
descriptor->empend("__unity__"sv);
tuple.append(Value { true });
rows.append(tuple);
for (auto& table_descriptor : table_or_subquery_list()) {
if (!table_descriptor->is_table())
return Result { SQLCommand::Select, SQLErrorCode::NotYetImplemented, "Sub-selects are not yet implemented"sv };
auto table_def = TRY(context.database->get_table(table_descriptor->schema_name(), table_descriptor->table_name()));
if (table_def->num_columns() == 0)
continue;
auto old_descriptor_size = descriptor->size();
descriptor->extend(table_def->to_tuple_descriptor());
while (!rows.is_empty() && (rows.first().size() == old_descriptor_size)) {
auto cartesian_row = rows.take_first();
auto table_rows = TRY(context.database->select_all(*table_def));
for (auto& table_row : table_rows) {
auto new_row = cartesian_row;
new_row.extend(table_row);
rows.append(new_row);
}
}
}
bool has_ordering { false };
auto sort_descriptor = adopt_ref(*new TupleDescriptor);
for (auto& term : m_ordering_term_list) {
sort_descriptor->append(TupleElementDescriptor { .order = term->order() });
has_ordering = true;
}
Tuple sort_key(sort_descriptor);
for (auto& row : rows) {
context.current_row = &row;
if (where_clause()) {
auto where_result = TRY(where_clause()->evaluate(context)).to_bool();
if (!where_result.has_value() || !where_result.value())
continue;
}
tuple.clear();
for (auto& col : columns) {
auto value = TRY(col->expression()->evaluate(context));
tuple.append(value);
}
if (has_ordering) {
sort_key.clear();
for (auto& term : m_ordering_term_list) {
auto value = TRY(term->expression()->evaluate(context));
sort_key.append(value);
}
}
result.insert_row(tuple, sort_key);
}
if (m_limit_clause != nullptr) {
size_t limit_value = NumericLimits<size_t>::max();
size_t offset_value = 0;
auto limit = TRY(m_limit_clause->limit_expression()->evaluate(context));
if (!limit.is_null()) {
auto limit_value_maybe = limit.to_int<size_t>();
if (!limit_value_maybe.has_value())
return Result { SQLCommand::Select, SQLErrorCode::SyntaxError, "LIMIT clause must evaluate to an integer value"sv };
limit_value = limit_value_maybe.value();
}
if (m_limit_clause->offset_expression() != nullptr) {
auto offset = TRY(m_limit_clause->offset_expression()->evaluate(context));
if (!offset.is_null()) {
auto offset_value_maybe = offset.to_int<size_t>();
if (!offset_value_maybe.has_value())
return Result { SQLCommand::Select, SQLErrorCode::SyntaxError, "OFFSET clause must evaluate to an integer value"sv };
offset_value = offset_value_maybe.value();
}
}
result.limit(offset_value, limit_value);
}
return result;
}
}

View file

@ -1,25 +0,0 @@
/*
* Copyright (c) 2021, Jan de Visser <jan@de-visser.net>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibSQL/AST/AST.h>
#include <LibSQL/Database.h>
#include <LibSQL/Meta.h>
#include <LibSQL/Row.h>
namespace SQL::AST {
ResultOr<ResultSet> Statement::execute(AK::NonnullRefPtr<Database> database, ReadonlySpan<Value> placeholder_values) const
{
ExecutionContext context { move(database), this, placeholder_values, nullptr };
auto result = TRY(execute(context));
// FIXME: When transactional sessions are supported, don't auto-commit modifications.
TRY(context.database->commit());
return result;
}
}

View file

@ -1,97 +0,0 @@
/*
* Copyright (c) 2021, Dylan Katz <dykatz@uw.edu>
* Copyright (c) 2022, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Debug.h>
#include <LibGfx/Palette.h>
#include <LibSQL/AST/Lexer.h>
#include <LibSQL/AST/SyntaxHighlighter.h>
namespace SQL::AST {
static Gfx::TextAttributes style_for_token_type(Gfx::Palette const& palette, TokenType type)
{
switch (Token::category(type)) {
case TokenCategory::Keyword:
return { palette.syntax_keyword(), {}, true };
case TokenCategory::Identifier:
return { palette.syntax_identifier() };
case TokenCategory::Number:
return { palette.syntax_number() };
case TokenCategory::Blob:
case TokenCategory::String:
return { palette.syntax_string() };
case TokenCategory::Operator:
return { palette.syntax_operator() };
case TokenCategory::Punctuation:
return { palette.syntax_punctuation() };
case TokenCategory::Invalid:
default:
return { palette.base_text() };
}
}
bool SyntaxHighlighter::is_identifier(u64 token) const
{
auto sql_token = static_cast<TokenType>(static_cast<size_t>(token));
return sql_token == TokenType::Identifier;
}
void SyntaxHighlighter::rehighlight(Palette const& palette)
{
auto text = m_client->get_text();
Lexer lexer(text);
Vector<Syntax::TextDocumentSpan> spans;
auto append_token = [&](Token const& token) {
if (token.value().is_empty())
return;
Syntax::TextDocumentSpan span;
span.range.set_start({ token.start_position().line - 1, token.start_position().column - 1 });
span.range.set_end({ token.end_position().line - 1, token.end_position().column - 1 });
span.attributes = style_for_token_type(palette, token.type());
span.data = static_cast<u64>(token.type());
spans.append(span);
dbgln_if(SYNTAX_HIGHLIGHTING_DEBUG, "{} @ '{}' {}:{} - {}:{}",
token.name(),
token.value(),
span.range.start().line(), span.range.start().column(),
span.range.end().line(), span.range.end().column());
};
for (;;) {
auto token = lexer.next();
append_token(token);
if (token.type() == TokenType::Eof)
break;
}
m_client->do_set_spans(move(spans));
m_has_brace_buddies = false;
highlight_matching_token_pair();
m_client->do_update();
}
Vector<SyntaxHighlighter::MatchingTokenPair> SyntaxHighlighter::matching_token_pairs_impl() const
{
static Vector<SyntaxHighlighter::MatchingTokenPair> pairs;
if (pairs.is_empty()) {
pairs.append({ static_cast<u64>(TokenType::ParenOpen), static_cast<u64>(TokenType::ParenClose) });
}
return pairs;
}
bool SyntaxHighlighter::token_types_equal(u64 token1, u64 token2) const
{
return static_cast<TokenType>(token1) == static_cast<TokenType>(token2);
}
}

View file

@ -1,32 +0,0 @@
/*
* Copyright (c) 2021, Dylan Katz <dykatz@uw.edu>
* Copyright (c) 2022, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibSyntax/Highlighter.h>
namespace SQL::AST {
class SyntaxHighlighter final : public Syntax::Highlighter {
public:
SyntaxHighlighter() = default;
virtual ~SyntaxHighlighter() override = default;
virtual bool is_identifier(u64) const override;
virtual Syntax::Language language() const override { return Syntax::Language::SQL; }
virtual Optional<StringView> comment_prefix() const override { return "--"sv; }
virtual Optional<StringView> comment_suffix() const override { return {}; }
virtual void rehighlight(Palette const&) override;
protected:
virtual Vector<MatchingTokenPair> matching_token_pairs_impl() const override;
virtual bool token_types_equal(u64, u64) const override;
};
}

View file

@ -1,53 +0,0 @@
/*
* Copyright (c) 2021, Tim Flynn <trflynn89@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "Token.h"
#include <AK/Assertions.h>
#include <AK/ByteString.h>
#include <stdlib.h>
namespace SQL::AST {
StringView Token::name(TokenType type)
{
switch (type) {
#define __ENUMERATE_SQL_TOKEN(value, type, category) \
case TokenType::type: \
return #type##sv;
ENUMERATE_SQL_TOKENS
#undef __ENUMERATE_SQL_TOKEN
default:
VERIFY_NOT_REACHED();
}
}
TokenCategory Token::category(TokenType type)
{
switch (type) {
#define __ENUMERATE_SQL_TOKEN(value, type, category) \
case TokenType::type: \
return TokenCategory::category;
ENUMERATE_SQL_TOKENS
#undef __ENUMERATE_SQL_TOKEN
default:
VERIFY_NOT_REACHED();
}
}
double Token::double_value() const
{
VERIFY(type() == TokenType::NumericLiteral);
ByteString value(m_value);
if (value[0] == '0' && value.length() >= 2) {
if (value[1] == 'x' || value[1] == 'X')
return static_cast<double>(strtoul(value.characters() + 2, nullptr, 16));
}
return strtod(value.characters(), nullptr);
}
}

View file

@ -1,255 +0,0 @@
/*
* Copyright (c) 2021, Tim Flynn <trflynn89@serenityos.org>
* Copyright (c) 2021, Jan de Visser <jan@de-visser.net>
* Copyright (c) 2021, Mahmoud Mandour <ma.mandourr@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/ByteString.h>
#include <AK/HashMap.h>
#include <AK/StringView.h>
namespace SQL::AST {
// https://sqlite.org/lang_keywords.html
#define ENUMERATE_SQL_TOKENS \
__ENUMERATE_SQL_TOKEN("ABORT", Abort, Keyword) \
__ENUMERATE_SQL_TOKEN("ACTION", Action, Keyword) \
__ENUMERATE_SQL_TOKEN("ADD", Add, Keyword) \
__ENUMERATE_SQL_TOKEN("AFTER", After, Keyword) \
__ENUMERATE_SQL_TOKEN("ALL", All, Keyword) \
__ENUMERATE_SQL_TOKEN("ALTER", Alter, Keyword) \
__ENUMERATE_SQL_TOKEN("ALWAYS", Always, Keyword) \
__ENUMERATE_SQL_TOKEN("ANALYZE", Analyze, Keyword) \
__ENUMERATE_SQL_TOKEN("AND", And, Keyword) \
__ENUMERATE_SQL_TOKEN("AS", As, Keyword) \
__ENUMERATE_SQL_TOKEN("ASC", Asc, Keyword) \
__ENUMERATE_SQL_TOKEN("ATTACH", Attach, Keyword) \
__ENUMERATE_SQL_TOKEN("AUTOINCREMENT", Autoincrement, Keyword) \
__ENUMERATE_SQL_TOKEN("BEFORE", Before, Keyword) \
__ENUMERATE_SQL_TOKEN("BEGIN", Begin, Keyword) \
__ENUMERATE_SQL_TOKEN("BETWEEN", Between, Keyword) \
__ENUMERATE_SQL_TOKEN("BY", By, Keyword) \
__ENUMERATE_SQL_TOKEN("CASCADE", Cascade, Keyword) \
__ENUMERATE_SQL_TOKEN("CASE", Case, Keyword) \
__ENUMERATE_SQL_TOKEN("CAST", Cast, Keyword) \
__ENUMERATE_SQL_TOKEN("CHECK", Check, Keyword) \
__ENUMERATE_SQL_TOKEN("COLLATE", Collate, Keyword) \
__ENUMERATE_SQL_TOKEN("COLUMN", Column, Keyword) \
__ENUMERATE_SQL_TOKEN("COMMIT", Commit, Keyword) \
__ENUMERATE_SQL_TOKEN("CONFLICT", Conflict, Keyword) \
__ENUMERATE_SQL_TOKEN("CONSTRAINT", Constraint, Keyword) \
__ENUMERATE_SQL_TOKEN("CREATE", Create, Keyword) \
__ENUMERATE_SQL_TOKEN("CROSS", Cross, Keyword) \
__ENUMERATE_SQL_TOKEN("CURRENT", Current, Keyword) \
__ENUMERATE_SQL_TOKEN("CURRENT_DATE", CurrentDate, Keyword) \
__ENUMERATE_SQL_TOKEN("CURRENT_TIME", CurrentTime, Keyword) \
__ENUMERATE_SQL_TOKEN("CURRENT_TIMESTAMP", CurrentTimestamp, Keyword) \
__ENUMERATE_SQL_TOKEN("DATABASE", Database, Keyword) \
__ENUMERATE_SQL_TOKEN("DEFAULT", Default, Keyword) \
__ENUMERATE_SQL_TOKEN("DEFERRABLE", Deferrable, Keyword) \
__ENUMERATE_SQL_TOKEN("DEFERRED", Deferred, Keyword) \
__ENUMERATE_SQL_TOKEN("DELETE", Delete, Keyword) \
__ENUMERATE_SQL_TOKEN("DESC", Desc, Keyword) \
__ENUMERATE_SQL_TOKEN("DESCRIBE", Describe, Keyword) \
__ENUMERATE_SQL_TOKEN("DETACH", Detach, Keyword) \
__ENUMERATE_SQL_TOKEN("DISTINCT", Distinct, Keyword) \
__ENUMERATE_SQL_TOKEN("DO", Do, Keyword) \
__ENUMERATE_SQL_TOKEN("DROP", Drop, Keyword) \
__ENUMERATE_SQL_TOKEN("EACH", Each, Keyword) \
__ENUMERATE_SQL_TOKEN("ELSE", Else, Keyword) \
__ENUMERATE_SQL_TOKEN("END", End, Keyword) \
__ENUMERATE_SQL_TOKEN("ESCAPE", Escape, Keyword) \
__ENUMERATE_SQL_TOKEN("EXCEPT", Except, Keyword) \
__ENUMERATE_SQL_TOKEN("EXCLUDE", Exclude, Keyword) \
__ENUMERATE_SQL_TOKEN("EXCLUSIVE", Exclusive, Keyword) \
__ENUMERATE_SQL_TOKEN("EXISTS", Exists, Keyword) \
__ENUMERATE_SQL_TOKEN("EXPLAIN", Explain, Keyword) \
__ENUMERATE_SQL_TOKEN("FAIL", Fail, Keyword) \
__ENUMERATE_SQL_TOKEN("FALSE", False, Keyword) \
__ENUMERATE_SQL_TOKEN("FILTER", Filter, Keyword) \
__ENUMERATE_SQL_TOKEN("FIRST", First, Keyword) \
__ENUMERATE_SQL_TOKEN("FOLLOWING", Following, Keyword) \
__ENUMERATE_SQL_TOKEN("FOR", For, Keyword) \
__ENUMERATE_SQL_TOKEN("FOREIGN", Foreign, Keyword) \
__ENUMERATE_SQL_TOKEN("FROM", From, Keyword) \
__ENUMERATE_SQL_TOKEN("FULL", Full, Keyword) \
__ENUMERATE_SQL_TOKEN("GENERATED", Generated, Keyword) \
__ENUMERATE_SQL_TOKEN("GLOB", Glob, Keyword) \
__ENUMERATE_SQL_TOKEN("GROUP", Group, Keyword) \
__ENUMERATE_SQL_TOKEN("GROUPS", Groups, Keyword) \
__ENUMERATE_SQL_TOKEN("HAVING", Having, Keyword) \
__ENUMERATE_SQL_TOKEN("IF", If, Keyword) \
__ENUMERATE_SQL_TOKEN("IGNORE", Ignore, Keyword) \
__ENUMERATE_SQL_TOKEN("IMMEDIATE", Immediate, Keyword) \
__ENUMERATE_SQL_TOKEN("IN", In, Keyword) \
__ENUMERATE_SQL_TOKEN("INDEX", Index, Keyword) \
__ENUMERATE_SQL_TOKEN("INDEXED", Indexed, Keyword) \
__ENUMERATE_SQL_TOKEN("INITIALLY", Initially, Keyword) \
__ENUMERATE_SQL_TOKEN("INNER", Inner, Keyword) \
__ENUMERATE_SQL_TOKEN("INSERT", Insert, Keyword) \
__ENUMERATE_SQL_TOKEN("INSTEAD", Instead, Keyword) \
__ENUMERATE_SQL_TOKEN("INTERSECT", Intersect, Keyword) \
__ENUMERATE_SQL_TOKEN("INTO", Into, Keyword) \
__ENUMERATE_SQL_TOKEN("IS", Is, Keyword) \
__ENUMERATE_SQL_TOKEN("ISNULL", Isnull, Keyword) \
__ENUMERATE_SQL_TOKEN("JOIN", Join, Keyword) \
__ENUMERATE_SQL_TOKEN("KEY", Key, Keyword) \
__ENUMERATE_SQL_TOKEN("LAST", Last, Keyword) \
__ENUMERATE_SQL_TOKEN("LEFT", Left, Keyword) \
__ENUMERATE_SQL_TOKEN("LIKE", Like, Keyword) \
__ENUMERATE_SQL_TOKEN("LIMIT", Limit, Keyword) \
__ENUMERATE_SQL_TOKEN("MATCH", Match, Keyword) \
__ENUMERATE_SQL_TOKEN("MATERIALIZED", Materialized, Keyword) \
__ENUMERATE_SQL_TOKEN("NATURAL", Natural, Keyword) \
__ENUMERATE_SQL_TOKEN("NO", No, Keyword) \
__ENUMERATE_SQL_TOKEN("NOT", Not, Keyword) \
__ENUMERATE_SQL_TOKEN("NOTHING", Nothing, Keyword) \
__ENUMERATE_SQL_TOKEN("NOTNULL", Notnull, Keyword) \
__ENUMERATE_SQL_TOKEN("NULL", Null, Keyword) \
__ENUMERATE_SQL_TOKEN("NULLS", Nulls, Keyword) \
__ENUMERATE_SQL_TOKEN("OF", Of, Keyword) \
__ENUMERATE_SQL_TOKEN("OFFSET", Offset, Keyword) \
__ENUMERATE_SQL_TOKEN("ON", On, Keyword) \
__ENUMERATE_SQL_TOKEN("OR", Or, Keyword) \
__ENUMERATE_SQL_TOKEN("ORDER", Order, Keyword) \
__ENUMERATE_SQL_TOKEN("OTHERS", Others, Keyword) \
__ENUMERATE_SQL_TOKEN("OUTER", Outer, Keyword) \
__ENUMERATE_SQL_TOKEN("OVER", Over, Keyword) \
__ENUMERATE_SQL_TOKEN("PARTITION", Partition, Keyword) \
__ENUMERATE_SQL_TOKEN("PLAN", Plan, Keyword) \
__ENUMERATE_SQL_TOKEN("PRAGMA", Pragma, Keyword) \
__ENUMERATE_SQL_TOKEN("PRECEDING", Preceding, Keyword) \
__ENUMERATE_SQL_TOKEN("PRIMARY", Primary, Keyword) \
__ENUMERATE_SQL_TOKEN("QUERY", Query, Keyword) \
__ENUMERATE_SQL_TOKEN("RAISE", Raise, Keyword) \
__ENUMERATE_SQL_TOKEN("RANGE", Range, Keyword) \
__ENUMERATE_SQL_TOKEN("RECURSIVE", Recursive, Keyword) \
__ENUMERATE_SQL_TOKEN("REFERENCES", References, Keyword) \
__ENUMERATE_SQL_TOKEN("REGEXP", Regexp, Keyword) \
__ENUMERATE_SQL_TOKEN("REINDEX", Reindex, Keyword) \
__ENUMERATE_SQL_TOKEN("RELEASE", Release, Keyword) \
__ENUMERATE_SQL_TOKEN("RENAME", Rename, Keyword) \
__ENUMERATE_SQL_TOKEN("REPLACE", Replace, Keyword) \
__ENUMERATE_SQL_TOKEN("RESTRICT", Restrict, Keyword) \
__ENUMERATE_SQL_TOKEN("RETURNING", Returning, Keyword) \
__ENUMERATE_SQL_TOKEN("RIGHT", Right, Keyword) \
__ENUMERATE_SQL_TOKEN("ROLLBACK", Rollback, Keyword) \
__ENUMERATE_SQL_TOKEN("ROW", Row, Keyword) \
__ENUMERATE_SQL_TOKEN("ROWS", Rows, Keyword) \
__ENUMERATE_SQL_TOKEN("SAVEPOINT", Savepoint, Keyword) \
__ENUMERATE_SQL_TOKEN("SCHEMA", Schema, Keyword) \
__ENUMERATE_SQL_TOKEN("SELECT", Select, Keyword) \
__ENUMERATE_SQL_TOKEN("SET", Set, Keyword) \
__ENUMERATE_SQL_TOKEN("TABLE", Table, Keyword) \
__ENUMERATE_SQL_TOKEN("TEMP", Temp, Keyword) \
__ENUMERATE_SQL_TOKEN("TEMPORARY", Temporary, Keyword) \
__ENUMERATE_SQL_TOKEN("THEN", Then, Keyword) \
__ENUMERATE_SQL_TOKEN("TIES", Ties, Keyword) \
__ENUMERATE_SQL_TOKEN("TO", To, Keyword) \
__ENUMERATE_SQL_TOKEN("TRANSACTION", Transaction, Keyword) \
__ENUMERATE_SQL_TOKEN("TRIGGER", Trigger, Keyword) \
__ENUMERATE_SQL_TOKEN("TRUE", True, Keyword) \
__ENUMERATE_SQL_TOKEN("UNBOUNDED", Unbounded, Keyword) \
__ENUMERATE_SQL_TOKEN("UNION", Union, Keyword) \
__ENUMERATE_SQL_TOKEN("UNIQUE", Unique, Keyword) \
__ENUMERATE_SQL_TOKEN("UPDATE", Update, Keyword) \
__ENUMERATE_SQL_TOKEN("USING", Using, Keyword) \
__ENUMERATE_SQL_TOKEN("VACUUM", Vacuum, Keyword) \
__ENUMERATE_SQL_TOKEN("VALUES", Values, Keyword) \
__ENUMERATE_SQL_TOKEN("VIEW", View, Keyword) \
__ENUMERATE_SQL_TOKEN("VIRTUAL", Virtual, Keyword) \
__ENUMERATE_SQL_TOKEN("WHEN", When, Keyword) \
__ENUMERATE_SQL_TOKEN("WHERE", Where, Keyword) \
__ENUMERATE_SQL_TOKEN("WINDOW", Window, Keyword) \
__ENUMERATE_SQL_TOKEN("WITH", With, Keyword) \
__ENUMERATE_SQL_TOKEN("WITHOUT", Without, Keyword) \
__ENUMERATE_SQL_TOKEN("_identifier_", Identifier, Identifier) \
__ENUMERATE_SQL_TOKEN("_numeric_", NumericLiteral, Number) \
__ENUMERATE_SQL_TOKEN("_string_", StringLiteral, String) \
__ENUMERATE_SQL_TOKEN("_blob_", BlobLiteral, Blob) \
__ENUMERATE_SQL_TOKEN("_eof_", Eof, Invalid) \
__ENUMERATE_SQL_TOKEN("_invalid_", Invalid, Invalid) \
__ENUMERATE_SQL_TOKEN("?", Placeholder, Operator) \
__ENUMERATE_SQL_TOKEN("&", Ampersand, Operator) \
__ENUMERATE_SQL_TOKEN("*", Asterisk, Operator) \
__ENUMERATE_SQL_TOKEN(",", Comma, Punctuation) \
__ENUMERATE_SQL_TOKEN("/", Divide, Operator) \
__ENUMERATE_SQL_TOKEN("||", DoublePipe, Operator) \
__ENUMERATE_SQL_TOKEN("=", Equals, Operator) \
__ENUMERATE_SQL_TOKEN("==", EqualsEquals, Operator) \
__ENUMERATE_SQL_TOKEN(">", GreaterThan, Operator) \
__ENUMERATE_SQL_TOKEN(">=", GreaterThanEquals, Operator) \
__ENUMERATE_SQL_TOKEN("<", LessThan, Operator) \
__ENUMERATE_SQL_TOKEN("<=", LessThanEquals, Operator) \
__ENUMERATE_SQL_TOKEN("-", Minus, Operator) \
__ENUMERATE_SQL_TOKEN("%", Modulus, Operator) \
__ENUMERATE_SQL_TOKEN("!=", NotEquals1, Operator) \
__ENUMERATE_SQL_TOKEN("<>", NotEquals2, Operator) \
__ENUMERATE_SQL_TOKEN(")", ParenClose, Punctuation) \
__ENUMERATE_SQL_TOKEN("(", ParenOpen, Punctuation) \
__ENUMERATE_SQL_TOKEN(".", Period, Operator) \
__ENUMERATE_SQL_TOKEN("|", Pipe, Operator) \
__ENUMERATE_SQL_TOKEN("+", Plus, Operator) \
__ENUMERATE_SQL_TOKEN(";", SemiColon, Punctuation) \
__ENUMERATE_SQL_TOKEN("<<", ShiftLeft, Operator) \
__ENUMERATE_SQL_TOKEN(">>", ShiftRight, Operator) \
__ENUMERATE_SQL_TOKEN("~", Tilde, Operator)
enum class TokenType {
#define __ENUMERATE_SQL_TOKEN(value, type, category) type,
ENUMERATE_SQL_TOKENS
#undef __ENUMERATE_SQL_TOKEN
_COUNT_OF_TOKENS,
};
enum class TokenCategory {
Invalid,
Keyword,
Identifier,
Number,
String,
Blob,
Operator,
Punctuation,
};
struct SourcePosition {
size_t line { 0 };
size_t column { 0 };
};
class Token {
public:
Token(TokenType type, ByteString value, SourcePosition start_position, SourcePosition end_position)
: m_type(type)
, m_value(move(value))
, m_start_position(start_position)
, m_end_position(end_position)
{
}
static StringView name(TokenType);
static TokenCategory category(TokenType);
StringView name() const { return name(m_type); }
TokenType type() const { return m_type; }
TokenCategory category() const { return category(m_type); }
ByteString const& value() const { return m_value; }
double double_value() const;
SourcePosition const& start_position() const { return m_start_position; }
SourcePosition const& end_position() const { return m_end_position; }
private:
TokenType m_type;
ByteString m_value;
SourcePosition m_start_position;
SourcePosition m_end_position;
};
}

View file

@ -1,63 +0,0 @@
/*
* Copyright (c) 2022, Tim Flynn <trflynn89@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibSQL/AST/AST.h>
#include <LibSQL/Database.h>
#include <LibSQL/Meta.h>
#include <LibSQL/Row.h>
namespace SQL::AST {
ResultOr<ResultSet> Update::execute(ExecutionContext& context) const
{
auto const& schema_name = m_qualified_table_name->schema_name();
auto const& table_name = m_qualified_table_name->table_name();
auto table_def = TRY(context.database->get_table(schema_name, table_name));
Vector<Row> matched_rows;
for (auto& table_row : TRY(context.database->select_all(*table_def))) {
context.current_row = &table_row;
if (auto const& where_clause = this->where_clause()) {
auto where_result = TRY(where_clause->evaluate(context)).to_bool();
if (!where_result.has_value() || !where_result.value())
continue;
}
TRY(matched_rows.try_append(move(table_row)));
}
ResultSet result { SQLCommand::Update };
for (auto& update_column : m_update_columns) {
auto row_value = TRY(update_column.expression->evaluate(context));
for (auto& table_row : matched_rows) {
auto& row_descriptor = *table_row.descriptor();
for (auto const& column_name : update_column.column_names) {
if (!table_row.has(column_name))
return Result { SQLCommand::Update, SQLErrorCode::ColumnDoesNotExist, column_name };
auto column_index = row_descriptor.find_if([&](auto element) { return element.name == column_name; }).index();
auto column_type = row_descriptor[column_index].type;
if (!row_value.is_type_compatible_with(column_type))
return Result { SQLCommand::Update, SQLErrorCode::InvalidValueType, column_name };
table_row[column_index] = row_value;
}
TRY(context.database->update(table_row));
result.insert_row(table_row, {});
}
}
return result;
}
}

View file

@ -1,113 +0,0 @@
/*
* Copyright (c) 2021, Jan de Visser <jan@de-visser.net>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibSQL/BTree.h>
#include <LibSQL/Meta.h>
namespace SQL {
ErrorOr<NonnullRefPtr<BTree>> BTree::create(Serializer& serializer, NonnullRefPtr<TupleDescriptor> const& descriptor, bool unique, Block::Index block_index)
{
return adopt_nonnull_ref_or_enomem(new (nothrow) BTree(serializer, descriptor, unique, block_index));
}
ErrorOr<NonnullRefPtr<BTree>> BTree::create(Serializer& serializer, NonnullRefPtr<TupleDescriptor> const& descriptor, Block::Index block_index)
{
return create(serializer, descriptor, true, block_index);
}
BTree::BTree(Serializer& serializer, NonnullRefPtr<TupleDescriptor> const& descriptor, bool unique, Block::Index block_index)
: Index(serializer, descriptor, unique, block_index)
, m_root(nullptr)
{
}
BTreeIterator BTree::begin()
{
if (!m_root)
initialize_root();
VERIFY(m_root);
return BTreeIterator(m_root, -1);
}
BTreeIterator BTree::end()
{
return BTreeIterator(nullptr, -1);
}
void BTree::initialize_root()
{
if (block_index()) {
if (serializer().has_block(block_index())) {
serializer().read_storage(block_index());
m_root = serializer().make_and_deserialize<TreeNode>(*this, block_index());
} else {
m_root = make<TreeNode>(*this, nullptr, block_index());
}
} else {
set_block_index(request_new_block_index());
m_root = make<TreeNode>(*this, nullptr, block_index());
if (on_new_root)
on_new_root();
}
m_root->dump_if(0, "initialize_root");
}
TreeNode* BTree::new_root()
{
set_block_index(request_new_block_index());
m_root = make<TreeNode>(*this, nullptr, m_root.leak_ptr(), block_index());
serializer().serialize_and_write(*m_root.ptr());
if (on_new_root)
on_new_root();
return m_root;
}
bool BTree::insert(Key const& key)
{
if (!m_root)
initialize_root();
return m_root->insert(key);
}
bool BTree::update_key_pointer(Key const& key)
{
if (!m_root)
initialize_root();
return m_root->update_key_pointer(key);
}
Optional<u32> BTree::get(Key& key)
{
if (!m_root)
initialize_root();
return m_root->get(key);
}
BTreeIterator BTree::find(Key const& key)
{
if (!m_root)
initialize_root();
for (auto node = m_root->node_for(key); node; node = node->up()) {
for (auto ix = 0u; ix < node->size(); ix++) {
auto match = (*node)[ix].match(key);
if (match == 0)
return BTreeIterator(node, (int)ix);
else if (match > 0)
return end();
}
}
return end();
}
void BTree::list_tree()
{
if (!m_root)
initialize_root();
m_root->list_node(0);
}
}

View file

@ -1,201 +0,0 @@
/*
* Copyright (c) 2021, Jan de Visser <jan@de-visser.net>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/ByteString.h>
#include <AK/Function.h>
#include <AK/NonnullRefPtr.h>
#include <AK/Optional.h>
#include <AK/RefPtr.h>
#include <AK/Vector.h>
#include <LibCore/EventReceiver.h>
#include <LibSQL/Forward.h>
#include <LibSQL/Heap.h>
#include <LibSQL/Index.h>
#include <LibSQL/Key.h>
namespace SQL {
/**
* The BTree class models a B-Tree index. It contains a collection of
* Key objects organized in TreeNode objects. Keys can be inserted,
* located, deleted, and the set can be traversed in sort order. All keys in
* a tree have the same underlying structure. A BTree's TreeNodes and
* the keys it includes are lazily loaded from the Heap when needed.
*
* The classes implementing the B-Tree functionality are BTree, TreeNode,
* BTreeIterator, and DownPointer (a smart pointer-like helper class).
*/
class DownPointer {
public:
explicit DownPointer(TreeNode*, Block::Index = 0);
DownPointer(TreeNode*, TreeNode*);
DownPointer(DownPointer&&);
DownPointer(TreeNode*, DownPointer&);
~DownPointer() = default;
[[nodiscard]] Block::Index block_index() const { return m_block_index; }
TreeNode* node();
private:
void deserialize(Serializer&);
TreeNode* m_owner;
Block::Index m_block_index { 0 };
OwnPtr<TreeNode> m_node { nullptr };
friend TreeNode;
};
class TreeNode : public IndexNode {
public:
TreeNode(BTree&, Block::Index = 0);
TreeNode(BTree&, TreeNode*, Block::Index = 0);
TreeNode(BTree&, TreeNode*, TreeNode*, Block::Index = 0);
~TreeNode() override = default;
[[nodiscard]] BTree& tree() const { return m_tree; }
[[nodiscard]] TreeNode* up() const { return m_up; }
[[nodiscard]] size_t size() const { return m_entries.size(); }
[[nodiscard]] size_t length() const;
[[nodiscard]] Vector<Key> entries() const { return m_entries; }
[[nodiscard]] Block::Index down_pointer(size_t) const;
[[nodiscard]] TreeNode* down_node(size_t);
[[nodiscard]] bool is_leaf() const { return m_is_leaf; }
Key const& operator[](size_t index) const { return m_entries[index]; }
bool insert(Key const&);
bool update_key_pointer(Key const&);
TreeNode* node_for(Key const&);
Optional<u32> get(Key&);
void deserialize(Serializer&);
void serialize(Serializer&) const;
private:
TreeNode(BTree&, TreeNode*, DownPointer&, u32 = 0);
void dump_if(int, ByteString&& = "");
bool insert_in_leaf(Key const&);
void just_insert(Key const&, TreeNode* = nullptr);
void split();
void list_node(int);
BTree& m_tree;
TreeNode* m_up;
Vector<Key> m_entries;
bool m_is_leaf { true };
Vector<DownPointer> m_down;
friend BTree;
friend BTreeIterator;
};
class BTree : public Index {
public:
static ErrorOr<NonnullRefPtr<BTree>> create(Serializer&, NonnullRefPtr<TupleDescriptor> const&, bool unique, Block::Index);
static ErrorOr<NonnullRefPtr<BTree>> create(Serializer&, NonnullRefPtr<TupleDescriptor> const&, Block::Index);
Block::Index root() const { return m_root ? m_root->block_index() : 0; }
bool insert(Key const&);
bool update_key_pointer(Key const&);
Optional<u32> get(Key&);
BTreeIterator find(Key const& key);
BTreeIterator begin();
static BTreeIterator end();
void list_tree();
Function<void(void)> on_new_root;
private:
BTree(Serializer&, NonnullRefPtr<TupleDescriptor> const&, bool unique, Block::Index);
void initialize_root();
TreeNode* new_root();
OwnPtr<TreeNode> m_root { nullptr };
friend BTreeIterator;
friend DownPointer;
friend TreeNode;
};
class BTreeIterator {
public:
[[nodiscard]] bool is_end() const { return m_where == Where::End; }
[[nodiscard]] size_t index() const { return m_index; }
bool update(Key const&);
bool operator==(BTreeIterator const& other) const { return cmp(other) == 0; }
bool operator!=(BTreeIterator const& other) const { return cmp(other) != 0; }
bool operator<(BTreeIterator const& other) const { return cmp(other) < 0; }
bool operator>(BTreeIterator const& other) const { return cmp(other) > 0; }
bool operator<=(BTreeIterator const& other) const { return cmp(other) <= 0; }
bool operator>=(BTreeIterator const& other) const { return cmp(other) >= 0; }
bool operator==(Key const& other) const { return cmp(other) == 0; }
bool operator!=(Key const& other) const { return cmp(other) != 0; }
bool operator<(Key const& other) const { return cmp(other) < 0; }
bool operator>(Key const& other) const { return cmp(other) > 0; }
bool operator<=(Key const& other) const { return cmp(other) <= 0; }
bool operator>=(Key const& other) const { return cmp(other) >= 0; }
BTreeIterator operator++()
{
*this = next();
return *this;
}
BTreeIterator operator++(int)
{
*this = next();
return *this;
}
BTreeIterator operator--()
{
*this = previous();
return *this;
}
BTreeIterator const operator--(int)
{
*this = previous();
return *this;
}
Key const& operator*() const
{
VERIFY(!is_end());
return (*m_current)[m_index];
}
Key const& operator->() const
{
VERIFY(!is_end());
return (*m_current)[m_index];
}
BTreeIterator& operator=(BTreeIterator const&);
BTreeIterator(BTreeIterator const&) = default;
private:
BTreeIterator(TreeNode*, int index);
static BTreeIterator end() { return BTreeIterator(nullptr, -1); }
[[nodiscard]] int cmp(BTreeIterator const&) const;
[[nodiscard]] int cmp(Key const&) const;
[[nodiscard]] BTreeIterator next() const;
[[nodiscard]] BTreeIterator previous() const;
[[nodiscard]] Key key() const;
enum class Where {
Valid,
End
};
Where m_where { Where::Valid };
TreeNode* m_current { nullptr };
int m_index { -1 };
friend BTree;
};
}

View file

@ -1,247 +0,0 @@
/*
* Copyright (c) 2021, Jan de Visser <jan@de-visser.net>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibSQL/BTree.h>
namespace SQL {
BTreeIterator::BTreeIterator(TreeNode* node, int index)
: m_current(node)
, m_index(index)
{
if (!node) {
m_where = Where::End;
} else {
if (index < 0) {
while (!node->is_leaf() && (node->size() != 0)) {
node = node->down_node(0);
}
if (node->size() == 0) {
m_where = Where::End;
m_current = nullptr;
m_index = -1;
} else {
m_where = Where::Valid;
m_current = node;
m_index = 0;
}
} else {
VERIFY(m_index < (int)m_current->size());
}
}
}
int BTreeIterator::cmp(BTreeIterator const& other) const
{
if (is_end())
return (other.is_end()) ? 0 : 1;
if (other.is_end())
return -1;
VERIFY(&other.m_current->tree() == &m_current->tree());
VERIFY((m_current->size() > 0) && (other.m_current->size() > 0));
if (&m_current != &other.m_current)
return (*m_current)[m_current->size() - 1].compare((*(other.m_current))[0]);
return (*m_current)[m_index].compare((*(other.m_current))[other.m_index]);
}
int BTreeIterator::cmp(Key const& other) const
{
if (is_end())
return 1;
if (other.is_null())
return -1;
return key().compare(other);
}
BTreeIterator BTreeIterator::next() const
{
if (is_end())
return end();
auto ix = m_index;
auto node = m_current;
if (ix < (int)(node->size() - 1)) {
if (node->is_leaf()) {
// We're in the middle of a leaf node. Next entry is
// is the next entry of the node:
return BTreeIterator(node, ix + 1);
} else {
/*
* We're in the middle of a non-leaf node. The iterator's
* next value is all the way down to the right, first entry.
*
* |
* +--+--+--+--+
* | |##| | |
* +--+--+--+--+
* / | | | \
* |
* +--+--+--+--+
* | | | | |
* +--+--+--+--+
* /
* +--+--+--+--+
* |++| | | |
* +--+--+--+--+
*/
ix++;
while (!node->is_leaf()) {
node = node->down_node(ix);
ix = 0;
}
}
VERIFY(node->is_leaf() && (ix < (int)node->size()));
return BTreeIterator(node, ix);
}
if (node->is_leaf()) {
// We currently at the last entry of a leaf node. We need to check
// one or more levels up until we end up in the "middle" of a node.
// If one level up we're still at the end of the node, we need
// to keep going up until we hit the root node. If we're at the
// end of the root node, we reached the end of the btree.
for (auto up = node->up(); up; up = node->up()) {
for (size_t i = 0; i < up->size(); i++) {
// One level up, try to find the entry with the current
// node's pointer as the left pointer:
if (up->down_pointer(i) == node->block_index())
// Found it. This is the iterator's next value:
return BTreeIterator(up, (int)i);
}
// We didn't find the m_current's pointer as a left node. So
// it must be the right node all the way at the end and we need
// to go one more level up:
node = up;
}
// We reached the root node and we're still at the end of the node.
// That means we're at the end of the btree.
return end();
}
// If we're at the end of a non-leaf node, we need to follow the
// right pointer down until we find a leaf:
TreeNode* down;
for (down = node->down_node(node->size()); !down->is_leaf(); down = down->down_node(0))
;
return BTreeIterator(down, 0);
}
// FIXME Reverse iterating doesn't quite work; we don't recognize the
// end (which is really the beginning) of the tree.
BTreeIterator BTreeIterator::previous() const
{
if (is_end())
return end();
auto node = m_current;
auto ix = m_index;
if (ix > 0) {
if (node->is_leaf()) {
// We're in the middle of a leaf node. Previous entry is
// is the previous entry of the node:
return BTreeIterator(node, ix - 1);
} else {
/*
* We're in the middle of a non-leaf node. The iterator's
* previous value is all the way down to the left, last entry.
*
* |
* +--+--+--+--+
* | | |##| |
* +--+--+--+--+
* / | | | \
* |
* +--+--+--+--+
* | | | | |
* +--+--+--+--+
* \
* +--+--+--+--+
* | | | |++|
* +--+--+--+--+
*/
while (!node->is_leaf()) {
node = node->down_node(ix);
ix = (int)node->size();
}
}
VERIFY(node->is_leaf() && (ix <= (int)node->size()));
return BTreeIterator(node, ix);
}
if (node->is_leaf()) {
// We currently at the first entry of a leaf node. We need to check one
// or more levels up until we end up in the "middle" of a node.
// If one level up we're still at the start of the node, we need
// to keep going up until we hit the root node. If we're at the
// start of the root node, we reached the start of the btree.
auto stash_current = node;
for (auto up = node->up(); up; up = node->up()) {
for (size_t i = up->size(); i > 0; i--) {
// One level up, try to find the entry with the current
// node's pointer as the right pointer:
if (up->down_pointer(i) == node->block_index()) {
// Found it. This is the iterator's next value:
node = up;
ix = (int)i - 1;
return BTreeIterator(node, ix);
}
}
// We didn't find the m_current's pointer as a right node. So
// it must be the left node all the way at the start and we need
// to go one more level up:
node = up;
}
// We reached the root node and we're still at the start of the node.
// That means we're at the start of the btree.
return BTreeIterator(stash_current, 0);
}
// If we're at the start of a non-leaf node, we need to follow the
// left pointer down until we find a leaf:
TreeNode* down = node->down_node(0);
while (!down->is_leaf())
down = down->down_node(down->size());
return BTreeIterator(down, down->size() - 1);
}
Key BTreeIterator::key() const
{
if (is_end())
return {};
return (*m_current)[m_index];
}
bool BTreeIterator::update(Key const& new_value)
{
if (is_end())
return false;
if ((cmp(new_value) == 0) && (key().block_index() == new_value.block_index()))
return true;
auto previous_iter = previous();
auto next_iter = next();
if (!m_current->tree().duplicates_allowed() && ((previous_iter == new_value) || (next_iter == new_value))) {
return false;
}
if ((previous_iter > new_value) || (next_iter < new_value))
return false;
// We are friend of BTree and TreeNode. Don't know how I feel about that.
m_current->m_entries[m_index] = new_value;
m_current->tree().serializer().serialize_and_write(*m_current);
return true;
}
BTreeIterator& BTreeIterator::operator=(BTreeIterator const& other)
{
if (&other != this) {
m_current = other.m_current;
m_index = other.m_index;
m_where = other.m_where;
}
return *this;
}
}

View file

@ -1,43 +0,0 @@
set(SOURCES
AST/CreateSchema.cpp
AST/CreateTable.cpp
AST/Delete.cpp
AST/Describe.cpp
AST/Expression.cpp
AST/Insert.cpp
AST/Lexer.cpp
AST/Parser.cpp
AST/Select.cpp
AST/Statement.cpp
AST/SyntaxHighlighter.cpp
AST/Token.cpp
AST/Update.cpp
BTree.cpp
BTreeIterator.cpp
Database.cpp
Heap.cpp
Index.cpp
Key.cpp
Meta.cpp
Result.cpp
ResultSet.cpp
Row.cpp
Serializer.cpp
SQLClient.cpp
TreeNode.cpp
Tuple.cpp
Value.cpp
)
if (NOT SERENITYOS)
compile_ipc(../../Services/SQLServer/SQLClient.ipc ../../Services/SQLServer/SQLClientEndpoint.h)
compile_ipc(../../Services/SQLServer/SQLServer.ipc ../../Services/SQLServer/SQLServerEndpoint.h)
endif()
set(GENERATED_SOURCES
../../Services/SQLServer/SQLClientEndpoint.h
../../Services/SQLServer/SQLServerEndpoint.h
)
serenity_lib(LibSQL sql)
target_link_libraries(LibSQL PRIVATE LibCore LibFileSystem LibIPC LibSyntax LibRegex)

View file

@ -1,264 +0,0 @@
/*
* Copyright (c) 2021, Jan de Visser <jan@de-visser.net>
* Copyright (c) 2021, Mahmoud Mandour <ma.mandourr@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/ByteString.h>
#include <LibSQL/BTree.h>
#include <LibSQL/Database.h>
#include <LibSQL/Heap.h>
#include <LibSQL/Meta.h>
#include <LibSQL/Row.h>
#include <LibSQL/Tuple.h>
namespace SQL {
ErrorOr<NonnullRefPtr<Database>> Database::create(ByteString name)
{
auto heap = TRY(Heap::create(move(name)));
return adopt_nonnull_ref_or_enomem(new (nothrow) Database(move(heap)));
}
Database::Database(NonnullRefPtr<Heap> heap)
: m_heap(move(heap))
, m_serializer(m_heap)
{
}
ResultOr<void> Database::open()
{
VERIFY(!m_open);
TRY(m_heap->open());
m_schemas = TRY(BTree::create(m_serializer, SchemaDef::index_def()->to_tuple_descriptor(), m_heap->schemas_root()));
m_schemas->on_new_root = [&]() {
m_heap->set_schemas_root(m_schemas->root());
};
m_tables = TRY(BTree::create(m_serializer, TableDef::index_def()->to_tuple_descriptor(), m_heap->tables_root()));
m_tables->on_new_root = [&]() {
m_heap->set_tables_root(m_tables->root());
};
m_table_columns = TRY(BTree::create(m_serializer, ColumnDef::index_def()->to_tuple_descriptor(), m_heap->table_columns_root()));
m_table_columns->on_new_root = [&]() {
m_heap->set_table_columns_root(m_table_columns->root());
};
m_open = true;
auto ensure_schema_exists = [&](auto schema_name) -> ResultOr<NonnullRefPtr<SchemaDef>> {
if (auto result = get_schema(schema_name); result.is_error()) {
if (result.error().error() != SQLErrorCode::SchemaDoesNotExist)
return result.release_error();
auto schema_def = TRY(SchemaDef::create(schema_name));
TRY(add_schema(*schema_def));
return schema_def;
} else {
return result.release_value();
}
};
(void)TRY(ensure_schema_exists("default"sv));
auto master_schema = TRY(ensure_schema_exists("master"sv));
if (auto result = get_table("master"sv, "internal_describe_table"sv); result.is_error()) {
if (result.error().error() != SQLErrorCode::TableDoesNotExist)
return result.release_error();
auto internal_describe_table = TRY(TableDef::create(master_schema, "internal_describe_table"));
internal_describe_table->append_column("Name", SQLType::Text);
internal_describe_table->append_column("Type", SQLType::Text);
TRY(add_table(*internal_describe_table));
}
return {};
}
Database::~Database() = default;
ErrorOr<void> Database::commit()
{
VERIFY(is_open());
TRY(m_heap->flush());
return {};
}
ResultOr<void> Database::add_schema(SchemaDef const& schema)
{
VERIFY(is_open());
if (!m_schemas->insert(schema.key()))
return Result { SQLCommand::Unknown, SQLErrorCode::SchemaExists, schema.name() };
return {};
}
Key Database::get_schema_key(ByteString const& schema_name)
{
auto key = SchemaDef::make_key();
key["schema_name"] = schema_name;
return key;
}
ResultOr<NonnullRefPtr<SchemaDef>> Database::get_schema(ByteString const& schema)
{
VERIFY(is_open());
auto schema_name = schema;
if (schema.is_empty())
schema_name = "default"sv;
Key key = get_schema_key(schema_name);
if (auto it = m_schema_cache.find(key.hash()); it != m_schema_cache.end())
return it->value;
auto schema_iterator = m_schemas->find(key);
if (schema_iterator.is_end() || (*schema_iterator != key))
return Result { SQLCommand::Unknown, SQLErrorCode::SchemaDoesNotExist, schema_name };
auto schema_def = TRY(SchemaDef::create(*schema_iterator));
m_schema_cache.set(key.hash(), schema_def);
return schema_def;
}
ResultOr<void> Database::add_table(TableDef& table)
{
VERIFY(is_open());
if (!m_tables->insert(table.key()))
return Result { SQLCommand::Unknown, SQLErrorCode::TableExists, table.name() };
for (auto& column : table.columns()) {
if (!m_table_columns->insert(column->key()))
VERIFY_NOT_REACHED();
}
return {};
}
Key Database::get_table_key(ByteString const& schema_name, ByteString const& table_name)
{
auto key = TableDef::make_key(get_schema_key(schema_name));
key["table_name"] = table_name;
return key;
}
ResultOr<NonnullRefPtr<TableDef>> Database::get_table(ByteString const& schema, ByteString const& name)
{
VERIFY(is_open());
auto schema_name = schema;
if (schema.is_empty())
schema_name = "default"sv;
Key key = get_table_key(schema_name, name);
if (auto it = m_table_cache.find(key.hash()); it != m_table_cache.end())
return it->value;
auto table_iterator = m_tables->find(key);
if (table_iterator.is_end() || (*table_iterator != key))
return Result { SQLCommand::Unknown, SQLErrorCode::TableDoesNotExist, ByteString::formatted("{}.{}", schema_name, name) };
auto schema_def = TRY(get_schema(schema));
auto table_def = TRY(TableDef::create(schema_def, name));
table_def->set_block_index((*table_iterator).block_index());
m_table_cache.set(key.hash(), table_def);
auto table_hash = table_def->hash();
auto column_key = ColumnDef::make_key(table_def);
for (auto it = m_table_columns->find(column_key); !it.is_end() && ((*it)["table_hash"].to_int<u32>() == table_hash); ++it)
table_def->append_column(*it);
return table_def;
}
ErrorOr<Vector<Row>> Database::select_all(TableDef& table)
{
VERIFY(m_table_cache.get(table.key().hash()).has_value());
Vector<Row> ret;
for (auto block_index = table.block_index(); block_index; block_index = ret.last().next_block_index())
ret.append(m_serializer.deserialize_block<Row>(block_index, table, block_index));
return ret;
}
ErrorOr<Vector<Row>> Database::match(TableDef& table, Key const& key)
{
VERIFY(m_table_cache.get(table.key().hash()).has_value());
Vector<Row> ret;
// TODO Match key against indexes defined on table. If found,
// use the index instead of scanning the table.
for (auto block_index = table.block_index(); block_index;) {
auto row = m_serializer.deserialize_block<Row>(block_index, table, block_index);
if (row.match(key))
ret.append(row);
block_index = ret.last().next_block_index();
}
return ret;
}
ErrorOr<void> Database::insert(Row& row)
{
VERIFY(m_table_cache.get(row.table().key().hash()).has_value());
// TODO: implement table constraints such as unique, foreign key, etc.
row.set_block_index(m_heap->request_new_block_index());
row.set_next_block_index(row.table().block_index());
TRY(update(row));
// TODO update indexes defined on table.
auto table_key = row.table().key();
table_key.set_block_index(row.block_index());
VERIFY(m_tables->update_key_pointer(table_key));
row.table().set_block_index(row.block_index());
return {};
}
ErrorOr<void> Database::remove(Row& row)
{
auto& table = row.table();
VERIFY(m_table_cache.get(table.key().hash()).has_value());
TRY(m_heap->free_storage(row.block_index()));
if (table.block_index() == row.block_index()) {
auto table_key = table.key();
table_key.set_block_index(row.next_block_index());
m_tables->update_key_pointer(table_key);
table.set_block_index(row.next_block_index());
return {};
}
for (auto block_index = table.block_index(); block_index;) {
auto current = m_serializer.deserialize_block<Row>(block_index, table, block_index);
if (current.next_block_index() == row.block_index()) {
current.set_next_block_index(row.next_block_index());
TRY(update(current));
break;
}
block_index = current.next_block_index();
}
return {};
}
ErrorOr<void> Database::update(Row& tuple)
{
VERIFY(m_table_cache.get(tuple.table().key().hash()).has_value());
// TODO: implement table constraints such as unique, foreign key, etc.
m_serializer.reset();
m_serializer.serialize_and_write<Tuple>(tuple);
// TODO update indexes defined on table.
return {};
}
}

View file

@ -1,64 +0,0 @@
/*
* Copyright (c) 2021, Jan de Visser <jan@de-visser.net>
* Copyright (c) 2021, Mahmoud Mandour <ma.mandourr@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/ByteString.h>
#include <AK/NonnullRefPtr.h>
#include <AK/RefPtr.h>
#include <LibSQL/Forward.h>
#include <LibSQL/Heap.h>
#include <LibSQL/Meta.h>
#include <LibSQL/Result.h>
#include <LibSQL/Serializer.h>
namespace SQL {
/**
* A Database object logically connects a Heap with the SQL data we want
* to store in it. It has BTree pointers for B-Trees holding the definitions
* of tables, columns, indexes, and other SQL objects.
*/
class Database : public RefCounted<Database> {
public:
static ErrorOr<NonnullRefPtr<Database>> create(ByteString);
~Database();
ResultOr<void> open();
bool is_open() const { return m_open; }
ErrorOr<void> commit();
ErrorOr<size_t> file_size_in_bytes() const { return m_heap->file_size_in_bytes(); }
ResultOr<void> add_schema(SchemaDef const&);
static Key get_schema_key(ByteString const&);
ResultOr<NonnullRefPtr<SchemaDef>> get_schema(ByteString const&);
ResultOr<void> add_table(TableDef& table);
static Key get_table_key(ByteString const&, ByteString const&);
ResultOr<NonnullRefPtr<TableDef>> get_table(ByteString const&, ByteString const&);
ErrorOr<Vector<Row>> select_all(TableDef&);
ErrorOr<Vector<Row>> match(TableDef&, Key const&);
ErrorOr<void> insert(Row&);
ErrorOr<void> remove(Row&);
ErrorOr<void> update(Row&);
private:
explicit Database(NonnullRefPtr<Heap>);
bool m_open { false };
NonnullRefPtr<Heap> m_heap;
Serializer m_serializer;
RefPtr<BTree> m_schemas;
RefPtr<BTree> m_tables;
RefPtr<BTree> m_table_columns;
HashMap<u32, NonnullRefPtr<SchemaDef>> m_schema_cache;
HashMap<u32, NonnullRefPtr<TableDef>> m_table_cache;
};
}

View file

@ -1,89 +0,0 @@
/*
* Copyright (c) 2021, Tim Flynn <trflynn89@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
namespace SQL {
class BTree;
class BTreeIterator;
class ColumnDef;
class Database;
class Heap;
class Index;
class IndexNode;
class IndexDef;
class Key;
class KeyPartDef;
class Relation;
class Result;
class ResultSet;
class Row;
class SchemaDef;
class Serializer;
class TableDef;
class TreeNode;
class Tuple;
class TupleDescriptor;
struct TupleElementDescriptor;
class Value;
}
namespace SQL::AST {
class AddColumn;
class AlterTable;
class ASTNode;
class BetweenExpression;
class BinaryOperatorExpression;
class BlobLiteral;
class CaseExpression;
class CastExpression;
class ChainedExpression;
class CollateExpression;
class ColumnDefinition;
class ColumnNameExpression;
class CommonTableExpression;
class CommonTableExpressionList;
class CreateTable;
class Delete;
class DropColumn;
class DropTable;
class ErrorExpression;
class ErrorStatement;
class ExistsExpression;
class Expression;
class GroupByClause;
class InChainedExpression;
class InSelectionExpression;
class Insert;
class InTableExpression;
class InvertibleNestedDoubleExpression;
class InvertibleNestedExpression;
class IsExpression;
class Lexer;
class LimitClause;
class MatchExpression;
class NestedDoubleExpression;
class NestedExpression;
class NullExpression;
class NullLiteral;
class NumericLiteral;
class OrderingTerm;
class Parser;
class QualifiedTableName;
class RenameColumn;
class RenameTable;
class ResultColumn;
class ReturningClause;
class Select;
class SignedNumber;
class Statement;
class StringLiteral;
class TableOrSubquery;
class Token;
class TypeName;
class UnaryOperatorExpression;
class Update;
}

View file

@ -1,367 +0,0 @@
/*
* Copyright (c) 2021, Jan de Visser <jan@de-visser.net>
* Copyright (c) 2023, Jelle Raaijmakers <jelle@gmta.nl>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/ByteString.h>
#include <AK/Format.h>
#include <AK/QuickSort.h>
#include <LibCore/System.h>
#include <LibSQL/Heap.h>
#include <sys/stat.h>
namespace SQL {
ErrorOr<NonnullRefPtr<Heap>> Heap::create(ByteString file_name)
{
return adopt_nonnull_ref_or_enomem(new (nothrow) Heap(move(file_name)));
}
Heap::Heap(ByteString file_name)
: m_name(move(file_name))
{
}
Heap::~Heap()
{
if (m_file && !m_write_ahead_log.is_empty()) {
if (auto maybe_error = flush(); maybe_error.is_error())
warnln("~Heap({}): {}", name(), maybe_error.error());
}
}
ErrorOr<void> Heap::open()
{
VERIFY(!m_file);
size_t file_size = 0;
struct stat stat_buffer;
if (stat(name().characters(), &stat_buffer) != 0) {
if (errno != ENOENT) {
warnln("Heap::open({}): could not stat: {}"sv, name(), strerror(errno));
return Error::from_string_literal("Heap::open(): could not stat file");
}
} else if (!S_ISREG(stat_buffer.st_mode)) {
warnln("Heap::open({}): can only use regular files"sv, name());
return Error::from_string_literal("Heap::open(): can only use regular files");
} else {
file_size = stat_buffer.st_size;
}
if (file_size > 0) {
m_next_block = file_size / Block::SIZE;
m_highest_block_written = m_next_block - 1;
}
auto file = TRY(Core::File::open(name(), Core::File::OpenMode::ReadWrite));
m_file = TRY(Core::InputBufferedFile::create(move(file)));
if (file_size > 0) {
if (auto error_maybe = read_zero_block(); error_maybe.is_error()) {
m_file = nullptr;
return error_maybe.release_error();
}
} else {
TRY(initialize_zero_block());
}
// FIXME: We should more gracefully handle version incompatibilities. For now, we drop the database.
if (m_version != VERSION) {
dbgln_if(SQL_DEBUG, "Heap file {} opened has incompatible version {}. Deleting for version {}.", name(), m_version, VERSION);
m_file = nullptr;
TRY(Core::System::unlink(name()));
return open();
}
// Perform a heap scan to find all free blocks
// FIXME: this is very inefficient; store free blocks in a persistent heap structure
for (Block::Index index = 1; index <= m_highest_block_written; ++index) {
auto block_data = TRY(read_raw_block(index));
auto size_in_bytes = *reinterpret_cast<u32*>(block_data.data());
if (size_in_bytes == 0)
TRY(m_free_block_indices.try_append(index));
}
dbgln_if(SQL_DEBUG, "Heap file {} opened; number of blocks = {}; free blocks = {}", name(), m_highest_block_written, m_free_block_indices.size());
return {};
}
ErrorOr<size_t> Heap::file_size_in_bytes() const
{
TRY(m_file->seek(0, SeekMode::FromEndPosition));
return TRY(m_file->tell());
}
bool Heap::has_block(Block::Index index) const
{
return (index <= m_highest_block_written || m_write_ahead_log.contains(index))
&& !m_free_block_indices.contains_slow(index);
}
Block::Index Heap::request_new_block_index()
{
if (!m_free_block_indices.is_empty())
return m_free_block_indices.take_last();
return m_next_block++;
}
ErrorOr<ByteBuffer> Heap::read_storage(Block::Index index)
{
dbgln_if(SQL_DEBUG, "{}({})", __FUNCTION__, index);
// Reconstruct the data storage from a potential chain of blocks
ByteBuffer data;
while (index > 0) {
auto block = TRY(read_block(index));
dbgln_if(SQL_DEBUG, " -> {} bytes", block.size_in_bytes());
TRY(data.try_append(block.data().bytes().slice(0, block.size_in_bytes())));
index = block.next_block();
}
return data;
}
ErrorOr<void> Heap::write_storage(Block::Index index, ReadonlyBytes data)
{
dbgln_if(SQL_DEBUG, "{}({}, {} bytes)", __FUNCTION__, index, data.size());
if (index == 0)
return Error::from_string_view("Writing to zero block is not allowed"sv);
if (data.is_empty())
return Error::from_string_view("Writing empty data is not allowed"sv);
if (m_free_block_indices.contains_slow(index))
return Error::from_string_view("Invalid write to a free block index"sv);
// Split up the storage across multiple blocks if necessary, creating a chain
u32 remaining_size = static_cast<u32>(data.size());
u32 offset_in_data = 0;
Block::Index existing_next_block_index = 0;
while (remaining_size > 0) {
auto block_data_size = AK::min(remaining_size, Block::DATA_SIZE);
remaining_size -= block_data_size;
ByteBuffer block_data;
if (has_block(index)) {
auto existing_block = TRY(read_block(index));
block_data = existing_block.data();
TRY(block_data.try_resize(block_data_size));
existing_next_block_index = existing_block.next_block();
} else {
block_data = TRY(ByteBuffer::create_uninitialized(block_data_size));
existing_next_block_index = 0;
}
Block::Index next_block_index = existing_next_block_index;
if (next_block_index == 0 && remaining_size > 0)
next_block_index = request_new_block_index();
else if (remaining_size == 0)
next_block_index = 0;
block_data.bytes().overwrite(0, data.offset(offset_in_data), block_data_size);
TRY(write_block({ index, block_data_size, next_block_index, move(block_data) }));
index = next_block_index;
offset_in_data += block_data_size;
}
// Free remaining blocks in existing chain, if any
if (existing_next_block_index > 0)
TRY(free_storage(existing_next_block_index));
return {};
}
ErrorOr<ByteBuffer> Heap::read_raw_block(Block::Index index)
{
VERIFY(m_file);
VERIFY(index < m_next_block);
if (auto wal_entry = m_write_ahead_log.get(index); wal_entry.has_value())
return wal_entry.value();
TRY(m_file->seek(index * Block::SIZE, SeekMode::SetPosition));
auto buffer = TRY(ByteBuffer::create_uninitialized(Block::SIZE));
TRY(m_file->read_until_filled(buffer));
return buffer;
}
ErrorOr<Block> Heap::read_block(Block::Index index)
{
dbgln_if(SQL_DEBUG, "{}({})", __FUNCTION__, index);
auto buffer = TRY(read_raw_block(index));
auto size_in_bytes = *reinterpret_cast<u32*>(buffer.offset_pointer(0));
auto next_block = *reinterpret_cast<Block::Index*>(buffer.offset_pointer(sizeof(u32)));
auto data = TRY(buffer.slice(Block::HEADER_SIZE, Block::DATA_SIZE));
return Block { index, size_in_bytes, next_block, move(data) };
}
ErrorOr<void> Heap::write_raw_block(Block::Index index, ReadonlyBytes data)
{
dbgln_if(SQL_DEBUG, "Write raw block {}", index);
VERIFY(m_file);
VERIFY(data.size() == Block::SIZE);
TRY(m_file->seek(index * Block::SIZE, SeekMode::SetPosition));
TRY(m_file->write_until_depleted(data));
if (index > m_highest_block_written)
m_highest_block_written = index;
return {};
}
ErrorOr<void> Heap::write_raw_block_to_wal(Block::Index index, ByteBuffer&& data)
{
dbgln_if(SQL_DEBUG, "{}({})", __FUNCTION__, index);
VERIFY(index < m_next_block);
VERIFY(data.size() == Block::SIZE);
TRY(m_write_ahead_log.try_set(index, move(data)));
return {};
}
ErrorOr<void> Heap::write_block(Block const& block)
{
dbgln_if(SQL_DEBUG, "{}({})", __FUNCTION__, block.index());
VERIFY(block.index() < m_next_block);
VERIFY(block.next_block() < m_next_block);
VERIFY(block.size_in_bytes() > 0);
VERIFY(block.data().size() <= Block::DATA_SIZE);
auto size_in_bytes = block.size_in_bytes();
auto next_block = block.next_block();
auto heap_data = TRY(ByteBuffer::create_zeroed(Block::SIZE));
heap_data.overwrite(0, &size_in_bytes, sizeof(size_in_bytes));
heap_data.overwrite(sizeof(size_in_bytes), &next_block, sizeof(next_block));
block.data().bytes().copy_to(heap_data.bytes().slice(Block::HEADER_SIZE));
return write_raw_block_to_wal(block.index(), move(heap_data));
}
ErrorOr<void> Heap::free_storage(Block::Index index)
{
dbgln_if(SQL_DEBUG, "{}({})", __FUNCTION__, index);
VERIFY(index > 0);
while (index > 0) {
auto block = TRY(read_block(index));
TRY(free_block(block));
index = block.next_block();
}
return {};
}
ErrorOr<void> Heap::free_block(Block const& block)
{
auto index = block.index();
dbgln_if(SQL_DEBUG, "{}({})", __FUNCTION__, index);
VERIFY(index > 0);
VERIFY(has_block(index));
// Zero out freed blocks to facilitate a free block scan upon opening the database later
auto zeroed_data = TRY(ByteBuffer::create_zeroed(Block::SIZE));
TRY(write_raw_block_to_wal(index, move(zeroed_data)));
return m_free_block_indices.try_append(index);
}
ErrorOr<void> Heap::flush()
{
VERIFY(m_file);
auto indices = m_write_ahead_log.keys();
quick_sort(indices);
for (auto index : indices) {
dbgln_if(SQL_DEBUG, "Flushing block {}", index);
auto& data = m_write_ahead_log.get(index).value();
TRY(write_raw_block(index, data));
}
m_write_ahead_log.clear();
dbgln_if(SQL_DEBUG, "WAL flushed; new number of blocks = {}", m_highest_block_written);
return {};
}
constexpr static auto FILE_ID = "SerenitySQL "sv;
constexpr static auto VERSION_OFFSET = FILE_ID.length();
constexpr static auto SCHEMAS_ROOT_OFFSET = VERSION_OFFSET + sizeof(u32);
constexpr static auto TABLES_ROOT_OFFSET = SCHEMAS_ROOT_OFFSET + sizeof(u32);
constexpr static auto TABLE_COLUMNS_ROOT_OFFSET = TABLES_ROOT_OFFSET + sizeof(u32);
constexpr static auto USER_VALUES_OFFSET = TABLE_COLUMNS_ROOT_OFFSET + sizeof(u32);
ErrorOr<void> Heap::read_zero_block()
{
dbgln_if(SQL_DEBUG, "Read zero block from {}", name());
auto block = TRY(read_raw_block(0));
auto file_id_buffer = TRY(block.slice(0, FILE_ID.length()));
auto file_id = StringView(file_id_buffer);
if (file_id != FILE_ID) {
warnln("{}: Zero page corrupt. This is probably not a {} heap file"sv, name(), FILE_ID);
return Error::from_string_literal("Heap()::read_zero_block(): Zero page corrupt. This is probably not a SerenitySQL heap file");
}
memcpy(&m_version, block.offset_pointer(VERSION_OFFSET), sizeof(u32));
dbgln_if(SQL_DEBUG, "Version: {}.{}", (m_version & 0xFFFF0000) >> 16, (m_version & 0x0000FFFF));
memcpy(&m_schemas_root, block.offset_pointer(SCHEMAS_ROOT_OFFSET), sizeof(u32));
dbgln_if(SQL_DEBUG, "Schemas root node: {}", m_schemas_root);
memcpy(&m_tables_root, block.offset_pointer(TABLES_ROOT_OFFSET), sizeof(u32));
dbgln_if(SQL_DEBUG, "Tables root node: {}", m_tables_root);
memcpy(&m_table_columns_root, block.offset_pointer(TABLE_COLUMNS_ROOT_OFFSET), sizeof(u32));
dbgln_if(SQL_DEBUG, "Table columns root node: {}", m_table_columns_root);
memcpy(m_user_values.data(), block.offset_pointer(USER_VALUES_OFFSET), m_user_values.size() * sizeof(u32));
for (auto ix = 0u; ix < m_user_values.size(); ix++) {
if (m_user_values[ix])
dbgln_if(SQL_DEBUG, "User value {}: {}", ix, m_user_values[ix]);
}
return {};
}
ErrorOr<void> Heap::update_zero_block()
{
dbgln_if(SQL_DEBUG, "Write zero block to {}", name());
dbgln_if(SQL_DEBUG, "Version: {}.{}", (m_version & 0xFFFF0000) >> 16, (m_version & 0x0000FFFF));
dbgln_if(SQL_DEBUG, "Schemas root node: {}", m_schemas_root);
dbgln_if(SQL_DEBUG, "Tables root node: {}", m_tables_root);
dbgln_if(SQL_DEBUG, "Table Columns root node: {}", m_table_columns_root);
for (auto ix = 0u; ix < m_user_values.size(); ix++) {
if (m_user_values[ix] > 0)
dbgln_if(SQL_DEBUG, "User value {}: {}", ix, m_user_values[ix]);
}
auto buffer = TRY(ByteBuffer::create_zeroed(Block::SIZE));
auto buffer_bytes = buffer.bytes();
buffer_bytes.overwrite(0, FILE_ID.characters_without_null_termination(), FILE_ID.length());
buffer_bytes.overwrite(VERSION_OFFSET, &m_version, sizeof(u32));
buffer_bytes.overwrite(SCHEMAS_ROOT_OFFSET, &m_schemas_root, sizeof(u32));
buffer_bytes.overwrite(TABLES_ROOT_OFFSET, &m_tables_root, sizeof(u32));
buffer_bytes.overwrite(TABLE_COLUMNS_ROOT_OFFSET, &m_table_columns_root, sizeof(u32));
buffer_bytes.overwrite(USER_VALUES_OFFSET, m_user_values.data(), m_user_values.size() * sizeof(u32));
return write_raw_block_to_wal(0, move(buffer));
}
ErrorOr<void> Heap::initialize_zero_block()
{
m_version = VERSION;
m_schemas_root = 0;
m_tables_root = 0;
m_table_columns_root = 0;
m_next_block = 1;
m_highest_block_written = 0;
for (auto& user : m_user_values)
user = 0u;
return update_zero_block();
}
}

View file

@ -1,153 +0,0 @@
/*
* Copyright (c) 2021, Jan de Visser <jan@de-visser.net>
* Copyright (c) 2023, Jelle Raaijmakers <jelle@gmta.nl>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Array.h>
#include <AK/ByteString.h>
#include <AK/Debug.h>
#include <AK/HashMap.h>
#include <AK/RefCounted.h>
#include <AK/Vector.h>
#include <LibCore/File.h>
namespace SQL {
/**
* A Block represents a single discrete chunk of 1024 bytes inside the Heap, and
* acts as the container format for the actual data we are storing. This structure
* is used for everything except block 0, the zero / super block.
*
* If data needs to be stored that is larger than 1016 bytes, Blocks are chained
* together by setting the next block index and the data is reconstructed by
* repeatedly reading blocks until the next block index is 0.
*/
class Block {
public:
typedef u32 Index;
static constexpr u32 SIZE = 1024;
static constexpr u32 HEADER_SIZE = sizeof(u32) + sizeof(Index);
static constexpr u32 DATA_SIZE = SIZE - HEADER_SIZE;
Block(Index index, u32 size_in_bytes, Index next_block, ByteBuffer data)
: m_index(index)
, m_size_in_bytes(size_in_bytes)
, m_next_block(next_block)
, m_data(move(data))
{
VERIFY(index > 0);
}
Index index() const { return m_index; }
u32 size_in_bytes() const { return m_size_in_bytes; }
Index next_block() const { return m_next_block; }
ByteBuffer const& data() const { return m_data; }
private:
Index m_index;
u32 m_size_in_bytes;
Index m_next_block;
ByteBuffer m_data;
};
/**
* A Heap is a logical container for database (SQL) data. Conceptually a
* Heap can be a database file, or a memory block, or another storage medium.
* It contains datastructures, like B-Trees, hash_index tables, or tuple stores
* (basically a list of data tuples).
*
* A Heap can be thought of the backing storage of a single database. It's
* assumed that a single SQL database is backed by a single Heap.
*/
class Heap : public RefCounted<Heap> {
public:
static constexpr u32 VERSION = 5;
static ErrorOr<NonnullRefPtr<Heap>> create(ByteString);
virtual ~Heap();
ByteString const& name() const { return m_name; }
ErrorOr<void> open();
ErrorOr<size_t> file_size_in_bytes() const;
[[nodiscard]] bool has_block(Block::Index) const;
[[nodiscard]] Block::Index request_new_block_index();
Block::Index schemas_root() const { return m_schemas_root; }
void set_schemas_root(Block::Index root)
{
m_schemas_root = root;
update_zero_block().release_value_but_fixme_should_propagate_errors();
}
Block::Index tables_root() const { return m_tables_root; }
void set_tables_root(Block::Index root)
{
m_tables_root = root;
update_zero_block().release_value_but_fixme_should_propagate_errors();
}
Block::Index table_columns_root() const { return m_table_columns_root; }
void set_table_columns_root(Block::Index root)
{
m_table_columns_root = root;
update_zero_block().release_value_but_fixme_should_propagate_errors();
}
u32 version() const { return m_version; }
u32 user_value(size_t index) const
{
return m_user_values[index];
}
void set_user_value(size_t index, u32 value)
{
m_user_values[index] = value;
update_zero_block().release_value_but_fixme_should_propagate_errors();
}
ErrorOr<ByteBuffer> read_storage(Block::Index);
ErrorOr<void> write_storage(Block::Index, ReadonlyBytes);
ErrorOr<void> free_storage(Block::Index);
ErrorOr<void> flush();
private:
explicit Heap(ByteString);
ErrorOr<ByteBuffer> read_raw_block(Block::Index);
ErrorOr<void> write_raw_block(Block::Index, ReadonlyBytes);
ErrorOr<void> write_raw_block_to_wal(Block::Index, ByteBuffer&&);
ErrorOr<Block> read_block(Block::Index);
ErrorOr<void> write_block(Block const&);
ErrorOr<void> free_block(Block const&);
ErrorOr<void> read_zero_block();
ErrorOr<void> initialize_zero_block();
ErrorOr<void> update_zero_block();
ByteString m_name;
OwnPtr<Core::InputBufferedFile> m_file;
Block::Index m_highest_block_written { 0 };
Block::Index m_next_block { 1 };
Block::Index m_schemas_root { 0 };
Block::Index m_tables_root { 0 };
Block::Index m_table_columns_root { 0 };
u32 m_version { VERSION };
Array<u32, 16> m_user_values { 0 };
HashMap<Block::Index, ByteBuffer> m_write_ahead_log;
Vector<Block::Index> m_free_block_indices;
};
}

View file

@ -1,28 +0,0 @@
/*
* Copyright (c) 2021, Jan de Visser <jan@de-visser.net>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibSQL/Heap.h>
#include <LibSQL/Index.h>
#include <LibSQL/TupleDescriptor.h>
namespace SQL {
Index::Index(Serializer& serializer, NonnullRefPtr<TupleDescriptor> const& descriptor, bool unique, Block::Index block_index)
: m_serializer(serializer)
, m_descriptor(descriptor)
, m_unique(unique)
, m_block_index(block_index)
{
}
Index::Index(Serializer& serializer, NonnullRefPtr<TupleDescriptor> const& descriptor, Block::Index block_index)
: m_serializer(serializer)
, m_descriptor(descriptor)
, m_block_index(block_index)
{
}
}

View file

@ -1,58 +0,0 @@
/*
* Copyright (c) 2021, Jan de Visser <jan@de-visser.net>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/RefCounted.h>
#include <LibSQL/Forward.h>
#include <LibSQL/Meta.h>
#include <LibSQL/Serializer.h>
namespace SQL {
class IndexNode {
public:
virtual ~IndexNode() = default;
[[nodiscard]] Block::Index block_index() const { return m_block_index; }
IndexNode* as_index_node() { return dynamic_cast<IndexNode*>(this); }
protected:
explicit IndexNode(Block::Index block_index)
: m_block_index(block_index)
{
}
void set_block_index(Block::Index block_index) { m_block_index = block_index; }
private:
Block::Index m_block_index;
};
class Index : public RefCounted<Index> {
public:
virtual ~Index() = default;
NonnullRefPtr<TupleDescriptor> descriptor() const { return m_descriptor; }
[[nodiscard]] bool duplicates_allowed() const { return !m_unique; }
[[nodiscard]] bool unique() const { return m_unique; }
[[nodiscard]] Block::Index block_index() const { return m_block_index; }
protected:
Index(Serializer&, NonnullRefPtr<TupleDescriptor> const&, bool unique, Block::Index block_index);
Index(Serializer&, NonnullRefPtr<TupleDescriptor> const&, Block::Index block_index);
[[nodiscard]] Serializer& serializer() { return m_serializer; }
void set_block_index(Block::Index block_index) { m_block_index = block_index; }
u32 request_new_block_index() { return m_serializer.request_new_block_index(); }
private:
Serializer m_serializer;
NonnullRefPtr<TupleDescriptor> m_descriptor;
bool m_unique { false };
Block::Index m_block_index { 0 };
};
}

View file

@ -1,34 +0,0 @@
/*
* Copyright (c) 2021, Jan de Visser <jan@de-visser.net>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibSQL/Key.h>
#include <LibSQL/Meta.h>
namespace SQL {
Key::Key(NonnullRefPtr<TupleDescriptor> const& descriptor)
: Tuple(descriptor)
{
}
Key::Key(NonnullRefPtr<IndexDef> index)
: Tuple(index->to_tuple_descriptor())
, m_index(index)
{
}
Key::Key(NonnullRefPtr<TupleDescriptor> const& descriptor, Serializer& serializer)
: Tuple(descriptor, serializer)
{
}
Key::Key(RefPtr<IndexDef> index, Serializer& serializer)
: Key(index->to_tuple_descriptor())
{
Tuple::deserialize(serializer);
}
}

View file

@ -1,29 +0,0 @@
/*
* Copyright (c) 2021, Jan de Visser <jan@de-visser.net>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/RefPtr.h>
#include <LibSQL/Forward.h>
#include <LibSQL/Meta.h>
#include <LibSQL/Tuple.h>
namespace SQL {
class Key : public Tuple {
public:
Key() = default;
explicit Key(NonnullRefPtr<TupleDescriptor> const&);
explicit Key(NonnullRefPtr<IndexDef>);
Key(NonnullRefPtr<TupleDescriptor> const&, Serializer&);
Key(RefPtr<IndexDef>, Serializer&);
RefPtr<IndexDef> index() const { return m_index; }
private:
RefPtr<IndexDef> m_index { nullptr };
};
}

View file

@ -1,239 +0,0 @@
/*
* Copyright (c) 2021, Jan de Visser <jan@de-visser.net>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibSQL/Key.h>
#include <LibSQL/Meta.h>
#include <LibSQL/Type.h>
namespace SQL {
u32 Relation::hash() const
{
return key().hash();
}
ErrorOr<NonnullRefPtr<SchemaDef>> SchemaDef::create(ByteString name)
{
return adopt_nonnull_ref_or_enomem(new (nothrow) SchemaDef(move(name)));
}
ErrorOr<NonnullRefPtr<SchemaDef>> SchemaDef::create(Key const& key)
{
return create(key["schema_name"].to_byte_string());
}
SchemaDef::SchemaDef(ByteString name)
: Relation(move(name))
{
}
Key SchemaDef::key() const
{
auto key = Key(index_def()->to_tuple_descriptor());
key["schema_name"] = name();
key.set_block_index(block_index());
return key;
}
Key SchemaDef::make_key()
{
return Key(index_def());
}
NonnullRefPtr<IndexDef> SchemaDef::index_def()
{
NonnullRefPtr<IndexDef> s_index_def = IndexDef::create("$schema", true, 0).release_value_but_fixme_should_propagate_errors();
if (!s_index_def->size()) {
s_index_def->append_column("schema_name", SQLType::Text, Order::Ascending);
}
return s_index_def;
}
ErrorOr<NonnullRefPtr<ColumnDef>> ColumnDef::create(Relation* parent, size_t column_number, ByteString name, SQLType sql_type)
{
return adopt_nonnull_ref_or_enomem(new (nothrow) ColumnDef(parent, column_number, move(name), sql_type));
}
ColumnDef::ColumnDef(Relation* parent, size_t column_number, ByteString name, SQLType sql_type)
: Relation(move(name), parent)
, m_index(column_number)
, m_type(sql_type)
, m_default(Value(sql_type))
{
}
Key ColumnDef::key() const
{
auto key = Key(index_def());
key["table_hash"] = parent()->hash();
key["column_number"] = column_number();
key["column_name"] = name();
key["column_type"] = to_underlying(type());
return key;
}
void ColumnDef::set_default_value(Value const& default_value)
{
VERIFY(default_value.type() == type());
m_default = default_value;
}
Key ColumnDef::make_key(TableDef const& table_def)
{
Key key(index_def());
key["table_hash"] = table_def.key().hash();
return key;
}
NonnullRefPtr<IndexDef> ColumnDef::index_def()
{
NonnullRefPtr<IndexDef> s_index_def = IndexDef::create("$column", true, 0).release_value_but_fixme_should_propagate_errors();
if (!s_index_def->size()) {
s_index_def->append_column("table_hash", SQLType::Integer, Order::Ascending);
s_index_def->append_column("column_number", SQLType::Integer, Order::Ascending);
s_index_def->append_column("column_name", SQLType::Text, Order::Ascending);
s_index_def->append_column("column_type", SQLType::Integer, Order::Ascending);
}
return s_index_def;
}
ErrorOr<NonnullRefPtr<KeyPartDef>> KeyPartDef::create(IndexDef* index, ByteString name, SQLType sql_type, Order sort_order)
{
return adopt_nonnull_ref_or_enomem(new (nothrow) KeyPartDef(index, move(name), sql_type, sort_order));
}
KeyPartDef::KeyPartDef(IndexDef* index, ByteString name, SQLType sql_type, Order sort_order)
: ColumnDef(index, index->size(), move(name), sql_type)
, m_sort_order(sort_order)
{
}
ErrorOr<NonnullRefPtr<IndexDef>> IndexDef::create(TableDef* table, ByteString name, bool unique, u32 pointer)
{
return adopt_nonnull_ref_or_enomem(new (nothrow) IndexDef(table, move(name), unique, pointer));
}
ErrorOr<NonnullRefPtr<IndexDef>> IndexDef::create(ByteString name, bool unique, u32 pointer)
{
return create(nullptr, move(name), unique, pointer);
}
IndexDef::IndexDef(TableDef* table, ByteString name, bool unique, u32 pointer)
: Relation(move(name), pointer, table)
, m_key_definition()
, m_unique(unique)
{
}
void IndexDef::append_column(ByteString name, SQLType sql_type, Order sort_order)
{
auto part = KeyPartDef::create(this, move(name), sql_type, sort_order).release_value_but_fixme_should_propagate_errors();
m_key_definition.append(part);
}
NonnullRefPtr<TupleDescriptor> IndexDef::to_tuple_descriptor() const
{
NonnullRefPtr<TupleDescriptor> ret = adopt_ref(*new TupleDescriptor);
for (auto& part : m_key_definition) {
ret->append({ "", "", part->name(), part->type(), part->sort_order() });
}
return ret;
}
Key IndexDef::key() const
{
auto key = Key(index_def()->to_tuple_descriptor());
key["table_hash"] = parent()->key().hash();
key["index_name"] = name();
key["unique"] = unique() ? 1 : 0;
return key;
}
Key IndexDef::make_key(TableDef const& table_def)
{
Key key(index_def());
key["table_hash"] = table_def.key().hash();
return key;
}
NonnullRefPtr<IndexDef> IndexDef::index_def()
{
NonnullRefPtr<IndexDef> s_index_def = IndexDef::create("$index", true, 0).release_value_but_fixme_should_propagate_errors();
if (!s_index_def->size()) {
s_index_def->append_column("table_hash", SQLType::Integer, Order::Ascending);
s_index_def->append_column("index_name", SQLType::Text, Order::Ascending);
s_index_def->append_column("unique", SQLType::Integer, Order::Ascending);
}
return s_index_def;
}
ErrorOr<NonnullRefPtr<TableDef>> TableDef::create(SchemaDef* schema, ByteString name)
{
return adopt_nonnull_ref_or_enomem(new (nothrow) TableDef(schema, move(name)));
}
TableDef::TableDef(SchemaDef* schema, ByteString name)
: Relation(move(name), schema)
, m_columns()
, m_indexes()
{
}
NonnullRefPtr<TupleDescriptor> TableDef::to_tuple_descriptor() const
{
NonnullRefPtr<TupleDescriptor> ret = adopt_ref(*new TupleDescriptor);
for (auto& part : m_columns) {
ret->append({ parent()->name(), name(), part->name(), part->type(), Order::Ascending });
}
return ret;
}
Key TableDef::key() const
{
auto key = Key(index_def()->to_tuple_descriptor());
key["schema_hash"] = parent()->key().hash();
key["table_name"] = name();
key.set_block_index(block_index());
return key;
}
void TableDef::append_column(ByteString name, SQLType sql_type)
{
auto column = ColumnDef::create(this, num_columns(), move(name), sql_type).release_value_but_fixme_should_propagate_errors();
m_columns.append(column);
}
void TableDef::append_column(Key const& column)
{
auto column_type = column["column_type"].to_int<UnderlyingType<SQLType>>();
VERIFY(column_type.has_value());
append_column(column["column_name"].to_byte_string(), static_cast<SQLType>(*column_type));
}
Key TableDef::make_key(SchemaDef const& schema_def)
{
return TableDef::make_key(schema_def.key());
}
Key TableDef::make_key(Key const& schema_key)
{
Key key(index_def());
key["schema_hash"] = schema_key.hash();
return key;
}
NonnullRefPtr<IndexDef> TableDef::index_def()
{
NonnullRefPtr<IndexDef> s_index_def = IndexDef::create("$table", true, 0).release_value_but_fixme_should_propagate_errors();
if (!s_index_def->size()) {
s_index_def->append_column("schema_hash", SQLType::Integer, Order::Ascending);
s_index_def->append_column("table_name", SQLType::Text, Order::Ascending);
}
return s_index_def;
}
}

View file

@ -1,155 +0,0 @@
/*
* Copyright (c) 2021, Jan de Visser <jan@de-visser.net>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/ByteString.h>
#include <AK/NonnullRefPtr.h>
#include <AK/RefCounted.h>
#include <AK/Result.h>
#include <AK/Vector.h>
#include <LibCore/EventReceiver.h>
#include <LibSQL/Forward.h>
#include <LibSQL/Heap.h>
#include <LibSQL/Type.h>
#include <LibSQL/Value.h>
namespace SQL {
/**
* This file declares objects describing tables, indexes, and columns.
* It remains to be seen if this will survive in it's current form.
*/
class Relation : public RefCounted<Relation> {
public:
virtual ~Relation() = default;
ByteString const& name() const { return m_name; }
Relation const* parent() const { return m_parent; }
u32 hash() const;
Block::Index block_index() const { return m_block_index; }
void set_block_index(Block::Index block_index) { m_block_index = block_index; }
virtual Key key() const = 0;
protected:
Relation(ByteString name, Block::Index block_index, Relation* parent = nullptr)
: m_name(move(name))
, m_block_index(block_index)
, m_parent(parent)
{
}
explicit Relation(ByteString name, Relation* parent = nullptr)
: Relation(move(name), 0, parent)
{
}
private:
ByteString m_name;
Block::Index m_block_index { 0 };
Relation const* m_parent { nullptr };
};
class SchemaDef : public Relation {
public:
static ErrorOr<NonnullRefPtr<SchemaDef>> create(ByteString name);
static ErrorOr<NonnullRefPtr<SchemaDef>> create(Key const&);
Key key() const override;
static NonnullRefPtr<IndexDef> index_def();
static Key make_key();
private:
explicit SchemaDef(ByteString);
};
class ColumnDef : public Relation {
public:
static ErrorOr<NonnullRefPtr<ColumnDef>> create(Relation*, size_t, ByteString, SQLType);
Key key() const override;
SQLType type() const { return m_type; }
size_t column_number() const { return m_index; }
void set_not_null(bool can_not_be_null) { m_not_null = can_not_be_null; }
bool not_null() const { return m_not_null; }
void set_default_value(Value const& default_value);
Value const& default_value() const { return m_default; }
static NonnullRefPtr<IndexDef> index_def();
static Key make_key(TableDef const&);
protected:
ColumnDef(Relation*, size_t, ByteString, SQLType);
private:
size_t m_index;
SQLType m_type { SQLType::Text };
bool m_not_null { false };
Value m_default;
};
class KeyPartDef : public ColumnDef {
public:
static ErrorOr<NonnullRefPtr<KeyPartDef>> create(IndexDef*, ByteString, SQLType, Order = Order::Ascending);
Order sort_order() const { return m_sort_order; }
private:
KeyPartDef(IndexDef*, ByteString, SQLType, Order);
Order m_sort_order { Order::Ascending };
};
class IndexDef : public Relation {
public:
static ErrorOr<NonnullRefPtr<IndexDef>> create(TableDef*, ByteString, bool unique = true, u32 pointer = 0);
static ErrorOr<NonnullRefPtr<IndexDef>> create(ByteString, bool unique = true, u32 pointer = 0);
Vector<NonnullRefPtr<KeyPartDef>> const& key_definition() const { return m_key_definition; }
bool unique() const { return m_unique; }
[[nodiscard]] size_t size() const { return m_key_definition.size(); }
void append_column(ByteString, SQLType, Order = Order::Ascending);
Key key() const override;
[[nodiscard]] NonnullRefPtr<TupleDescriptor> to_tuple_descriptor() const;
static NonnullRefPtr<IndexDef> index_def();
static Key make_key(TableDef const& table_def);
private:
IndexDef(TableDef*, ByteString, bool unique, u32 pointer);
Vector<NonnullRefPtr<KeyPartDef>> m_key_definition;
bool m_unique { false };
friend TableDef;
};
class TableDef : public Relation {
public:
static ErrorOr<NonnullRefPtr<TableDef>> create(SchemaDef*, ByteString);
Key key() const override;
void append_column(ByteString, SQLType);
void append_column(Key const&);
size_t num_columns() { return m_columns.size(); }
size_t num_indexes() { return m_indexes.size(); }
Vector<NonnullRefPtr<ColumnDef>> const& columns() const { return m_columns; }
Vector<NonnullRefPtr<IndexDef>> const& indexes() const { return m_indexes; }
[[nodiscard]] NonnullRefPtr<TupleDescriptor> to_tuple_descriptor() const;
static NonnullRefPtr<IndexDef> index_def();
static Key make_key(SchemaDef const& schema_def);
static Key make_key(Key const& schema_key);
private:
explicit TableDef(SchemaDef*, ByteString);
Vector<NonnullRefPtr<ColumnDef>> m_columns;
Vector<NonnullRefPtr<IndexDef>> m_indexes;
};
}

View file

@ -1,47 +0,0 @@
/*
* Copyright (c) 2022, Jan de Visser <jan@de-visser.net>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/StringBuilder.h>
#include <LibSQL/Result.h>
namespace SQL {
ByteString Result::error_string() const
{
VERIFY(is_error());
StringView error_code;
StringView error_description;
switch (m_error) {
#undef __ENUMERATE_SQL_ERROR
#define __ENUMERATE_SQL_ERROR(error, description) \
case SQLErrorCode::error: \
error_code = #error##sv; \
error_description = description##sv; \
break;
ENUMERATE_SQL_ERRORS(__ENUMERATE_SQL_ERROR)
#undef __ENUMERATE_SQL_ERROR
default:
VERIFY_NOT_REACHED();
}
StringBuilder builder;
builder.appendff("{}: ", error_code);
if (m_error_message.has_value()) {
if (error_description.find("{}"sv).has_value())
builder.appendff(error_description, *m_error_message);
else
builder.appendff("{}: {}", error_description, *m_error_message);
} else {
builder.append(error_description);
}
return builder.to_byte_string();
}
}

View file

@ -1,132 +0,0 @@
/*
* Copyright (c) 2021, Jan de Visser <jan@de-visser.net>
* Copyright (c) 2021, Mahmoud Mandour <ma.mandourr@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/ByteString.h>
#include <AK/Error.h>
#include <AK/Noncopyable.h>
#include <LibSQL/Type.h>
namespace SQL {
#define ENUMERATE_SQL_COMMANDS(S) \
S(Unknown) \
S(Create) \
S(Delete) \
S(Describe) \
S(Insert) \
S(Select) \
S(Update)
enum class SQLCommand {
#undef __ENUMERATE_SQL_COMMAND
#define __ENUMERATE_SQL_COMMAND(command) command,
ENUMERATE_SQL_COMMANDS(__ENUMERATE_SQL_COMMAND)
#undef __ENUMERATE_SQL_COMMAND
};
constexpr char const* command_tag(SQLCommand command)
{
switch (command) {
#undef __ENUMERATE_SQL_COMMAND
#define __ENUMERATE_SQL_COMMAND(command) \
case SQLCommand::command: \
return #command;
ENUMERATE_SQL_COMMANDS(__ENUMERATE_SQL_COMMAND)
#undef __ENUMERATE_SQL_COMMAND
}
}
#define ENUMERATE_SQL_ERRORS(S) \
S(AmbiguousColumnName, "Column name '{}' is ambiguous") \
S(BooleanOperatorTypeMismatch, "Cannot apply '{}' operator to non-boolean operands") \
S(ColumnDoesNotExist, "Column '{}' does not exist") \
S(DatabaseDoesNotExist, "Database '{}' does not exist") \
S(DatabaseUnavailable, "Database Unavailable") \
S(IntegerOperatorTypeMismatch, "Cannot apply '{}' operator to non-numeric operands") \
S(IntegerOverflow, "Operation would cause integer overflow") \
S(InternalError, "{}") \
S(InvalidDatabaseName, "Invalid database name '{}'") \
S(InvalidNumberOfPlaceholderValues, "Number of values does not match number of placeholders") \
S(InvalidNumberOfValues, "Number of values does not match number of columns") \
S(InvalidOperator, "Invalid operator '{}'") \
S(InvalidType, "Invalid type '{}'") \
S(InvalidValueType, "Invalid type for attribute '{}'") \
S(NoError, "No error") \
S(NotYetImplemented, "{}") \
S(NumericOperatorTypeMismatch, "Cannot apply '{}' operator to non-numeric operands") \
S(SchemaDoesNotExist, "Schema '{}' does not exist") \
S(SchemaExists, "Schema '{}' already exist") \
S(StatementUnavailable, "Statement with id '{}' Unavailable") \
S(SyntaxError, "Syntax Error") \
S(TableDoesNotExist, "Table '{}' does not exist") \
S(TableExists, "Table '{}' already exist")
enum class SQLErrorCode {
#undef __ENUMERATE_SQL_ERROR
#define __ENUMERATE_SQL_ERROR(error, description) error,
ENUMERATE_SQL_ERRORS(__ENUMERATE_SQL_ERROR)
#undef __ENUMERATE_SQL_ERROR
};
class [[nodiscard]] Result {
AK_MAKE_NONCOPYABLE(Result);
AK_MAKE_DEFAULT_MOVABLE(Result);
public:
ALWAYS_INLINE Result(SQLCommand command)
: m_command(command)
{
}
ALWAYS_INLINE Result(SQLCommand command, SQLErrorCode error)
: m_command(command)
, m_error(error)
{
}
ALWAYS_INLINE Result(SQLCommand command, SQLErrorCode error, ByteString error_message)
: m_command(command)
, m_error(error)
, m_error_message(move(error_message))
{
}
ALWAYS_INLINE Result(Error error)
: m_error(SQLErrorCode::InternalError)
, m_error_message(error.string_literal())
{
}
SQLCommand command() const { return m_command; }
SQLErrorCode error() const { return m_error; }
ByteString error_string() const;
// These are for compatibility with the TRY() macro in AK.
[[nodiscard]] bool is_error() const { return m_error != SQLErrorCode::NoError; }
[[nodiscard]] Result release_value() { return move(*this); }
Result release_error()
{
VERIFY(is_error());
if (m_error_message.has_value())
return { m_command, m_error, m_error_message.release_value() };
return { m_command, m_error };
}
private:
SQLCommand m_command { SQLCommand::Unknown };
SQLErrorCode m_error { SQLErrorCode::NoError };
Optional<ByteString> m_error_message {};
};
template<typename ValueType>
using ResultOr = ErrorOr<ValueType, Result>;
}

View file

@ -1,53 +0,0 @@
/*
* Copyright (c) 2022, Jan de Visser <jan@de-visser.net>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibSQL/ResultSet.h>
namespace SQL {
size_t ResultSet::binary_search(Tuple const& sort_key, size_t low, size_t high)
{
if (high <= low) {
auto compare = sort_key.compare(at(low).sort_key);
return (compare > 0) ? low + 1 : low;
}
auto mid = (low + high) / 2;
auto compare = sort_key.compare(at(mid).sort_key);
if (compare == 0)
return mid + 1;
if (compare > 0)
return binary_search(sort_key, mid + 1, high);
return binary_search(sort_key, low, mid);
}
void ResultSet::insert_row(Tuple const& row, Tuple const& sort_key)
{
if ((sort_key.size() == 0) || is_empty()) {
empend(row, sort_key);
return;
}
auto ix = binary_search(sort_key, 0, size() - 1);
insert(ix, ResultRow { row, sort_key });
}
void ResultSet::limit(size_t offset, size_t limit)
{
if (offset > 0) {
if (offset > size()) {
clear();
return;
}
remove(0, offset);
}
if (size() > limit)
remove(limit, size() - limit);
}
}

View file

@ -1,47 +0,0 @@
/*
* Copyright (c) 2022, Jan de Visser <jan@de-visser.net>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Vector.h>
#include <LibSQL/Result.h>
#include <LibSQL/Tuple.h>
#include <LibSQL/Type.h>
namespace SQL {
struct ResultRow {
Tuple row;
Tuple sort_key;
};
class ResultSet : public Vector<ResultRow> {
public:
ALWAYS_INLINE ResultSet(SQLCommand command)
: m_command(command)
{
}
ALWAYS_INLINE ResultSet(SQLCommand command, Vector<ByteString> column_names)
: m_command(command)
, m_column_names(move(column_names))
{
}
SQLCommand command() const { return m_command; }
Vector<ByteString> const& column_names() const { return m_column_names; }
void insert_row(Tuple const& row, Tuple const& sort_key);
void limit(size_t offset, size_t limit);
private:
size_t binary_search(Tuple const& sort_key, size_t low, size_t high);
SQLCommand m_command { SQLCommand::Unknown };
Vector<ByteString> m_column_names;
};
}

View file

@ -1,31 +0,0 @@
/*
* Copyright (c) 2021, Jan de Visser <jan@de-visser.net>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibSQL/Meta.h>
#include <LibSQL/Row.h>
namespace SQL {
Row::Row(NonnullRefPtr<TableDef> table, Block::Index block_index)
: Tuple(table->to_tuple_descriptor())
, m_table(move(table))
{
set_block_index(block_index);
}
void Row::deserialize(Serializer& serializer)
{
Tuple::deserialize(serializer);
m_next_block_index = serializer.deserialize<Block::Index>();
}
void Row::serialize(Serializer& serializer) const
{
Tuple::serialize(serializer);
serializer.serialize<Block::Index>(next_block_index());
}
}

View file

@ -1,46 +0,0 @@
/*
* Copyright (c) 2021, Jan de Visser <jan@de-visser.net>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/NonnullRefPtr.h>
#include <LibSQL/Forward.h>
#include <LibSQL/Meta.h>
#include <LibSQL/Tuple.h>
#include <LibSQL/Value.h>
namespace SQL {
/**
* A Tuple is an element of a sequential-access persistence data structure
* like a flat table. Like a key it has a definition for all its parts,
* but unlike a key this definition is not optional.
*
* FIXME Tuples should logically belong to a TupleStore object, but right now
* they stand by themselves; they contain a row's worth of data and a pointer
* to the next Tuple.
*/
class Row : public Tuple {
public:
explicit Row(NonnullRefPtr<TableDef>, Block::Index block_index = 0);
virtual ~Row() override = default;
[[nodiscard]] Block::Index next_block_index() const { return m_next_block_index; }
void set_next_block_index(Block::Index index) { m_next_block_index = index; }
TableDef const& table() const { return *m_table; }
TableDef& table() { return *m_table; }
[[nodiscard]] virtual size_t length() const override { return Tuple::length() + sizeof(Block::Index); }
virtual void serialize(Serializer&) const override;
virtual void deserialize(Serializer&) override;
private:
NonnullRefPtr<TableDef> m_table;
Block::Index m_next_block_index { 0 };
};
}

View file

@ -1,88 +0,0 @@
/*
* Copyright (c) 2021, Jan de Visser <jan@de-visser.net>
* Copyright (c) 2022, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/ByteString.h>
#include <AK/ScopeGuard.h>
#include <AK/String.h>
#include <LibSQL/SQLClient.h>
namespace SQL {
void SQLClient::execution_success(u64 statement_id, u64 execution_id, Vector<ByteString> const& column_names, bool has_results, size_t created, size_t updated, size_t deleted)
{
if (!on_execution_success) {
outln("{} row(s) created, {} updated, {} deleted", created, updated, deleted);
return;
}
ExecutionSuccess success {
.statement_id = statement_id,
.execution_id = execution_id,
.column_names = move(const_cast<Vector<ByteString>&>(column_names)),
.has_results = has_results,
.rows_created = created,
.rows_updated = updated,
.rows_deleted = deleted,
};
on_execution_success(move(success));
}
void SQLClient::execution_error(u64 statement_id, u64 execution_id, SQLErrorCode const& code, ByteString const& message)
{
if (!on_execution_error) {
warnln("Execution error for statement_id {}: {} ({})", statement_id, message, to_underlying(code));
return;
}
ExecutionError error {
.statement_id = statement_id,
.execution_id = execution_id,
.error_code = code,
.error_message = move(const_cast<ByteString&>(message)),
};
on_execution_error(move(error));
}
void SQLClient::next_result(u64 statement_id, u64 execution_id, Vector<Value> const& row)
{
ScopeGuard guard { [&]() { async_ready_for_next_result(statement_id, execution_id); } };
if (!on_next_result) {
StringBuilder builder;
builder.join(", "sv, row, "\"{}\""sv);
outln("{}", builder.string_view());
return;
}
ExecutionResult result {
.statement_id = statement_id,
.execution_id = execution_id,
.values = move(const_cast<Vector<Value>&>(row)),
};
on_next_result(move(result));
}
void SQLClient::results_exhausted(u64 statement_id, u64 execution_id, size_t total_rows)
{
if (!on_results_exhausted) {
outln("{} total row(s)", total_rows);
return;
}
ExecutionComplete success {
.statement_id = statement_id,
.execution_id = execution_id,
.total_rows = total_rows,
};
on_results_exhausted(move(success));
}
}

View file

@ -1,75 +0,0 @@
/*
* Copyright (c) 2021, Jan de Visser <jan@de-visser.net>
* Copyright (c) 2022, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibIPC/ConnectionToServer.h>
#include <LibSQL/Result.h>
#include <SQLServer/SQLClientEndpoint.h>
#include <SQLServer/SQLServerEndpoint.h>
namespace SQL {
struct ExecutionSuccess {
u64 statement_id { 0 };
u64 execution_id { 0 };
Vector<ByteString> column_names;
bool has_results { false };
size_t rows_created { 0 };
size_t rows_updated { 0 };
size_t rows_deleted { 0 };
};
struct ExecutionError {
u64 statement_id { 0 };
u64 execution_id { 0 };
SQLErrorCode error_code;
ByteString error_message;
};
struct ExecutionResult {
u64 statement_id { 0 };
u64 execution_id { 0 };
Vector<Value> values;
};
struct ExecutionComplete {
u64 statement_id { 0 };
u64 execution_id { 0 };
size_t total_rows { 0 };
};
class SQLClient
: public IPC::ConnectionToServer<SQLClientEndpoint, SQLServerEndpoint>
, public SQLClientEndpoint {
IPC_CLIENT_CONNECTION(SQLClient, "/tmp/session/%sid/portal/sql"sv)
public:
explicit SQLClient(NonnullOwnPtr<Core::LocalSocket> socket)
: IPC::ConnectionToServer<SQLClientEndpoint, SQLServerEndpoint>(*this, move(socket))
{
}
virtual ~SQLClient() = default;
Function<void(ExecutionSuccess)> on_execution_success;
Function<void(ExecutionError)> on_execution_error;
Function<void(ExecutionResult)> on_next_result;
Function<void(ExecutionComplete)> on_results_exhausted;
private:
virtual void execution_success(u64 statement_id, u64 execution_id, Vector<ByteString> const& column_names, bool has_results, size_t created, size_t updated, size_t deleted) override;
virtual void execution_error(u64 statement_id, u64 execution_id, SQLErrorCode const& code, ByteString const& message) override;
virtual void next_result(u64 statement_id, u64 execution_id, Vector<SQL::Value> const&) override;
virtual void results_exhausted(u64 statement_id, u64 execution_id, size_t total_rows) override;
};
}

View file

@ -1,28 +0,0 @@
/*
* Copyright (c) 2021, Jan de Visser <jan@de-visser.net>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibSQL/Serializer.h>
namespace SQL {
void Serializer::serialize(ByteString const& text)
{
serialize<u32>(text.length());
if (!text.is_empty())
write((u8 const*)text.characters(), text.length());
}
void Serializer::deserialize_to(ByteString& text)
{
auto length = deserialize<u32>();
if (length > 0) {
text = ByteString(reinterpret_cast<char const*>(read(length)), length);
} else {
text = "";
}
}
}

View file

@ -1,167 +0,0 @@
/*
* Copyright (c) 2021, Jan de Visser <jan@de-visser.net>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/ByteBuffer.h>
#include <AK/ByteString.h>
#include <AK/Debug.h>
#include <AK/Format.h>
#include <LibSQL/Forward.h>
#include <LibSQL/Heap.h>
namespace SQL {
class Serializer {
public:
Serializer() = default;
Serializer(RefPtr<Heap> heap)
: m_heap(heap)
{
}
void read_storage(Block::Index block_index)
{
m_buffer = m_heap->read_storage(block_index).release_value_but_fixme_should_propagate_errors();
m_current_offset = 0;
}
void reset()
{
m_buffer.clear();
m_current_offset = 0;
}
void rewind()
{
m_current_offset = 0;
}
template<typename T, typename... Args>
T deserialize_block(Block::Index block_index, Args&&... args)
{
read_storage(block_index);
return deserialize<T>(forward<Args>(args)...);
}
template<typename T>
void deserialize_block_to(Block::Index block_index, T& t)
{
read_storage(block_index);
return deserialize_to<T>(t);
}
template<typename T>
void deserialize_to(T& t)
{
if constexpr (IsArithmetic<T>)
memcpy(&t, read(sizeof(T)), sizeof(T));
else
t.deserialize(*this);
}
void deserialize_to(ByteString& text);
template<typename T, typename... Args>
NonnullOwnPtr<T> make_and_deserialize(Args&&... args)
{
auto ptr = make<T>(forward<Args>(args)...);
ptr->deserialize(*this);
return ptr;
}
template<typename T, typename... Args>
NonnullRefPtr<T> adopt_and_deserialize(Args&&... args)
{
auto ptr = adopt_ref(*new T(forward<Args>(args)...));
ptr->deserialize(*this);
return ptr;
}
template<typename T, typename... Args>
T deserialize(Args&&... args)
{
T t(forward<Args>(args)...);
deserialize_to(t);
return t;
}
template<typename T>
void serialize(T const& t)
{
if constexpr (IsArithmetic<T>)
write((u8 const*)(&t), sizeof(T));
else
t.serialize(*this);
}
void serialize(ByteString const&);
template<typename T>
bool serialize_and_write(T const& t)
{
VERIFY(!m_heap.is_null());
reset();
serialize<T>(t);
m_heap->write_storage(t.block_index(), m_buffer).release_value_but_fixme_should_propagate_errors();
return true;
}
[[nodiscard]] size_t offset() const { return m_current_offset; }
u32 request_new_block_index()
{
return m_heap->request_new_block_index();
}
bool has_block(u32 pointer) const
{
return m_heap->has_block(pointer);
}
Heap& heap()
{
return *m_heap;
}
private:
void write(u8 const* ptr, size_t sz)
{
if constexpr (SQL_DEBUG)
dump(ptr, sz, "(out) =>");
m_buffer.append(ptr, sz);
m_current_offset += sz;
}
u8 const* read(size_t sz)
{
auto buffer_ptr = m_buffer.offset_pointer(m_current_offset);
if constexpr (SQL_DEBUG)
dump(buffer_ptr, sz, "<= (in)");
m_current_offset += sz;
return buffer_ptr;
}
static void dump(u8 const* ptr, size_t sz, ByteString const& prefix)
{
StringBuilder builder;
builder.appendff("{0} {1:04x} | ", prefix, sz);
Vector<ByteString> bytes;
for (auto ix = 0u; ix < sz; ++ix)
bytes.append(ByteString::formatted("{0:02x}", *(ptr + ix)));
StringBuilder bytes_builder;
bytes_builder.join(' ', bytes);
builder.append(bytes_builder.to_byte_string());
dbgln(builder.to_byte_string());
}
ByteBuffer m_buffer {};
size_t m_current_offset { 0 };
// FIXME: make this a NonnullRefPtr<Heap> so we can get rid of the null checks
RefPtr<Heap> m_heap { nullptr };
};
}

View file

@ -1,383 +0,0 @@
/*
* Copyright (c) 2021, Jan de Visser <jan@de-visser.net>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Debug.h>
#include <AK/Format.h>
#include <AK/StringBuilder.h>
#include <LibSQL/BTree.h>
#include <LibSQL/Serializer.h>
namespace SQL {
DownPointer::DownPointer(TreeNode* owner, Block::Index block_index)
: m_owner(owner)
, m_block_index(block_index)
, m_node(nullptr)
{
}
DownPointer::DownPointer(TreeNode* owner, TreeNode* node)
: m_owner(owner)
, m_block_index((node) ? node->block_index() : 0)
, m_node(adopt_own_if_nonnull(node))
{
}
DownPointer::DownPointer(TreeNode* owner, DownPointer& down)
: m_owner(owner)
, m_block_index(down.m_block_index)
, m_node(move(down.m_node))
{
}
DownPointer::DownPointer(DownPointer&& other)
: m_owner(other.m_owner)
, m_block_index(other.block_index())
, m_node(other.m_node ? move(other.m_node) : nullptr)
{
}
TreeNode* DownPointer::node()
{
if (!m_node)
deserialize(m_owner->tree().serializer());
return m_node;
}
void DownPointer::deserialize(Serializer& serializer)
{
if (m_node || !m_block_index)
return;
serializer.read_storage(m_block_index);
m_node = serializer.make_and_deserialize<TreeNode>(m_owner->tree(), m_owner, m_block_index);
}
TreeNode::TreeNode(BTree& tree, Block::Index block_index)
: IndexNode(block_index)
, m_tree(tree)
, m_up(nullptr)
, m_entries()
, m_down()
{
}
TreeNode::TreeNode(BTree& tree, TreeNode* up, Block::Index block_index)
: IndexNode(block_index)
, m_tree(tree)
, m_up(up)
, m_entries()
, m_down()
{
m_down.append(DownPointer(this, nullptr));
m_is_leaf = true;
}
TreeNode::TreeNode(BTree& tree, TreeNode* up, DownPointer& left, Block::Index block_index)
: IndexNode(block_index)
, m_tree(tree)
, m_up(up)
, m_entries()
, m_down()
{
if (left.m_node != nullptr)
left.m_node->m_up = this;
m_down.append(DownPointer(this, left));
m_is_leaf = left.block_index() == 0;
if (!block_index)
set_block_index(m_tree.request_new_block_index());
}
TreeNode::TreeNode(BTree& tree, TreeNode* up, TreeNode* left, Block::Index block_index)
: IndexNode(block_index)
, m_tree(tree)
, m_up(up)
, m_entries()
, m_down()
{
m_down.append(DownPointer(this, left));
m_is_leaf = left->block_index() == 0;
}
void TreeNode::deserialize(Serializer& serializer)
{
auto nodes = serializer.deserialize<u32>();
dbgln_if(SQL_DEBUG, "Deserializing node. Size {}", nodes);
if (nodes > 0) {
for (u32 i = 0; i < nodes; i++) {
auto left = serializer.deserialize<u32>();
dbgln_if(SQL_DEBUG, "Down[{}] {}", i, left);
if (!m_down.is_empty())
VERIFY((left == 0) == m_is_leaf);
else
m_is_leaf = (left == 0);
m_entries.append(serializer.deserialize<Key>(m_tree.descriptor()));
m_down.empend(this, left);
}
auto right = serializer.deserialize<u32>();
dbgln_if(SQL_DEBUG, "Right {}", right);
VERIFY((right == 0) == m_is_leaf);
m_down.empend(this, right);
}
}
void TreeNode::serialize(Serializer& serializer) const
{
u32 sz = size();
serializer.serialize<u32>(sz);
if (sz > 0) {
for (auto ix = 0u; ix < size(); ix++) {
auto& entry = m_entries[ix];
dbgln_if(SQL_DEBUG, "Serializing Left[{}] = {}", ix, m_down[ix].block_index());
serializer.serialize<u32>(is_leaf() ? 0u : m_down[ix].block_index());
serializer.serialize<Key>(entry);
}
dbgln_if(SQL_DEBUG, "Serializing Right = {}", m_down[size()].block_index());
serializer.serialize<u32>(is_leaf() ? 0u : m_down[size()].block_index());
}
}
size_t TreeNode::length() const
{
if (!size())
return 0;
size_t len = sizeof(u32);
for (auto& key : m_entries)
len += sizeof(u32) + key.length();
return len;
}
bool TreeNode::insert(Key const& key)
{
dbgln_if(SQL_DEBUG, "[#{}] INSERT({})", block_index(), key.to_byte_string());
if (!is_leaf())
return node_for(key)->insert_in_leaf(key);
return insert_in_leaf(key);
}
bool TreeNode::update_key_pointer(Key const& key)
{
dbgln_if(SQL_DEBUG, "[#{}] UPDATE({}, {})", block_index(), key.to_byte_string(), key.block_index());
if (!is_leaf())
return node_for(key)->update_key_pointer(key);
for (auto ix = 0u; ix < size(); ix++) {
if (key == m_entries[ix]) {
dbgln_if(SQL_DEBUG, "[#{}] {} == {}",
block_index(), key.to_byte_string(), m_entries[ix].to_byte_string());
if (m_entries[ix].block_index() != key.block_index()) {
m_entries[ix].set_block_index(key.block_index());
dump_if(SQL_DEBUG, "To WAL");
tree().serializer().serialize_and_write<TreeNode>(*this);
}
return true;
}
}
return false;
}
bool TreeNode::insert_in_leaf(Key const& key)
{
VERIFY(is_leaf());
if (!m_tree.duplicates_allowed()) {
for (auto& entry : m_entries) {
if (key == entry) {
dbgln_if(SQL_DEBUG, "[#{}] duplicate key {}", block_index(), key.to_byte_string());
return false;
}
}
}
dbgln_if(SQL_DEBUG, "[#{}] insert_in_leaf({})", block_index(), key.to_byte_string());
just_insert(key, nullptr);
return true;
}
Block::Index TreeNode::down_pointer(size_t ix) const
{
return m_down[ix].block_index();
}
TreeNode* TreeNode::down_node(size_t ix)
{
return m_down[ix].node();
}
TreeNode* TreeNode::node_for(Key const& key)
{
dump_if(SQL_DEBUG, ByteString::formatted("node_for(Key {})", key.to_byte_string()));
if (is_leaf())
return this;
for (size_t ix = 0; ix < size(); ix++) {
if (key < m_entries[ix]) {
dbgln_if(SQL_DEBUG, "[{}] {} < {} v{}",
block_index(), (ByteString)key, (ByteString)m_entries[ix], m_down[ix].block_index());
return down_node(ix)->node_for(key);
}
}
dbgln_if(SQL_DEBUG, "[#{}] {} >= {} v{}",
block_index(), key.to_byte_string(), (ByteString)m_entries[size() - 1], m_down[size()].block_index());
return down_node(size())->node_for(key);
}
Optional<u32> TreeNode::get(Key& key)
{
dump_if(SQL_DEBUG, ByteString::formatted("get({})", key.to_byte_string()));
for (auto ix = 0u; ix < size(); ix++) {
if (key < m_entries[ix]) {
if (is_leaf()) {
dbgln_if(SQL_DEBUG, "[#{}] {} < {} -> 0",
block_index(), key.to_byte_string(), (ByteString)m_entries[ix]);
return {};
} else {
dbgln_if(SQL_DEBUG, "[{}] {} < {} ({} -> {})",
block_index(), key.to_byte_string(), (ByteString)m_entries[ix],
ix, m_down[ix].block_index());
return down_node(ix)->get(key);
}
}
if (key == m_entries[ix]) {
dbgln_if(SQL_DEBUG, "[#{}] {} == {} -> {}",
block_index(), key.to_byte_string(), (ByteString)m_entries[ix],
m_entries[ix].block_index());
key.set_block_index(m_entries[ix].block_index());
return m_entries[ix].block_index();
}
}
if (m_entries.is_empty()) {
dbgln_if(SQL_DEBUG, "[#{}] {} Empty node??", block_index(), key.to_byte_string());
VERIFY_NOT_REACHED();
}
if (is_leaf()) {
dbgln_if(SQL_DEBUG, "[#{}] {} > {} -> 0",
block_index(), key.to_byte_string(), (ByteString)m_entries[size() - 1]);
return {};
}
dbgln_if(SQL_DEBUG, "[#{}] {} > {} ({} -> {})",
block_index(), key.to_byte_string(), (ByteString)m_entries[size() - 1],
size(), m_down[size()].block_index());
return down_node(size())->get(key);
}
void TreeNode::just_insert(Key const& key, TreeNode* right)
{
dbgln_if(SQL_DEBUG, "[#{}] just_insert({}, right = {})",
block_index(), (ByteString)key, (right) ? right->block_index() : 0);
dump_if(SQL_DEBUG, "Before");
for (auto ix = 0u; ix < size(); ix++) {
if (key < m_entries[ix]) {
m_entries.insert(ix, key);
VERIFY(is_leaf() == (right == nullptr));
m_down.insert(ix + 1, DownPointer(this, right));
if (length() > Block::DATA_SIZE) {
split();
} else {
dump_if(SQL_DEBUG, "To WAL");
tree().serializer().serialize_and_write(*this);
}
return;
}
}
m_entries.append(key);
m_down.empend(this, right);
if (length() > Block::DATA_SIZE) {
split();
} else {
dump_if(SQL_DEBUG, "To WAL");
tree().serializer().serialize_and_write(*this);
}
}
void TreeNode::split()
{
dump_if(SQL_DEBUG, "Splitting node");
if (!m_up)
// Make new m_up. This is the new root node.
m_up = m_tree.new_root();
// Take the left pointer for the new node:
auto median_index = size() / 2;
if (!(size() % 2))
++median_index;
DownPointer left = m_down.take(median_index);
// Create the new right node:
auto* new_node = new TreeNode(tree(), m_up, left);
// Move the rightmost keys from this node to the new right node:
while (m_entries.size() > median_index) {
auto entry = m_entries.take(median_index);
auto down = m_down.take(median_index);
// Reparent to new right node:
if (down.m_node != nullptr)
down.m_node->m_up = new_node;
new_node->m_entries.append(entry);
new_node->m_down.append(move(down));
}
// Move the median key in the node one level up. Its right node will
// be the new node:
auto median = m_entries.take_last();
dump_if(SQL_DEBUG, "Split Left To WAL");
tree().serializer().serialize_and_write(*this);
new_node->dump_if(SQL_DEBUG, "Split Right to WAL");
tree().serializer().serialize_and_write(*new_node);
m_up->just_insert(median, new_node);
}
void TreeNode::dump_if(int flag, ByteString&& msg)
{
if (!flag)
return;
StringBuilder builder;
builder.appendff("[#{}] ", block_index());
if (!msg.is_empty())
builder.appendff("{}", msg);
builder.append(": "sv);
if (m_up)
builder.appendff("[^{}] -> ", m_up->block_index());
else
builder.append("* -> "sv);
for (size_t ix = 0; ix < m_entries.size(); ix++) {
if (!is_leaf())
builder.appendff("[v{}] ", m_down[ix].block_index());
else
VERIFY(m_down[ix].block_index() == 0);
builder.appendff("'{}' ", (ByteString)m_entries[ix]);
}
if (!is_leaf())
builder.appendff("[v{}]", m_down[size()].block_index());
else
VERIFY(m_down[size()].block_index() == 0);
builder.appendff(" (size {}", (int)size());
if (is_leaf())
builder.append(", leaf"sv);
builder.append(')');
dbgln(builder.to_byte_string());
}
void TreeNode::list_node(int indent)
{
auto do_indent = [&]() {
for (int i = 0; i < indent; ++i)
warn(" ");
};
do_indent();
warnln("--> #{}", block_index());
for (auto ix = 0u; ix < size(); ix++) {
if (!is_leaf())
down_node(ix)->list_node(indent + 2);
do_indent();
warnln("{}", m_entries[ix].to_byte_string());
}
if (!is_leaf())
down_node(size())->list_node(indent + 2);
}
}

View file

@ -1,207 +0,0 @@
/*
* Copyright (c) 2021, Jan de Visser <jan@de-visser.net>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/ByteString.h>
#include <AK/StringBuilder.h>
#include <LibSQL/Serializer.h>
#include <LibSQL/Tuple.h>
#include <LibSQL/TupleDescriptor.h>
#include <LibSQL/Value.h>
namespace SQL {
Tuple::Tuple()
: m_descriptor(adopt_ref(*new TupleDescriptor))
, m_data()
{
}
Tuple::Tuple(NonnullRefPtr<TupleDescriptor> const& descriptor, Block::Index block_index)
: m_descriptor(descriptor)
, m_data()
, m_block_index(block_index)
{
for (auto& element : *descriptor)
m_data.empend(element.type);
}
Tuple::Tuple(NonnullRefPtr<TupleDescriptor> const& descriptor, Serializer& serializer)
: Tuple(descriptor)
{
deserialize(serializer);
}
void Tuple::deserialize(Serializer& serializer)
{
dbgln_if(SQL_DEBUG, "deserialize tuple at offset {}", serializer.offset());
serializer.deserialize_to<u32>(m_block_index);
dbgln_if(SQL_DEBUG, "block_index: {}", m_block_index);
auto number_of_elements = serializer.deserialize<u32>();
m_data.clear();
m_descriptor->clear();
for (auto ix = 0u; ix < number_of_elements; ++ix) {
m_descriptor->append(serializer.deserialize<TupleElementDescriptor>());
m_data.append(serializer.deserialize<Value>());
}
}
void Tuple::serialize(Serializer& serializer) const
{
VERIFY(m_descriptor->size() == m_data.size());
dbgln_if(SQL_DEBUG, "Serializing tuple with block_index {}", block_index());
serializer.serialize<u32>(block_index());
serializer.serialize<u32>(m_descriptor->size());
for (auto ix = 0u; ix < m_descriptor->size(); ix++) {
serializer.serialize<TupleElementDescriptor>((*m_descriptor)[ix]);
serializer.serialize<Value>(m_data[ix]);
}
}
Tuple::Tuple(Tuple const& other)
: m_descriptor(other.m_descriptor)
, m_data()
{
copy_from(other);
}
Tuple& Tuple::operator=(Tuple const& other)
{
if (this != &other)
copy_from(other);
return *this;
}
Optional<size_t> Tuple::index_of(StringView name) const
{
for (auto ix = 0u; ix < m_descriptor->size(); ix++) {
auto& part = (*m_descriptor)[ix];
if (part.name == name)
return ix;
}
return {};
}
Value const& Tuple::operator[](ByteString const& name) const
{
auto index = index_of(name);
VERIFY(index.has_value());
return (*this)[index.value()];
}
Value& Tuple::operator[](ByteString const& name)
{
auto index = index_of(name);
VERIFY(index.has_value());
return (*this)[index.value()];
}
void Tuple::append(Value const& value)
{
VERIFY(descriptor()->size() >= size());
if (descriptor()->size() == size())
descriptor()->append(value.descriptor());
m_data.append(value);
}
Tuple& Tuple::operator+=(Value const& value)
{
append(value);
return *this;
}
void Tuple::extend(Tuple const& other)
{
VERIFY((descriptor()->size() == size()) || (descriptor()->size() >= size() + other.size()));
if (descriptor()->size() == size())
descriptor()->extend(other.descriptor());
m_data.extend(other.m_data);
}
size_t Tuple::length() const
{
size_t len = 2 * sizeof(u32);
for (auto ix = 0u; ix < m_descriptor->size(); ix++) {
auto& descriptor = (*m_descriptor)[ix];
auto& value = m_data[ix];
len += descriptor.length();
len += value.length();
}
return len;
}
ByteString Tuple::to_byte_string() const
{
StringBuilder builder;
for (auto& part : m_data) {
if (!builder.is_empty())
builder.append('|');
builder.append(part.to_byte_string());
}
if (block_index() != 0)
builder.appendff(":{}", block_index());
return builder.to_byte_string();
}
void Tuple::copy_from(Tuple const& other)
{
if (*m_descriptor != *other.m_descriptor) {
m_descriptor->clear();
for (TupleElementDescriptor const& part : *other.m_descriptor)
m_descriptor->append(part);
}
m_data.clear();
for (auto& part : other.m_data)
m_data.append(part);
m_block_index = other.block_index();
}
int Tuple::compare(Tuple const& other) const
{
auto num_values = min(m_data.size(), other.m_data.size());
VERIFY(num_values > 0);
for (auto ix = 0u; ix < num_values; ix++) {
auto ret = m_data[ix].compare(other.m_data[ix]);
if (ret != 0) {
if ((ix < m_descriptor->size()) && (*m_descriptor)[ix].order == Order::Descending)
ret = -ret;
return ret;
}
}
return 0;
}
int Tuple::match(Tuple const& other) const
{
auto other_index = 0u;
for (auto const& part : *other.descriptor()) {
auto const& other_value = other[other_index];
if (other_value.is_null())
return 0;
auto my_index = index_of(part.name);
if (!my_index.has_value())
return -1;
auto ret = m_data[my_index.value()].compare(other_value);
if (ret != 0)
return ((*m_descriptor)[my_index.value()].order == Order::Descending) ? -ret : ret;
other_index++;
}
return 0;
}
u32 Tuple::hash() const
{
u32 ret = 0u;
for (auto& value : m_data) {
// This is an extension of the pair_int_hash function from AK/HashFunctions.h:
if (!ret)
ret = value.hash();
else
ret = int_hash((ret * 209) ^ (value.hash() * 413));
}
return ret;
}
}

View file

@ -1,85 +0,0 @@
/*
* Copyright (c) 2021, Jan de Visser <jan@de-visser.net>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Vector.h>
#include <LibSQL/Forward.h>
#include <LibSQL/TupleDescriptor.h>
#include <LibSQL/Value.h>
namespace SQL {
/**
* A Tuple is an element of a random-access data structure persisted in a Heap.
* Tuple objects stored in such a structure have a definition controlling the
* number of parts or columns the tuple has, the types of the parts, and the
* sort order of these parts. Besides having an optional definition, a Tuple
* consists of one Value object per part. In addition, tuples have a u32 pointer
* member which points to a Heap location.
*
* Tuple is a base class; concrete subclasses are Key, which implements the
* elements of an index, and Row, which implements the rows in a table.
*/
class Tuple {
public:
Tuple();
explicit Tuple(NonnullRefPtr<TupleDescriptor> const&, Block::Index = 0);
Tuple(NonnullRefPtr<TupleDescriptor> const&, Serializer&);
Tuple(Tuple const&);
virtual ~Tuple() = default;
Tuple& operator=(Tuple const&);
[[nodiscard]] ByteString to_byte_string() const;
explicit operator ByteString() const { return to_byte_string(); }
bool operator<(Tuple const& other) const { return compare(other) < 0; }
bool operator<=(Tuple const& other) const { return compare(other) <= 0; }
bool operator==(Tuple const& other) const { return compare(other) == 0; }
bool operator!=(Tuple const& other) const { return compare(other) != 0; }
bool operator>(Tuple const& other) const { return compare(other) > 0; }
bool operator>=(Tuple const& other) const { return compare(other) >= 0; }
[[nodiscard]] bool is_null() const { return m_data.is_empty(); }
[[nodiscard]] bool has(ByteString const& name) const { return index_of(name).has_value(); }
Value const& operator[](size_t ix) const { return m_data[ix]; }
Value& operator[](size_t ix) { return m_data[ix]; }
Value const& operator[](ByteString const& name) const;
Value& operator[](ByteString const& name);
void append(Value const&);
Tuple& operator+=(Value const&);
void extend(Tuple const&);
[[nodiscard]] Block::Index block_index() const { return m_block_index; }
void set_block_index(Block::Index index) { m_block_index = index; }
[[nodiscard]] size_t size() const { return m_data.size(); }
[[nodiscard]] virtual size_t length() const;
void clear() { m_data.clear(); }
[[nodiscard]] NonnullRefPtr<TupleDescriptor> descriptor() const { return m_descriptor; }
[[nodiscard]] int compare(Tuple const&) const;
[[nodiscard]] int match(Tuple const&) const;
[[nodiscard]] u32 hash() const;
[[nodiscard]] Vector<Value> take_data() { return move(m_data); }
protected:
[[nodiscard]] Optional<size_t> index_of(StringView) const;
void copy_from(Tuple const&);
virtual void serialize(Serializer&) const;
virtual void deserialize(Serializer&);
private:
NonnullRefPtr<TupleDescriptor> m_descriptor;
Vector<Value> m_data;
Block::Index m_block_index { 0 };
friend Serializer;
};
}

View file

@ -1,104 +0,0 @@
/*
* Copyright (c) 2021, Jan de Visser <jan@de-visser.net>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Vector.h>
#include <LibSQL/Serializer.h>
#include <LibSQL/Type.h>
namespace SQL {
struct TupleElementDescriptor {
ByteString schema { "" };
ByteString table { "" };
ByteString name { "" };
SQLType type { SQLType::Text };
Order order { Order::Ascending };
bool operator==(TupleElementDescriptor const&) const = default;
void serialize(Serializer& serializer) const
{
serializer.serialize(name);
serializer.serialize<u8>((u8)type);
serializer.serialize<u8>((u8)order);
}
void deserialize(Serializer& serializer)
{
name = serializer.deserialize<ByteString>();
type = (SQLType)serializer.deserialize<u8>();
order = (Order)serializer.deserialize<u8>();
}
size_t length() const
{
return sizeof(u32) + name.length() + 2 * sizeof(u8);
}
ByteString to_byte_string() const
{
return ByteString::formatted(" name: {} type: {} order: {}", name, SQLType_name(type), Order_name(order));
}
};
class TupleDescriptor
: public Vector<TupleElementDescriptor>
, public RefCounted<TupleDescriptor> {
public:
TupleDescriptor() = default;
~TupleDescriptor() = default;
[[nodiscard]] int compare_ignoring_names(TupleDescriptor const& other) const
{
if (size() != other.size())
return (int)size() - (int)other.size();
for (auto ix = 0u; ix < size(); ++ix) {
auto elem = (*this)[ix];
auto other_elem = other[ix];
if ((elem.type != other_elem.type) || (elem.order != other_elem.order)) {
return 1;
}
}
return 0;
}
void serialize(Serializer& serializer) const
{
serializer.serialize<u32>(size());
for (auto& element : *this) {
serializer.serialize<TupleElementDescriptor>(element);
}
}
void deserialize(Serializer& serializer)
{
auto sz = serializer.deserialize<u32>();
for (auto ix = 0u; ix < sz; ix++) {
append(serializer.deserialize<TupleElementDescriptor>());
}
}
size_t length() const
{
size_t len = sizeof(u32);
for (auto& element : *this)
len += element.length();
return len;
}
ByteString to_byte_string() const
{
Vector<ByteString> elements;
for (auto& element : *this)
elements.append(element.to_byte_string());
return ByteString::formatted("[\n{}\n]", ByteString::join('\n', elements));
}
using Vector<TupleElementDescriptor>::operator==;
};
}

View file

@ -1,80 +0,0 @@
/*
* Copyright (c) 2021, Jan de Visser <jan@de-visser.net>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/HashMap.h>
#include <AK/StringView.h>
namespace SQL {
// Adding to this list is fine, but changing the order of any value here will result in LibSQL
// becoming unable to read existing .db files. If the order must absolutely be changed, be sure
// to bump Heap::VERSION.
#define ENUMERATE_SQL_TYPES(S) \
S("null", Null) \
S("text", Text) \
S("int", Integer) \
S("float", Float) \
S("bool", Boolean) \
S("tuple", Tuple)
enum class SQLType {
#undef __ENUMERATE_SQL_TYPE
#define __ENUMERATE_SQL_TYPE(name, type) type,
ENUMERATE_SQL_TYPES(__ENUMERATE_SQL_TYPE)
#undef __ENUMERATE_SQL_TYPE
};
constexpr StringView SQLType_name(SQLType t)
{
switch (t) {
#undef __ENUMERATE_SQL_TYPE
#define __ENUMERATE_SQL_TYPE(name, type) \
case SQLType::type: \
return name##sv;
ENUMERATE_SQL_TYPES(__ENUMERATE_SQL_TYPE)
#undef __ENUMERATE_SQL_TYPE
default:
VERIFY_NOT_REACHED();
}
}
#define ENUMERATE_ORDERS(S) \
S(Ascending) \
S(Descending)
enum class Order {
#undef __ENUMERATE_ORDER
#define __ENUMERATE_ORDER(order) order,
ENUMERATE_ORDERS(__ENUMERATE_ORDER)
#undef __ENUMERATE_ORDER
};
constexpr StringView Order_name(Order order)
{
switch (order) {
#undef __ENUMERATE_ORDER
#define __ENUMERATE_ORDER(order) \
case Order::order: \
return #order##sv;
ENUMERATE_ORDERS(__ENUMERATE_ORDER)
#undef __ENUMERATE_ORDER
default:
VERIFY_NOT_REACHED();
}
}
enum class Nulls {
First,
Last,
};
using ConnectionID = u64;
using StatementID = u64;
using ExecutionID = u64;
}

View file

@ -1,942 +0,0 @@
/*
* Copyright (c) 2021, Jan de Visser <jan@de-visser.net>
* Copyright (c) 2022-2024, Tim Flynn <trflynn89@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/NumericLimits.h>
#include <LibIPC/Decoder.h>
#include <LibIPC/Encoder.h>
#include <LibSQL/AST/AST.h>
#include <LibSQL/Serializer.h>
#include <LibSQL/TupleDescriptor.h>
#include <LibSQL/Value.h>
namespace SQL {
// We use the upper 4 bits of the encoded type to store extra information about the type. This
// includes if the value is null, and the encoded size of any integer type. Of course, this encoding
// only works if the SQL type itself fits in the lower 4 bits.
enum class SQLTypeWithCount {
#undef __ENUMERATE_SQL_TYPE
#define __ENUMERATE_SQL_TYPE(name, type) type,
ENUMERATE_SQL_TYPES(__ENUMERATE_SQL_TYPE)
#undef __ENUMERATE_SQL_TYPE
Count,
};
static_assert(to_underlying(SQLTypeWithCount::Count) <= 0x0f, "Too many SQL types for current encoding");
// Adding to this list is fine, but changing the order of any value here will result in LibSQL
// becoming unable to read existing .db files. If the order must absolutely be changed, be sure
// to bump Heap::VERSION.
enum class TypeData : u8 {
Null = 1 << 4,
Int8 = 2 << 4,
Int16 = 3 << 4,
Int32 = 4 << 4,
Int64 = 5 << 4,
Uint8 = 6 << 4,
Uint16 = 7 << 4,
Uint32 = 8 << 4,
Uint64 = 9 << 4,
};
template<typename Callback>
static decltype(auto) downsize_integer(Integer auto value, Callback&& callback)
{
if constexpr (IsSigned<decltype(value)>) {
if (AK::is_within_range<i8>(value))
return callback(static_cast<i8>(value), TypeData::Int8);
if (AK::is_within_range<i16>(value))
return callback(static_cast<i16>(value), TypeData::Int16);
if (AK::is_within_range<i32>(value))
return callback(static_cast<i32>(value), TypeData::Int32);
return callback(value, TypeData::Int64);
} else {
if (AK::is_within_range<u8>(value))
return callback(static_cast<i8>(value), TypeData::Uint8);
if (AK::is_within_range<u16>(value))
return callback(static_cast<i16>(value), TypeData::Uint16);
if (AK::is_within_range<u32>(value))
return callback(static_cast<i32>(value), TypeData::Uint32);
return callback(value, TypeData::Uint64);
}
}
template<typename Callback>
static decltype(auto) downsize_integer(Value const& value, Callback&& callback)
{
VERIFY(value.is_int());
if (value.value().has<i64>())
return downsize_integer(value.value().get<i64>(), forward<Callback>(callback));
return downsize_integer(value.value().get<u64>(), forward<Callback>(callback));
}
template<typename Callback>
static ResultOr<Value> perform_integer_operation(Value const& lhs, Value const& rhs, Callback&& callback)
{
VERIFY(lhs.is_int());
VERIFY(rhs.is_int());
if (lhs.value().has<i64>()) {
if (auto rhs_value = rhs.to_int<i64>(); rhs_value.has_value())
return callback(lhs.to_int<i64>().value(), rhs_value.value());
} else {
if (auto rhs_value = rhs.to_int<u64>(); rhs_value.has_value())
return callback(lhs.to_int<u64>().value(), rhs_value.value());
}
return Result { SQLCommand::Unknown, SQLErrorCode::IntegerOverflow };
}
Value::Value(SQLType type)
: m_type(type)
{
}
Value::Value(String value)
: Value(value.to_byte_string())
{
}
Value::Value(ByteString value)
: m_type(SQLType::Text)
, m_value(move(value))
{
}
Value::Value(double value)
{
if (trunc(value) == value) {
if (AK::is_within_range<i64>(value)) {
m_type = SQLType::Integer;
m_value = static_cast<i64>(value);
return;
}
if (AK::is_within_range<u64>(value)) {
m_type = SQLType::Integer;
m_value = static_cast<u64>(value);
return;
}
}
m_type = SQLType::Float;
m_value = value;
}
Value::Value(NonnullRefPtr<TupleDescriptor> descriptor, Vector<Value> values)
: m_type(SQLType::Tuple)
, m_value(TupleValue { move(descriptor), move(values) })
{
}
Value::Value(Value const& other)
: m_type(other.m_type)
, m_value(other.m_value)
{
}
Value::Value(Value&& other)
: m_type(other.m_type)
, m_value(move(other.m_value))
{
}
Value::Value(Duration duration)
: m_type(SQLType::Integer)
, m_value(duration.to_milliseconds())
{
}
Value::Value(UnixDateTime time)
: Value(time.offset_to_epoch())
{
}
Value::~Value() = default;
ResultOr<Value> Value::create_tuple(NonnullRefPtr<TupleDescriptor> descriptor)
{
Vector<Value> values;
TRY(values.try_resize(descriptor->size()));
for (size_t i = 0; i < descriptor->size(); ++i)
values[i].m_type = descriptor->at(i).type;
return Value { move(descriptor), move(values) };
}
ResultOr<Value> Value::create_tuple(Vector<Value> values)
{
auto descriptor = TRY(infer_tuple_descriptor(values));
return Value { move(descriptor), move(values) };
}
SQLType Value::type() const
{
return m_type;
}
StringView Value::type_name() const
{
switch (type()) {
#undef __ENUMERATE_SQL_TYPE
#define __ENUMERATE_SQL_TYPE(name, type) \
case SQLType::type: \
return name##sv;
ENUMERATE_SQL_TYPES(__ENUMERATE_SQL_TYPE)
#undef __ENUMERATE_SQL_TYPE
default:
VERIFY_NOT_REACHED();
}
}
bool Value::is_type_compatible_with(SQLType other_type) const
{
switch (type()) {
case SQLType::Null:
return false;
case SQLType::Integer:
case SQLType::Float:
return other_type == SQLType::Integer || other_type == SQLType::Float;
default:
break;
}
return type() == other_type;
}
bool Value::is_null() const
{
return !m_value.has_value();
}
bool Value::is_int() const
{
return m_value.has_value() && (m_value->has<i64>() || m_value->has<u64>());
}
ErrorOr<String> Value::to_string() const
{
if (is_null())
return String::from_utf8("(null)"sv);
return m_value->visit(
[](ByteString const& value) { return String::from_byte_string(value); },
[](Integer auto value) { return String::number(value); },
[](double value) { return String::number(value); },
[](bool value) { return String::from_utf8(value ? "true"sv : "false"sv); },
[](TupleValue const& value) {
StringBuilder builder;
builder.append('(');
builder.join(',', value.values);
builder.append(')');
return builder.to_string();
});
}
ByteString Value::to_byte_string() const
{
if (is_null())
return "(null)"sv;
return m_value->visit(
[](ByteString const& value) -> ByteString { return value; },
[](Integer auto value) -> ByteString { return ByteString::number(value); },
[](double value) -> ByteString { return ByteString::number(value); },
[](bool value) -> ByteString { return value ? "true"sv : "false"sv; },
[](TupleValue const& value) -> ByteString {
StringBuilder builder;
builder.append('(');
builder.join(',', value.values);
builder.append(')');
return builder.to_byte_string();
});
}
Optional<double> Value::to_double() const
{
if (is_null())
return {};
return m_value->visit(
[](ByteString const& value) -> Optional<double> { return value.to_number<double>(); },
[](Integer auto value) -> Optional<double> { return static_cast<double>(value); },
[](double value) -> Optional<double> { return value; },
[](bool value) -> Optional<double> { return static_cast<double>(value); },
[](TupleValue const&) -> Optional<double> { return {}; });
}
Optional<bool> Value::to_bool() const
{
if (is_null())
return {};
return m_value->visit(
[](ByteString const& value) -> Optional<bool> {
if (value.equals_ignoring_ascii_case("true"sv) || value.equals_ignoring_ascii_case("t"sv))
return true;
if (value.equals_ignoring_ascii_case("false"sv) || value.equals_ignoring_ascii_case("f"sv))
return false;
return {};
},
[](Integer auto value) -> Optional<bool> { return static_cast<bool>(value); },
[](double value) -> Optional<bool> { return fabs(value) > NumericLimits<double>::epsilon(); },
[](bool value) -> Optional<bool> { return value; },
[](TupleValue const& value) -> Optional<bool> {
for (auto const& element : value.values) {
auto as_bool = element.to_bool();
if (!as_bool.has_value())
return {};
if (!as_bool.value())
return false;
}
return true;
});
}
Optional<UnixDateTime> Value::to_unix_date_time() const
{
auto time = to_int<i64>();
if (!time.has_value())
return {};
return UnixDateTime::from_milliseconds_since_epoch(*time);
}
Optional<Vector<Value>> Value::to_vector() const
{
if (is_null() || (type() != SQLType::Tuple))
return {};
auto const& tuple = m_value->get<TupleValue>();
return tuple.values;
}
Value& Value::operator=(Value value)
{
m_type = value.m_type;
m_value = move(value.m_value);
return *this;
}
Value& Value::operator=(ByteString value)
{
m_type = SQLType::Text;
m_value = move(value);
return *this;
}
Value& Value::operator=(double value)
{
m_type = SQLType::Float;
m_value = value;
return *this;
}
ResultOr<void> Value::assign_tuple(NonnullRefPtr<TupleDescriptor> descriptor)
{
Vector<Value> values;
TRY(values.try_resize(descriptor->size()));
for (size_t i = 0; i < descriptor->size(); ++i)
values[i].m_type = descriptor->at(i).type;
m_type = SQLType::Tuple;
m_value = TupleValue { move(descriptor), move(values) };
return {};
}
ResultOr<void> Value::assign_tuple(Vector<Value> values)
{
if (is_null() || (type() != SQLType::Tuple)) {
auto descriptor = TRY(infer_tuple_descriptor(values));
m_type = SQLType::Tuple;
m_value = TupleValue { move(descriptor), move(values) };
return {};
}
auto& tuple = m_value->get<TupleValue>();
if (values.size() > tuple.descriptor->size())
return Result { SQLCommand::Unknown, SQLErrorCode::InvalidNumberOfValues };
for (size_t i = 0; i < values.size(); ++i) {
if (values[i].type() != tuple.descriptor->at(i).type)
return Result { SQLCommand::Unknown, SQLErrorCode::InvalidType, SQLType_name(values[i].type()) };
}
if (values.size() < tuple.descriptor->size()) {
size_t original_size = values.size();
MUST(values.try_resize(tuple.descriptor->size()));
for (size_t i = original_size; i < values.size(); ++i)
values[i].m_type = tuple.descriptor->at(i).type;
}
m_value = TupleValue { move(tuple.descriptor), move(values) };
return {};
}
size_t Value::length() const
{
if (is_null())
return 0;
// FIXME: This seems to be more of an encoded byte size rather than a length.
return m_value->visit(
[](ByteString const& value) -> size_t { return sizeof(u32) + value.length(); },
[](Integer auto value) -> size_t {
return downsize_integer(value, [](auto integer, auto) {
return sizeof(integer);
});
},
[](double value) -> size_t { return sizeof(value); },
[](bool value) -> size_t { return sizeof(value); },
[](TupleValue const& value) -> size_t {
auto size = value.descriptor->length() + sizeof(u32);
for (auto const& element : value.values)
size += element.length();
return size;
});
}
u32 Value::hash() const
{
if (is_null())
return 0;
return m_value->visit(
[](ByteString const& value) -> u32 { return value.hash(); },
[](Integer auto value) -> u32 {
return downsize_integer(value, [](auto integer, auto) {
if constexpr (sizeof(decltype(integer)) == 8)
return u64_hash(integer);
else
return int_hash(integer);
});
},
[](double) -> u32 { VERIFY_NOT_REACHED(); },
[](bool value) -> u32 { return int_hash(value); },
[](TupleValue const& value) -> u32 {
u32 hash = 0;
for (auto const& element : value.values) {
if (hash == 0)
hash = element.hash();
else
hash = pair_int_hash(hash, element.hash());
}
return hash;
});
}
int Value::compare(Value const& other) const
{
if (is_null())
return -1;
if (other.is_null())
return 1;
return m_value->visit(
[&](ByteString const& value) -> int { return value.view().compare(other.to_byte_string()); },
[&](Integer auto value) -> int {
auto casted = other.to_int<IntegerType<decltype(value)>>();
if (!casted.has_value())
return 1;
if (value == *casted)
return 0;
return value < *casted ? -1 : 1;
},
[&](double value) -> int {
auto casted = other.to_double();
if (!casted.has_value())
return 1;
auto diff = value - *casted;
if (fabs(diff) < NumericLimits<double>::epsilon())
return 0;
return diff < 0 ? -1 : 1;
},
[&](bool value) -> int {
auto casted = other.to_bool();
if (!casted.has_value())
return 1;
return value ^ *casted;
},
[&](TupleValue const& value) -> int {
if (other.is_null() || (other.type() != SQLType::Tuple)) {
if (value.values.size() == 1)
return value.values[0].compare(other);
return 1;
}
auto const& other_value = other.m_value->get<TupleValue>();
if (auto result = value.descriptor->compare_ignoring_names(*other_value.descriptor); result != 0)
return 1;
if (value.values.size() != other_value.values.size())
return value.values.size() < other_value.values.size() ? -1 : 1;
for (size_t i = 0; i < value.values.size(); ++i) {
auto result = value.values[i].compare(other_value.values[i]);
if (result == 0)
continue;
if (value.descriptor->at(i).order == Order::Descending)
result = -result;
return result;
}
return 0;
});
}
bool Value::operator==(Value const& value) const
{
return compare(value) == 0;
}
bool Value::operator==(StringView value) const
{
return to_byte_string() == value;
}
bool Value::operator==(double value) const
{
return to_double() == value;
}
bool Value::operator!=(Value const& value) const
{
return compare(value) != 0;
}
bool Value::operator<(Value const& value) const
{
return compare(value) < 0;
}
bool Value::operator<=(Value const& value) const
{
return compare(value) <= 0;
}
bool Value::operator>(Value const& value) const
{
return compare(value) > 0;
}
bool Value::operator>=(Value const& value) const
{
return compare(value) >= 0;
}
template<typename Operator>
static Result invalid_type_for_numeric_operator(Operator op)
{
if constexpr (IsSame<Operator, AST::BinaryOperator>)
return { SQLCommand::Unknown, SQLErrorCode::NumericOperatorTypeMismatch, BinaryOperator_name(op) };
else if constexpr (IsSame<Operator, AST::UnaryOperator>)
return { SQLCommand::Unknown, SQLErrorCode::NumericOperatorTypeMismatch, UnaryOperator_name(op) };
else
static_assert(DependentFalse<Operator>);
}
ResultOr<Value> Value::add(Value const& other) const
{
if (is_int() && other.is_int()) {
return perform_integer_operation(*this, other, [](auto lhs, auto rhs) -> ResultOr<Value> {
Checked result { lhs };
result.add(rhs);
if (result.has_overflow())
return Result { SQLCommand::Unknown, SQLErrorCode::IntegerOverflow };
return Value { result.value_unchecked() };
});
}
auto lhs = to_double();
auto rhs = other.to_double();
if (!lhs.has_value() || !rhs.has_value())
return invalid_type_for_numeric_operator(AST::BinaryOperator::Plus);
return Value { lhs.value() + rhs.value() };
}
ResultOr<Value> Value::subtract(Value const& other) const
{
if (is_int() && other.is_int()) {
return perform_integer_operation(*this, other, [](auto lhs, auto rhs) -> ResultOr<Value> {
Checked result { lhs };
result.sub(rhs);
if (result.has_overflow())
return Result { SQLCommand::Unknown, SQLErrorCode::IntegerOverflow };
return Value { result.value_unchecked() };
});
}
auto lhs = to_double();
auto rhs = other.to_double();
if (!lhs.has_value() || !rhs.has_value())
return invalid_type_for_numeric_operator(AST::BinaryOperator::Minus);
return Value { lhs.value() - rhs.value() };
}
ResultOr<Value> Value::multiply(Value const& other) const
{
if (is_int() && other.is_int()) {
return perform_integer_operation(*this, other, [](auto lhs, auto rhs) -> ResultOr<Value> {
Checked result { lhs };
result.mul(rhs);
if (result.has_overflow())
return Result { SQLCommand::Unknown, SQLErrorCode::IntegerOverflow };
return Value { result.value_unchecked() };
});
}
auto lhs = to_double();
auto rhs = other.to_double();
if (!lhs.has_value() || !rhs.has_value())
return invalid_type_for_numeric_operator(AST::BinaryOperator::Multiplication);
return Value { lhs.value() * rhs.value() };
}
ResultOr<Value> Value::divide(Value const& other) const
{
auto lhs = to_double();
auto rhs = other.to_double();
if (!lhs.has_value() || !rhs.has_value())
return invalid_type_for_numeric_operator(AST::BinaryOperator::Division);
if (rhs == 0.0)
return Result { SQLCommand::Unknown, SQLErrorCode::IntegerOverflow };
return Value { lhs.value() / rhs.value() };
}
ResultOr<Value> Value::modulo(Value const& other) const
{
if (!is_int() || !other.is_int())
return invalid_type_for_numeric_operator(AST::BinaryOperator::Modulo);
return perform_integer_operation(*this, other, [](auto lhs, auto rhs) -> ResultOr<Value> {
Checked result { lhs };
result.mod(rhs);
if (result.has_overflow())
return Result { SQLCommand::Unknown, SQLErrorCode::IntegerOverflow };
return Value { result.value_unchecked() };
});
}
ResultOr<Value> Value::negate() const
{
if (type() == SQLType::Integer) {
auto value = to_int<i64>();
if (!value.has_value())
return invalid_type_for_numeric_operator(AST::UnaryOperator::Minus);
return Value { value.value() * -1 };
}
if (type() == SQLType::Float)
return Value { -to_double().value() };
return invalid_type_for_numeric_operator(AST::UnaryOperator::Minus);
}
ResultOr<Value> Value::shift_left(Value const& other) const
{
if (!is_int() || !other.is_int())
return invalid_type_for_numeric_operator(AST::BinaryOperator::ShiftLeft);
return perform_integer_operation(*this, other, [](auto lhs, auto rhs) -> ResultOr<Value> {
using LHS = decltype(lhs);
using RHS = decltype(rhs);
static constexpr auto max_shift = static_cast<RHS>(sizeof(LHS) * 8);
if (rhs < 0 || rhs >= max_shift)
return Result { SQLCommand::Unknown, SQLErrorCode::IntegerOverflow };
return Value { lhs << rhs };
});
}
ResultOr<Value> Value::shift_right(Value const& other) const
{
if (!is_int() || !other.is_int())
return invalid_type_for_numeric_operator(AST::BinaryOperator::ShiftRight);
return perform_integer_operation(*this, other, [](auto lhs, auto rhs) -> ResultOr<Value> {
using LHS = decltype(lhs);
using RHS = decltype(rhs);
static constexpr auto max_shift = static_cast<RHS>(sizeof(LHS) * 8);
if (rhs < 0 || rhs >= max_shift)
return Result { SQLCommand::Unknown, SQLErrorCode::IntegerOverflow };
return Value { lhs >> rhs };
});
}
ResultOr<Value> Value::bitwise_or(Value const& other) const
{
if (!is_int() || !other.is_int())
return invalid_type_for_numeric_operator(AST::BinaryOperator::BitwiseOr);
return perform_integer_operation(*this, other, [](auto lhs, auto rhs) {
return Value { lhs | rhs };
});
}
ResultOr<Value> Value::bitwise_and(Value const& other) const
{
if (!is_int() || !other.is_int())
return invalid_type_for_numeric_operator(AST::BinaryOperator::BitwiseAnd);
return perform_integer_operation(*this, other, [](auto lhs, auto rhs) {
return Value { lhs & rhs };
});
}
ResultOr<Value> Value::bitwise_not() const
{
if (!is_int())
return invalid_type_for_numeric_operator(AST::UnaryOperator::BitwiseNot);
return downsize_integer(*this, [](auto value, auto) {
return Value { ~value };
});
}
static u8 encode_type_flags(Value const& value)
{
auto type_flags = to_underlying(value.type());
if (value.is_null()) {
type_flags |= to_underlying(TypeData::Null);
} else if (value.is_int()) {
downsize_integer(value, [&](auto, auto type_data) {
type_flags |= to_underlying(type_data);
});
}
return type_flags;
}
void Value::serialize(Serializer& serializer) const
{
auto type_flags = encode_type_flags(*this);
serializer.serialize<u8>(type_flags);
if (is_null())
return;
if (is_int()) {
downsize_integer(*this, [&](auto integer, auto) {
serializer.serialize(integer);
});
return;
}
m_value->visit(
[&](TupleValue const& value) {
serializer.serialize<TupleDescriptor>(*value.descriptor);
serializer.serialize(static_cast<u32>(value.values.size()));
for (auto const& element : value.values)
serializer.serialize<Value>(element);
},
[&](auto const& value) { serializer.serialize(value); });
}
void Value::deserialize(Serializer& serializer)
{
auto type_flags = serializer.deserialize<u8>();
auto type_data = static_cast<TypeData>(type_flags & 0xf0);
m_type = static_cast<SQLType>(type_flags & 0x0f);
if (type_data == TypeData::Null)
return;
switch (m_type) {
case SQLType::Null:
VERIFY_NOT_REACHED();
case SQLType::Text:
m_value = serializer.deserialize<ByteString>();
break;
case SQLType::Integer:
switch (type_data) {
case TypeData::Int8:
m_value = static_cast<i64>(serializer.deserialize<i8>(0));
break;
case TypeData::Int16:
m_value = static_cast<i64>(serializer.deserialize<i16>(0));
break;
case TypeData::Int32:
m_value = static_cast<i64>(serializer.deserialize<i32>(0));
break;
case TypeData::Int64:
m_value = static_cast<i64>(serializer.deserialize<i64>(0));
break;
case TypeData::Uint8:
m_value = static_cast<u64>(serializer.deserialize<u8>(0));
break;
case TypeData::Uint16:
m_value = static_cast<u64>(serializer.deserialize<u16>(0));
break;
case TypeData::Uint32:
m_value = static_cast<u64>(serializer.deserialize<u32>(0));
break;
case TypeData::Uint64:
m_value = static_cast<u64>(serializer.deserialize<u64>(0));
break;
default:
VERIFY_NOT_REACHED();
}
break;
case SQLType::Float:
m_value = serializer.deserialize<double>(0.0);
break;
case SQLType::Boolean:
m_value = serializer.deserialize<bool>(false);
break;
case SQLType::Tuple: {
auto descriptor = serializer.adopt_and_deserialize<TupleDescriptor>();
auto size = serializer.deserialize<u32>();
Vector<Value> values;
values.ensure_capacity(size);
for (size_t i = 0; i < size; ++i)
values.unchecked_append(serializer.deserialize<Value>());
m_value = TupleValue { move(descriptor), move(values) };
break;
}
}
}
TupleElementDescriptor Value::descriptor() const
{
return { "", "", "", type(), Order::Ascending };
}
ResultOr<NonnullRefPtr<TupleDescriptor>> Value::infer_tuple_descriptor(Vector<Value> const& values)
{
auto descriptor = TRY(adopt_nonnull_ref_or_enomem(new (nothrow) SQL::TupleDescriptor));
TRY(descriptor->try_ensure_capacity(values.size()));
for (auto const& element : values)
descriptor->unchecked_append({ ""sv, ""sv, ""sv, element.type(), Order::Ascending });
return descriptor;
}
}
template<>
ErrorOr<void> IPC::encode(Encoder& encoder, SQL::Value const& value)
{
auto type_flags = encode_type_flags(value);
TRY(encoder.encode(type_flags));
if (value.is_null())
return {};
switch (value.type()) {
case SQL::SQLType::Null:
return {};
case SQL::SQLType::Text:
return encoder.encode(value.to_byte_string());
case SQL::SQLType::Integer:
return SQL::downsize_integer(value, [&](auto integer, auto) {
return encoder.encode(integer);
});
case SQL::SQLType::Float:
return encoder.encode(value.to_double().value());
case SQL::SQLType::Boolean:
return encoder.encode(value.to_bool().value());
case SQL::SQLType::Tuple:
return encoder.encode(value.to_vector().value());
}
VERIFY_NOT_REACHED();
}
template<>
ErrorOr<SQL::Value> IPC::decode(Decoder& decoder)
{
auto type_flags = TRY(decoder.decode<u8>());
auto type_data = static_cast<SQL::TypeData>(type_flags & 0xf0);
auto type = static_cast<SQL::SQLType>(type_flags & 0x0f);
if (type_data == SQL::TypeData::Null)
return SQL::Value { type };
switch (type) {
case SQL::SQLType::Null:
return SQL::Value {};
case SQL::SQLType::Text:
return SQL::Value { TRY(decoder.decode<ByteString>()) };
case SQL::SQLType::Integer:
switch (type_data) {
case SQL::TypeData::Int8:
return SQL::Value { TRY(decoder.decode<i8>()) };
case SQL::TypeData::Int16:
return SQL::Value { TRY(decoder.decode<i16>()) };
case SQL::TypeData::Int32:
return SQL::Value { TRY(decoder.decode<i32>()) };
case SQL::TypeData::Int64:
return SQL::Value { TRY(decoder.decode<i64>()) };
case SQL::TypeData::Uint8:
return SQL::Value { TRY(decoder.decode<u8>()) };
case SQL::TypeData::Uint16:
return SQL::Value { TRY(decoder.decode<u16>()) };
case SQL::TypeData::Uint32:
return SQL::Value { TRY(decoder.decode<u32>()) };
case SQL::TypeData::Uint64:
return SQL::Value { TRY(decoder.decode<u64>()) };
default:
break;
}
break;
case SQL::SQLType::Float:
return SQL::Value { TRY(decoder.decode<double>()) };
case SQL::SQLType::Boolean:
return SQL::Value { TRY(decoder.decode<bool>()) };
case SQL::SQLType::Tuple: {
auto tuple = TRY(decoder.decode<Vector<SQL::Value>>());
auto value = SQL::Value::create_tuple(move(tuple));
if (value.is_error())
return Error::from_errno(to_underlying(value.error().error()));
return value.release_value();
}
}
VERIFY_NOT_REACHED();
}

View file

@ -1,202 +0,0 @@
/*
* Copyright (c) 2021, Jan de Visser <jan@de-visser.net>
* Copyright (c) 2022-2024, Tim Flynn <trflynn89@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/ByteString.h>
#include <AK/Checked.h>
#include <AK/Format.h>
#include <AK/Optional.h>
#include <AK/String.h>
#include <AK/StringView.h>
#include <AK/Variant.h>
#include <AK/Vector.h>
#include <LibIPC/Forward.h>
#include <LibSQL/Forward.h>
#include <LibSQL/Result.h>
#include <LibSQL/Type.h>
#include <math.h>
namespace SQL {
template<typename T>
concept Boolean = SameAs<RemoveCVReference<T>, bool>;
template<typename T>
concept Integer = (Integral<T> && !Boolean<T>);
/**
* A `Value` is an atomic piece of SQL data`. A `Value` has a basic type
* (Text/String, Integer, Float, etc). Richer types are implemented in higher
* level layers, but the resulting data is stored in these `Value` objects.
*/
class Value {
template<Integer T>
using IntegerType = Conditional<IsSigned<T>, i64, u64>;
public:
explicit Value(SQLType sql_type = SQLType::Null);
explicit Value(String);
explicit Value(ByteString);
explicit Value(double);
Value(Value const&);
Value(Value&&);
~Value();
explicit Value(Integer auto value)
: m_type(SQLType::Integer)
, m_value(static_cast<IntegerType<decltype(value)>>(value))
{
}
explicit Value(Boolean auto value)
: m_type(SQLType::Boolean)
, m_value(value)
{
}
explicit Value(UnixDateTime);
explicit Value(Duration);
static ResultOr<Value> create_tuple(NonnullRefPtr<TupleDescriptor>);
static ResultOr<Value> create_tuple(Vector<Value>);
[[nodiscard]] SQLType type() const;
[[nodiscard]] StringView type_name() const;
[[nodiscard]] bool is_type_compatible_with(SQLType) const;
[[nodiscard]] bool is_null() const;
[[nodiscard]] bool is_int() const;
[[nodiscard]] auto const& value() const
{
return *m_value;
}
[[nodiscard]] ErrorOr<String> to_string() const;
[[nodiscard]] ByteString to_byte_string() const;
[[nodiscard]] Optional<double> to_double() const;
[[nodiscard]] Optional<bool> to_bool() const;
[[nodiscard]] Optional<UnixDateTime> to_unix_date_time() const;
[[nodiscard]] Optional<Vector<Value>> to_vector() const;
template<Integer T>
[[nodiscard]] Optional<T> to_int() const
{
if (is_null())
return {};
return m_value->visit(
[](ByteString const& value) -> Optional<T> {
return value.to_number<T>();
},
[](Integer auto value) -> Optional<T> {
if (!AK::is_within_range<T>(value))
return {};
return static_cast<T>(value);
},
[](double value) -> Optional<T> {
if (!AK::is_within_range<T>(value))
return {};
return static_cast<T>(round(value));
},
[](bool value) -> Optional<T> { return static_cast<T>(value); },
[](TupleValue const&) -> Optional<T> { return {}; });
}
Value& operator=(Value);
Value& operator=(ByteString);
Value& operator=(double);
Value& operator=(Integer auto value)
{
m_type = SQLType::Integer;
m_value = static_cast<IntegerType<decltype(value)>>(value);
return *this;
}
ResultOr<void> assign_tuple(NonnullRefPtr<TupleDescriptor>);
ResultOr<void> assign_tuple(Vector<Value>);
Value& operator=(Boolean auto value)
{
m_type = SQLType::Boolean;
m_value = value;
return *this;
}
[[nodiscard]] size_t length() const;
[[nodiscard]] u32 hash() const;
void serialize(Serializer&) const;
void deserialize(Serializer&);
[[nodiscard]] int compare(Value const&) const;
bool operator==(Value const&) const;
bool operator==(StringView) const;
bool operator==(double) const;
template<Integer T>
bool operator==(T value)
{
return to_int<T>() == value;
}
bool operator!=(Value const&) const;
bool operator<(Value const&) const;
bool operator<=(Value const&) const;
bool operator>(Value const&) const;
bool operator>=(Value const&) const;
ResultOr<Value> add(Value const&) const;
ResultOr<Value> subtract(Value const&) const;
ResultOr<Value> multiply(Value const&) const;
ResultOr<Value> divide(Value const&) const;
ResultOr<Value> modulo(Value const&) const;
ResultOr<Value> negate() const;
ResultOr<Value> shift_left(Value const&) const;
ResultOr<Value> shift_right(Value const&) const;
ResultOr<Value> bitwise_or(Value const&) const;
ResultOr<Value> bitwise_and(Value const&) const;
ResultOr<Value> bitwise_not() const;
[[nodiscard]] TupleElementDescriptor descriptor() const;
private:
friend Serializer;
struct TupleValue {
NonnullRefPtr<TupleDescriptor> descriptor;
Vector<Value> values;
};
using ValueType = Variant<ByteString, i64, u64, double, bool, TupleValue>;
static ResultOr<NonnullRefPtr<TupleDescriptor>> infer_tuple_descriptor(Vector<Value> const& values);
Value(NonnullRefPtr<TupleDescriptor> descriptor, Vector<Value> values);
SQLType m_type { SQLType::Null };
Optional<ValueType> m_value;
};
}
template<>
struct AK::Formatter<SQL::Value> : Formatter<StringView> {
ErrorOr<void> format(FormatBuilder& builder, SQL::Value const& value)
{
return Formatter<StringView>::format(builder, value.to_byte_string());
}
};
namespace IPC {
template<>
ErrorOr<void> encode(Encoder&, SQL::Value const&);
template<>
ErrorOr<SQL::Value> decode(Decoder&);
}

View file

@ -37,8 +37,6 @@ StringView language_to_string(Language language)
return "Plain Text"sv;
case Language::Shell:
return "Shell"sv;
case Language::SQL:
return "SQL"sv;
}
VERIFY_NOT_REACHED();
}
@ -70,8 +68,6 @@ StringView common_language_extension(Language language)
return "txt"sv;
case Language::Shell:
return "sh"sv;
case Language::SQL:
return "sql"sv;
}
VERIFY_NOT_REACHED();
}
@ -100,8 +96,6 @@ Optional<Language> language_from_name(StringView name)
return Language::Markdown;
if (name.equals_ignoring_ascii_case("PlainText"sv))
return Language::PlainText;
if (name.equals_ignoring_ascii_case("SQL"sv))
return Language::SQL;
if (name.equals_ignoring_ascii_case("Shell"sv))
return Language::Shell;
@ -135,8 +129,6 @@ Optional<Language> language_from_filename(LexicalPath const& file)
return Language::Markdown;
if (extension.is_one_of("sh"sv, "bash"sv))
return Language::Shell;
if (extension == "sql"sv)
return Language::SQL;
// Check "txt" after the CMake related files that use "txt" as their extension.
if (extension == "txt"sv)

View file

@ -24,7 +24,6 @@ enum class Language {
Markdown,
PlainText,
Shell,
SQL,
};
StringView language_to_string(Language);

View file

@ -21,7 +21,6 @@ enum class ProcessType {
Chrome,
WebContent,
WebWorker,
SQLServer,
RequestServer,
ImageDecoder,
};

View file

@ -22,8 +22,6 @@ ProcessType process_type_from_name(StringView name)
return ProcessType::WebContent;
if (name == "WebWorker"sv)
return ProcessType::WebWorker;
if (name == "SQLServer"sv)
return ProcessType::SQLServer;
if (name == "RequestServer"sv)
return ProcessType::RequestServer;
if (name == "ImageDecoder"sv)
@ -42,8 +40,6 @@ StringView process_name_from_type(ProcessType type)
return "WebContent"sv;
case ProcessType::WebWorker:
return "WebWorker"sv;
case ProcessType::SQLServer:
return "SQLServer"sv;
case ProcessType::RequestServer:
return "RequestServer"sv;
case ProcessType::ImageDecoder:

View file

@ -1,17 +0,0 @@
compile_ipc(SQLServer.ipc SQLServerEndpoint.h)
compile_ipc(SQLClient.ipc SQLClientEndpoint.h)
set(SOURCES
ConnectionFromClient.cpp
DatabaseConnection.cpp
main.cpp
SQLStatement.cpp
)
set(GENERATED_SOURCES
SQLClientEndpoint.h
SQLServerEndpoint.h
)
serenity_bin(SQLServer)
target_link_libraries(SQLServer PRIVATE LibCore LibIPC LibSQL LibMain)

View file

@ -1,114 +0,0 @@
/*
* Copyright (c) 2021, Jan de Visser <jan@de-visser.net>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Vector.h>
#include <LibCore/StandardPaths.h>
#include <LibSQL/Result.h>
#include <SQLServer/ConnectionFromClient.h>
#include <SQLServer/DatabaseConnection.h>
#include <SQLServer/SQLStatement.h>
namespace SQLServer {
static HashMap<int, RefPtr<ConnectionFromClient>> s_connections;
RefPtr<ConnectionFromClient> ConnectionFromClient::client_connection_for(int client_id)
{
if (s_connections.contains(client_id))
return *s_connections.get(client_id).value();
dbgln_if(SQLSERVER_DEBUG, "Invalid client_id {}", client_id);
return nullptr;
}
void ConnectionFromClient::set_database_path(ByteString database_path)
{
m_database_path = move(database_path);
}
ConnectionFromClient::ConnectionFromClient(NonnullOwnPtr<Core::LocalSocket> socket, int client_id)
: IPC::ConnectionFromClient<SQLClientEndpoint, SQLServerEndpoint>(*this, move(socket), client_id)
, m_database_path(ByteString::formatted("{}/sql", Core::StandardPaths::data_directory()))
{
s_connections.set(client_id, *this);
}
void ConnectionFromClient::die()
{
s_connections.remove(client_id());
if (on_disconnect)
on_disconnect();
}
Messages::SQLServer::ConnectResponse ConnectionFromClient::connect(ByteString const& database_name)
{
dbgln_if(SQLSERVER_DEBUG, "ConnectionFromClient::connect(database_name: {})", database_name);
if (auto database_connection = DatabaseConnection::create(m_database_path, database_name, client_id()); !database_connection.is_error())
return { database_connection.value()->connection_id() };
return Optional<SQL::ConnectionID> {};
}
void ConnectionFromClient::disconnect(SQL::ConnectionID connection_id)
{
dbgln_if(SQLSERVER_DEBUG, "ConnectionFromClient::disconnect(connection_id: {})", connection_id);
auto database_connection = DatabaseConnection::connection_for(connection_id);
if (database_connection)
database_connection->disconnect();
else
dbgln("Database connection has disappeared");
}
Messages::SQLServer::PrepareStatementResponse ConnectionFromClient::prepare_statement(SQL::ConnectionID connection_id, ByteString const& sql)
{
dbgln_if(SQLSERVER_DEBUG, "ConnectionFromClient::prepare_statement(connection_id: {}, sql: '{}')", connection_id, sql);
auto database_connection = DatabaseConnection::connection_for(connection_id);
if (!database_connection) {
dbgln("Database connection has disappeared");
return Optional<SQL::StatementID> {};
}
auto result = database_connection->prepare_statement(sql);
if (result.is_error()) {
dbgln_if(SQLSERVER_DEBUG, "Could not parse SQL statement: {}", result.error().error_string());
return Optional<SQL::StatementID> {};
}
dbgln_if(SQLSERVER_DEBUG, "ConnectionFromClient::prepare_statement -> statement_id = {}", result.value());
return { result.value() };
}
Messages::SQLServer::ExecuteStatementResponse ConnectionFromClient::execute_statement(SQL::StatementID statement_id, Vector<SQL::Value> const& placeholder_values)
{
dbgln_if(SQLSERVER_DEBUG, "ConnectionFromClient::execute_query_statement(statement_id: {})", statement_id);
auto statement = SQLStatement::statement_for(statement_id);
if (statement && statement->connection().client_id() == client_id()) {
// FIXME: Support taking parameters from IPC requests.
return statement->execute(move(const_cast<Vector<SQL::Value>&>(placeholder_values)));
}
dbgln_if(SQLSERVER_DEBUG, "Statement has disappeared");
async_execution_error(statement_id, -1, SQL::SQLErrorCode::StatementUnavailable, ByteString::formatted("{}", statement_id));
return Optional<SQL::ExecutionID> {};
}
void ConnectionFromClient::ready_for_next_result(SQL::StatementID statement_id, SQL::ExecutionID execution_id)
{
dbgln_if(SQLSERVER_DEBUG, "ConnectionFromClient::ready_for_next_result(statement_id: {}, execution_id: {})", statement_id, execution_id);
auto statement = SQLStatement::statement_for(statement_id);
if (statement && statement->connection().client_id() == client_id()) {
statement->ready_for_next_result(execution_id);
return;
}
dbgln_if(SQLSERVER_DEBUG, "Statement has disappeared");
async_execution_error(statement_id, execution_id, SQL::SQLErrorCode::StatementUnavailable, ByteString::formatted("{}", statement_id));
}
}

View file

@ -1,46 +0,0 @@
/*
* Copyright (c) 2021, Jan de Visser <jan@de-visser.net>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/ByteString.h>
#include <AK/Function.h>
#include <AK/HashMap.h>
#include <AK/Vector.h>
#include <LibIPC/ConnectionFromClient.h>
#include <LibSQL/Type.h>
#include <SQLServer/SQLClientEndpoint.h>
#include <SQLServer/SQLServerEndpoint.h>
namespace SQLServer {
class ConnectionFromClient final
: public IPC::ConnectionFromClient<SQLClientEndpoint, SQLServerEndpoint> {
C_OBJECT(ConnectionFromClient);
public:
virtual ~ConnectionFromClient() override = default;
virtual void die() override;
static RefPtr<ConnectionFromClient> client_connection_for(int client_id);
void set_database_path(ByteString);
Function<void()> on_disconnect;
private:
explicit ConnectionFromClient(NonnullOwnPtr<Core::LocalSocket>, int client_id);
virtual Messages::SQLServer::ConnectResponse connect(ByteString const&) override;
virtual Messages::SQLServer::PrepareStatementResponse prepare_statement(SQL::ConnectionID, ByteString const&) override;
virtual Messages::SQLServer::ExecuteStatementResponse execute_statement(SQL::StatementID, Vector<SQL::Value> const& placeholder_values) override;
virtual void ready_for_next_result(SQL::StatementID, SQL::ExecutionID) override;
virtual void disconnect(SQL::ConnectionID) override;
ByteString m_database_path;
};
}

View file

@ -1,75 +0,0 @@
/*
* Copyright (c) 2021, Jan de Visser <jan@de-visser.net>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/LexicalPath.h>
#include <SQLServer/DatabaseConnection.h>
#include <SQLServer/SQLStatement.h>
namespace SQLServer {
static HashMap<SQL::ConnectionID, NonnullRefPtr<DatabaseConnection>> s_connections;
static SQL::ConnectionID s_next_connection_id = 0;
static ErrorOr<NonnullRefPtr<SQL::Database>> find_or_create_database(StringView database_path, StringView database_name)
{
for (auto const& connection : s_connections) {
if (connection.value->database_name() == database_name)
return connection.value->database();
}
auto database_file = ByteString::formatted("{}/{}.db", database_path, database_name);
return SQL::Database::create(move(database_file));
}
RefPtr<DatabaseConnection> DatabaseConnection::connection_for(SQL::ConnectionID connection_id)
{
if (s_connections.contains(connection_id))
return *s_connections.get(connection_id).value();
dbgln_if(SQLSERVER_DEBUG, "Invalid connection_id {}", connection_id);
return nullptr;
}
ErrorOr<NonnullRefPtr<DatabaseConnection>> DatabaseConnection::create(StringView database_path, ByteString database_name, int client_id)
{
if (LexicalPath path(database_name); (path.title() != database_name) || (path.dirname() != "."))
return Error::from_string_view("Invalid database name"sv);
auto database = TRY(find_or_create_database(database_path, database_name));
if (!database->is_open()) {
if (auto result = database->open(); result.is_error()) {
warnln("Could not open database: {}", result.error().error_string());
return Error::from_string_view("Could not open database"sv);
}
}
return adopt_nonnull_ref_or_enomem(new (nothrow) DatabaseConnection(move(database), move(database_name), client_id));
}
DatabaseConnection::DatabaseConnection(NonnullRefPtr<SQL::Database> database, ByteString database_name, int client_id)
: m_database(move(database))
, m_database_name(move(database_name))
, m_connection_id(s_next_connection_id++)
, m_client_id(client_id)
{
dbgln_if(SQLSERVER_DEBUG, "DatabaseConnection {} initiatedconnection with database '{}'", connection_id(), m_database_name);
s_connections.set(m_connection_id, *this);
}
void DatabaseConnection::disconnect()
{
dbgln_if(SQLSERVER_DEBUG, "DatabaseConnection::disconnect(connection_id {}, database '{}'", connection_id(), m_database_name);
s_connections.remove(connection_id());
}
SQL::ResultOr<SQL::StatementID> DatabaseConnection::prepare_statement(StringView sql)
{
dbgln_if(SQLSERVER_DEBUG, "DatabaseConnection::prepare_statement(connection_id {}, database '{}', sql '{}'", connection_id(), m_database_name, sql);
auto statement = TRY(SQLStatement::create(*this, sql));
return statement->statement_id();
}
}

View file

@ -1,39 +0,0 @@
/*
* Copyright (c) 2021, Jan de Visser <jan@de-visser.net>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/NonnullRefPtr.h>
#include <AK/RefCounted.h>
#include <LibSQL/Database.h>
#include <LibSQL/Result.h>
#include <LibSQL/Type.h>
#include <SQLServer/Forward.h>
namespace SQLServer {
class DatabaseConnection final : public RefCounted<DatabaseConnection> {
public:
static ErrorOr<NonnullRefPtr<DatabaseConnection>> create(StringView database_path, ByteString database_name, int client_id);
static RefPtr<DatabaseConnection> connection_for(SQL::ConnectionID connection_id);
SQL::ConnectionID connection_id() const { return m_connection_id; }
int client_id() const { return m_client_id; }
NonnullRefPtr<SQL::Database> database() { return m_database; }
StringView database_name() const { return m_database_name; }
void disconnect();
SQL::ResultOr<SQL::StatementID> prepare_statement(StringView sql);
private:
DatabaseConnection(NonnullRefPtr<SQL::Database> database, ByteString database_name, int client_id);
NonnullRefPtr<SQL::Database> m_database;
ByteString m_database_name;
SQL::ConnectionID m_connection_id { 0 };
int m_client_id { 0 };
};
}

View file

@ -1,13 +0,0 @@
/*
* Copyright (c) 2021, Jan de Visser <jan@de-visser.net>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
namespace SQLServer {
class ConnectionFromClient;
class DatabaseConnection;
class SQLStatement;
}

View file

@ -1,10 +0,0 @@
#include <LibSQL/Result.h>
#include <LibSQL/Value.h>
endpoint SQLClient
{
execution_success(u64 statement_id, u64 execution_id, Vector<ByteString> column_names, bool has_results, size_t created, size_t updated, size_t deleted) =|
next_result(u64 statement_id, u64 execution_id, Vector<SQL::Value> row) =|
results_exhausted(u64 statement_id, u64 execution_id, size_t total_rows) =|
execution_error(u64 statement_id, u64 execution_id, SQL::SQLErrorCode code, ByteString message) =|
}

View file

@ -1,10 +0,0 @@
#include <LibSQL/Value.h>
endpoint SQLServer
{
connect(ByteString name) => (Optional<u64> connection_id)
prepare_statement(u64 connection_id, ByteString statement) => (Optional<u64> statement_id)
execute_statement(u64 statement_id, Vector<SQL::Value> placeholder_values) => (Optional<u64> execution_id)
ready_for_next_result(u64 statement_id, u64 execution_id) =|
disconnect(u64 connection_id) => ()
}

View file

@ -1,146 +0,0 @@
/*
* Copyright (c) 2021, Jan de Visser <jan@de-visser.net>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibCore/EventReceiver.h>
#include <LibSQL/AST/Parser.h>
#include <SQLServer/ConnectionFromClient.h>
#include <SQLServer/DatabaseConnection.h>
#include <SQLServer/SQLStatement.h>
namespace SQLServer {
static HashMap<SQL::StatementID, NonnullRefPtr<SQLStatement>> s_statements;
static SQL::StatementID s_next_statement_id = 0;
RefPtr<SQLStatement> SQLStatement::statement_for(SQL::StatementID statement_id)
{
if (s_statements.contains(statement_id))
return *s_statements.get(statement_id).value();
dbgln_if(SQLSERVER_DEBUG, "Invalid statement_id {}", statement_id);
return nullptr;
}
SQL::ResultOr<NonnullRefPtr<SQLStatement>> SQLStatement::create(DatabaseConnection& connection, StringView sql)
{
auto parser = SQL::AST::Parser(SQL::AST::Lexer(sql));
auto statement = parser.next_statement();
if (parser.has_errors())
return SQL::Result { SQL::SQLCommand::Unknown, SQL::SQLErrorCode::SyntaxError, parser.errors()[0].to_byte_string() };
return TRY(adopt_nonnull_ref_or_enomem(new (nothrow) SQLStatement(connection, move(statement))));
}
SQLStatement::SQLStatement(DatabaseConnection& connection, NonnullRefPtr<SQL::AST::Statement> statement)
: m_connection(connection)
, m_statement_id(s_next_statement_id++)
, m_statement(move(statement))
{
dbgln_if(SQLSERVER_DEBUG, "SQLStatement({})", connection.connection_id());
s_statements.set(m_statement_id, *this);
}
void SQLStatement::report_error(SQL::Result result, SQL::ExecutionID execution_id)
{
dbgln_if(SQLSERVER_DEBUG, "SQLStatement::report_error(statement_id {}, error {}", statement_id(), result.error_string());
auto client_connection = ConnectionFromClient::client_connection_for(connection().client_id());
s_statements.remove(statement_id());
if (client_connection)
client_connection->async_execution_error(statement_id(), execution_id, result.error(), result.error_string());
else
warnln("Cannot return execution error. Client disconnected");
}
Optional<SQL::ExecutionID> SQLStatement::execute(Vector<SQL::Value> placeholder_values)
{
dbgln_if(SQLSERVER_DEBUG, "SQLStatement::execute(statement_id {}", statement_id());
auto client_connection = ConnectionFromClient::client_connection_for(connection().client_id());
if (!client_connection) {
warnln("Cannot yield next result. Client disconnected");
return {};
}
auto execution_id = m_next_execution_id++;
Core::deferred_invoke([this, strong_this = NonnullRefPtr(*this), placeholder_values = move(placeholder_values), execution_id] {
auto execution_result = m_statement->execute(connection().database(), placeholder_values);
if (execution_result.is_error()) {
report_error(execution_result.release_error(), execution_id);
return;
}
auto client_connection = ConnectionFromClient::client_connection_for(connection().client_id());
if (!client_connection) {
warnln("Cannot return statement execution results. Client disconnected");
return;
}
auto result = execution_result.release_value();
auto result_size = result.size();
if (should_send_result_rows(result)) {
client_connection->async_execution_success(statement_id(), execution_id, result.column_names(), true, 0, 0, 0);
m_ongoing_executions.set(execution_id, { move(result), result_size });
ready_for_next_result(execution_id);
} else {
if (result.command() == SQL::SQLCommand::Insert)
client_connection->async_execution_success(statement_id(), execution_id, result.column_names(), false, result_size, 0, 0);
else if (result.command() == SQL::SQLCommand::Update)
client_connection->async_execution_success(statement_id(), execution_id, result.column_names(), false, 0, result_size, 0);
else if (result.command() == SQL::SQLCommand::Delete)
client_connection->async_execution_success(statement_id(), execution_id, result.column_names(), false, 0, 0, result_size);
else
client_connection->async_execution_success(statement_id(), execution_id, result.column_names(), false, 0, 0, 0);
}
});
return execution_id;
}
void SQLStatement::ready_for_next_result(SQL::ExecutionID execution_id)
{
auto client_connection = ConnectionFromClient::client_connection_for(connection().client_id());
if (!client_connection) {
warnln("Cannot yield next result. Client disconnected");
return;
}
auto execution = m_ongoing_executions.get(execution_id);
if (!execution.has_value()) {
return;
}
if (execution->result.is_empty()) {
client_connection->async_results_exhausted(statement_id(), execution_id, execution->result_size);
m_ongoing_executions.remove(execution_id);
return;
}
auto result_row = execution->result.take_first();
client_connection->async_next_result(statement_id(), execution_id, result_row.row.take_data());
}
bool SQLStatement::should_send_result_rows(SQL::ResultSet const& result) const
{
if (result.is_empty())
return false;
switch (result.command()) {
case SQL::SQLCommand::Describe:
case SQL::SQLCommand::Select:
return true;
default:
return false;
}
}
}

View file

@ -1,50 +0,0 @@
/*
* Copyright (c) 2021, Jan de Visser <jan@de-visser.net>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/NonnullRefPtr.h>
#include <AK/RefCounted.h>
#include <AK/Vector.h>
#include <LibSQL/AST/AST.h>
#include <LibSQL/Result.h>
#include <LibSQL/ResultSet.h>
#include <LibSQL/Type.h>
#include <SQLServer/DatabaseConnection.h>
#include <SQLServer/Forward.h>
namespace SQLServer {
class SQLStatement final : public RefCounted<SQLStatement> {
public:
static SQL::ResultOr<NonnullRefPtr<SQLStatement>> create(DatabaseConnection&, StringView sql);
static RefPtr<SQLStatement> statement_for(SQL::StatementID statement_id);
SQL::StatementID statement_id() const { return m_statement_id; }
DatabaseConnection& connection() { return m_connection; }
Optional<SQL::ExecutionID> execute(Vector<SQL::Value> placeholder_values);
void ready_for_next_result(SQL::ExecutionID);
private:
SQLStatement(DatabaseConnection&, NonnullRefPtr<SQL::AST::Statement> statement);
bool should_send_result_rows(SQL::ResultSet const& result) const;
void report_error(SQL::Result, SQL::ExecutionID execution_id);
DatabaseConnection& m_connection;
SQL::StatementID m_statement_id { 0 };
struct Execution {
SQL::ResultSet result;
size_t result_size { 0 };
};
HashMap<SQL::ExecutionID, Execution> m_ongoing_executions;
SQL::ExecutionID m_next_execution_id { 0 };
NonnullRefPtr<SQL::AST::Statement> m_statement;
};
}

View file

@ -1,29 +0,0 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibCore/Directory.h>
#include <LibCore/EventLoop.h>
#include <LibCore/StandardPaths.h>
#include <LibCore/System.h>
#include <LibIPC/MultiServer.h>
#include <LibMain/Main.h>
#include <SQLServer/ConnectionFromClient.h>
ErrorOr<int> serenity_main(Main::Arguments)
{
TRY(Core::System::pledge("stdio accept unix rpath wpath cpath"));
auto database_path = ByteString::formatted("{}/sql", Core::StandardPaths::data_directory());
TRY(Core::Directory::create(database_path, Core::Directory::CreateDirectories::Yes));
TRY(Core::System::unveil(database_path, "rwc"sv));
TRY(Core::System::unveil(nullptr, nullptr));
Core::EventLoop event_loop;
auto server = TRY(IPC::MultiServer<SQLServer::ConnectionFromClient>::try_create());
return event_loop.exec();
}

View file

@ -10,7 +10,6 @@ set(CMD_SOURCES
js.cpp
lzcat.cpp
shred.cpp
sql.cpp
tar.cpp
test-jpeg-roundtrip.cpp
ttfdisasm.cpp
@ -44,7 +43,6 @@ target_link_libraries(image PRIVATE LibGfx)
target_link_libraries(isobmff PRIVATE LibGfx)
target_link_libraries(js PRIVATE LibCrypto LibJS LibLine LibLocale LibTextCodec)
target_link_libraries(lzcat PRIVATE LibCompress)
target_link_libraries(sql PRIVATE LibFileSystem LibIPC LibLine LibSQL)
target_link_libraries(tar PRIVATE LibArchive LibCompress LibFileSystem)
target_link_libraries(test-jpeg-roundtrip PRIVATE LibGfx)
target_link_libraries(ttfdisasm PRIVATE LibGfx)

View file

@ -1,384 +0,0 @@
/*
* Copyright (c) 2021-2024, Tim Flynn <trflynn89@serenityos.org>
* Copyright (c) 2022, Alex Major
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/ByteString.h>
#include <AK/Format.h>
#include <AK/String.h>
#include <AK/StringBuilder.h>
#include <LibCore/ArgsParser.h>
#include <LibCore/StandardPaths.h>
#include <LibFileSystem/FileSystem.h>
#include <LibLine/Editor.h>
#include <LibMain/Main.h>
#include <LibSQL/AST/Lexer.h>
#include <LibSQL/AST/Token.h>
#include <LibSQL/SQLClient.h>
#include <unistd.h>
#if !defined(AK_OS_SERENITY)
# include <LibCore/Process.h>
#endif
class SQLRepl {
public:
explicit SQLRepl(Core::EventLoop& loop, ByteString const& database_name, NonnullRefPtr<SQL::SQLClient> sql_client)
: m_history_path(ByteString::formatted("{}/.sql-history", Core::StandardPaths::home_directory()))
, m_sql_client(move(sql_client))
, m_loop(loop)
{
m_editor = Line::Editor::construct();
m_editor->load_history(m_history_path);
m_editor->on_display_refresh = [this](Line::Editor& editor) {
editor.strip_styles();
int open_indents = m_repl_line_level;
auto line = editor.line();
SQL::AST::Lexer lexer(line);
bool indenters_starting_line = true;
for (SQL::AST::Token token = lexer.next(); token.type() != SQL::AST::TokenType::Eof; token = lexer.next()) {
auto start = token.start_position().column - 1;
auto end = token.end_position().column - 1;
if (indenters_starting_line) {
if (token.type() != SQL::AST::TokenType::ParenClose)
indenters_starting_line = false;
else
--open_indents;
}
switch (token.category()) {
case SQL::AST::TokenCategory::Invalid:
editor.stylize({ start, end }, { Line::Style::Foreground(Line::Style::XtermColor::Red), Line::Style::Underline });
break;
case SQL::AST::TokenCategory::Number:
editor.stylize({ start, end }, { Line::Style::Foreground(Line::Style::XtermColor::Magenta) });
break;
case SQL::AST::TokenCategory::String:
editor.stylize({ start, end }, { Line::Style::Foreground(Line::Style::XtermColor::Green), Line::Style::Bold });
break;
case SQL::AST::TokenCategory::Blob:
editor.stylize({ start, end }, { Line::Style::Foreground(Line::Style::XtermColor::Magenta), Line::Style::Bold });
break;
case SQL::AST::TokenCategory::Keyword:
editor.stylize({ start, end }, { Line::Style::Foreground(Line::Style::XtermColor::Blue), Line::Style::Bold });
break;
case SQL::AST::TokenCategory::Identifier:
editor.stylize({ start, end }, { Line::Style::Foreground(Line::Style::XtermColor::White), Line::Style::Bold });
break;
default:
break;
}
}
m_editor->set_prompt(prompt_for_level(open_indents));
};
m_sql_client->on_execution_success = [this](auto result) {
if (result.rows_updated != 0 || result.rows_created != 0 || result.rows_deleted != 0)
outln("{} row(s) created, {} updated, {} deleted", result.rows_created, result.rows_updated, result.rows_deleted);
if (!result.has_results)
read_sql();
};
m_sql_client->on_next_result = [](auto result) {
StringBuilder builder;
builder.join(", "sv, result.values);
outln("{}", builder.to_byte_string());
};
m_sql_client->on_results_exhausted = [this](auto result) {
outln("{} row(s)", result.total_rows);
read_sql();
};
m_sql_client->on_execution_error = [this](auto result) {
outln("\033[33;1mExecution error:\033[0m {}", result.error_message);
read_sql();
};
if (!database_name.is_empty())
connect(database_name);
}
~SQLRepl()
{
m_editor->save_history(m_history_path);
}
void connect(ByteString const& database_name)
{
if (!m_database_name.is_empty()) {
m_sql_client->disconnect(m_connection_id);
m_database_name = {};
}
if (auto connection_id = m_sql_client->connect(database_name); connection_id.has_value()) {
outln("Connected to \033[33;1m{}\033[0m", database_name);
m_database_name = database_name;
m_connection_id = *connection_id;
} else {
warnln("\033[33;1mCould not connect to:\033[0m {}", database_name);
m_loop.quit(1);
}
}
void source_file(ByteString file_name)
{
m_input_file_chain.append(move(file_name));
m_quit_when_files_read = false;
}
void read_file(ByteString file_name)
{
m_input_file_chain.append(move(file_name));
m_quit_when_files_read = true;
}
auto run()
{
read_sql();
return m_loop.exec();
}
private:
ByteString m_history_path;
RefPtr<Line::Editor> m_editor { nullptr };
int m_repl_line_level { 0 };
bool m_keep_running { true };
ByteString m_database_name {};
NonnullRefPtr<SQL::SQLClient> m_sql_client;
SQL::ConnectionID m_connection_id { 0 };
Core::EventLoop& m_loop;
OwnPtr<Core::InputBufferedFile> m_input_file { nullptr };
bool m_quit_when_files_read { false };
Vector<ByteString> m_input_file_chain {};
Array<u8, 4096> m_buffer {};
Optional<ByteString> get_line()
{
if (!m_input_file && !m_input_file_chain.is_empty()) {
auto file_name = m_input_file_chain.take_first();
auto file_or_error = Core::File::open(file_name, Core::File::OpenMode::Read);
if (file_or_error.is_error()) {
warnln("Input file {} could not be opened: {}", file_name, file_or_error.error());
return {};
}
auto buffered_file_or_error = Core::InputBufferedFile::create(file_or_error.release_value());
if (buffered_file_or_error.is_error()) {
warnln("Input file {} could not be buffered: {}", file_name, buffered_file_or_error.error());
return {};
}
m_input_file = buffered_file_or_error.release_value();
}
if (m_input_file) {
auto line = m_input_file->read_line(m_buffer);
if (line.is_error()) {
warnln("Failed to read line: {}", line.error());
return {};
}
if (m_input_file->is_eof()) {
m_input_file->close();
m_input_file = nullptr;
if (m_quit_when_files_read && m_input_file_chain.is_empty())
return {};
}
return line.release_value();
// If the last file is exhausted but m_quit_when_files_read is false
// we fall through to the standard reading from the editor behavior
}
auto line_result = m_editor->get_line(prompt_for_level(m_repl_line_level));
if (line_result.is_error())
return {};
return line_result.value();
}
ByteString read_next_piece()
{
StringBuilder piece;
do {
if (!piece.is_empty())
piece.append('\n');
auto line_maybe = get_line();
if (!line_maybe.has_value()) {
m_keep_running = false;
return {};
}
auto& line = line_maybe.value();
auto lexer = SQL::AST::Lexer(line);
m_editor->add_to_history(line);
piece.append(line);
bool is_first_token = true;
bool is_command = false;
bool last_token_ended_statement = false;
bool tokens_found = false;
for (SQL::AST::Token token = lexer.next(); token.type() != SQL::AST::TokenType::Eof; token = lexer.next()) {
tokens_found = true;
switch (token.type()) {
case SQL::AST::TokenType::ParenOpen:
++m_repl_line_level;
break;
case SQL::AST::TokenType::ParenClose:
--m_repl_line_level;
break;
case SQL::AST::TokenType::SemiColon:
last_token_ended_statement = true;
break;
case SQL::AST::TokenType::Period:
if (is_first_token)
is_command = true;
break;
default:
last_token_ended_statement = is_command;
break;
}
is_first_token = false;
}
if (tokens_found)
m_repl_line_level = last_token_ended_statement ? 0 : (m_repl_line_level > 0 ? m_repl_line_level : 1);
} while ((m_repl_line_level > 0) || piece.is_empty());
return piece.to_byte_string();
}
void read_sql()
{
ByteString piece = read_next_piece();
// m_keep_running can be set to false when the file we are reading
// from is exhausted...
if (!m_keep_running) {
m_sql_client->disconnect(m_connection_id);
m_loop.quit(0);
return;
}
if (piece.starts_with('.')) {
bool ready_for_input = handle_command(piece);
if (ready_for_input)
m_loop.deferred_invoke([this]() {
read_sql();
});
} else if (auto statement_id = m_sql_client->prepare_statement(m_connection_id, piece); statement_id.has_value()) {
m_sql_client->async_execute_statement(*statement_id, {});
} else {
warnln("\033[33;1mError parsing SQL statement\033[0m: {}", piece);
m_loop.deferred_invoke([this]() {
read_sql();
});
}
// ...But m_keep_running can also be set to false by a command handler.
if (!m_keep_running) {
m_sql_client->disconnect(m_connection_id);
m_loop.quit(0);
return;
}
}
static ByteString prompt_for_level(int level)
{
static StringBuilder prompt_builder;
prompt_builder.clear();
prompt_builder.append("> "sv);
for (auto i = 0; i < level; ++i)
prompt_builder.append(" "sv);
return prompt_builder.to_byte_string();
}
bool handle_command(StringView command)
{
bool ready_for_input = true;
if (command == ".exit" || command == ".quit") {
m_keep_running = false;
ready_for_input = false;
} else if (command.starts_with(".connect "sv)) {
auto parts = command.split_view(' ');
if (parts.size() == 2) {
connect(parts[1]);
ready_for_input = false;
} else {
outln("\033[33;1mUsage: .connect <database name>\033[0m");
}
} else if (command.starts_with(".read "sv)) {
if (!m_input_file) {
auto parts = command.split_view(' ');
if (parts.size() == 2) {
source_file(parts[1]);
} else {
outln("\033[33;1mUsage: .read <sql file>\033[0m");
}
} else {
outln("\033[33;1mCannot recursively read sql files\033[0m");
}
} else {
outln("\033[33;1mUnrecognized command:\033[0m {}", command);
}
return ready_for_input;
}
};
ErrorOr<int> serenity_main(Main::Arguments arguments)
{
ByteString database_name(getlogin());
ByteString file_to_source;
ByteString file_to_read;
bool suppress_sqlrc = false;
auto sqlrc_path = ByteString::formatted("{}/.sqlrc", Core::StandardPaths::home_directory());
#if !defined(AK_OS_SERENITY)
StringView sql_server_path;
#endif
Core::ArgsParser args_parser;
args_parser.set_general_help("This is a client for the SerenitySQL database server.");
args_parser.add_option(database_name, "Database to connect to", "database", 'd', "database");
args_parser.add_option(file_to_read, "File to read", "read", 'r', "file");
args_parser.add_option(file_to_source, "File to source", "source", 's', "file");
args_parser.add_option(suppress_sqlrc, "Don't read ~/.sqlrc", "no-sqlrc", 'n');
#if !defined(AK_OS_SERENITY)
args_parser.add_option(sql_server_path, "Path to SQLServer to launch if needed", "sql-server-path", 'p', "path");
#endif
args_parser.parse(arguments);
Core::EventLoop loop;
#if defined(AK_OS_SERENITY)
auto sql_client = TRY(SQL::SQLClient::try_create());
#else
VERIFY(!sql_server_path.is_empty());
auto [_, sql_client] = TRY(Core::IPCProcess::spawn_singleton<SQL::SQLClient>({
.name = "SQLServer"sv,
.executable = sql_server_path,
}));
#endif
SQLRepl repl(loop, database_name, move(sql_client));
if (!suppress_sqlrc && FileSystem::exists(sqlrc_path))
repl.source_file(sqlrc_path);
if (!file_to_source.is_empty())
repl.source_file(file_to_source);
if (!file_to_read.is_empty())
repl.read_file(file_to_read);
return repl.run();
}