mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-21 23:20:20 +00:00
Everywhere: Remove LibSQL, SQLServer, and the sql REPL :^)
It is now entirely unused and replaced by sqlite3.
This commit is contained in:
parent
30e745ffa7
commit
8362c073f3
Notes:
sideshowbarker
2024-07-17 00:37:23 +09:00
Author: https://github.com/trflynn89 Commit: https://github.com/LadybirdBrowser/ladybird/commit/8362c073f3 Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/45 Reviewed-by: https://github.com/ADKaster ✅
100 changed files with 8 additions and 14561 deletions
3
.github/CODEOWNERS
vendored
3
.github/CODEOWNERS
vendored
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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})
|
||||
|
|
|
@ -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>();
|
||||
|
|
|
@ -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&);
|
||||
|
|
|
@ -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)
|
|
@ -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();
|
||||
}
|
|
@ -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}
|
||||
|
|
|
@ -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/)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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=",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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"
|
||||
}
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
]
|
||||
}
|
|
@ -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",
|
||||
]
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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());
|
||||
}
|
|
@ -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
|
@ -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
|
@ -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()
|
||||
|
|
|
@ -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
|
@ -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 };
|
||||
}
|
||||
|
||||
}
|
|
@ -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 };
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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(¤t_token);
|
||||
consume(¤t_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(¤t_token);
|
||||
}
|
||||
}
|
||||
|
||||
if (!found_two_char_token && !found_one_char_token) {
|
||||
token_type = TokenType::Invalid;
|
||||
consume(¤t_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(¤t_token);
|
||||
if (m_current_char == '.') {
|
||||
consume(¤t_token);
|
||||
while (isdigit(m_current_char))
|
||||
consume(¤t_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(¤t_token);
|
||||
} while (isdigit(m_current_char));
|
||||
}
|
||||
} else {
|
||||
do {
|
||||
consume(¤t_token);
|
||||
} while (isdigit(m_current_char));
|
||||
|
||||
if (m_current_char == '.') {
|
||||
consume(¤t_token);
|
||||
while (isdigit(m_current_char))
|
||||
consume(¤t_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(¤t_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(¤t_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(¤t_token);
|
||||
if (m_current_char == '-' || m_current_char == '+')
|
||||
consume(¤t_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(¤t_token);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Lexer::consume_hexadecimal_number(StringBuilder& current_token)
|
||||
{
|
||||
consume(¤t_token);
|
||||
if (!isxdigit(m_current_char))
|
||||
return false;
|
||||
|
||||
while (isxdigit(m_current_char))
|
||||
consume(¤t_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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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)
|
|
@ -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 {};
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
|
@ -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)
|
||||
{
|
||||
}
|
||||
|
||||
}
|
|
@ -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 };
|
||||
};
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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 };
|
||||
};
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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>;
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
|
@ -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 };
|
||||
};
|
||||
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
|
@ -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 = "";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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 };
|
||||
};
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
|
@ -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==;
|
||||
};
|
||||
|
||||
}
|
|
@ -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;
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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&);
|
||||
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -24,7 +24,6 @@ enum class Language {
|
|||
Markdown,
|
||||
PlainText,
|
||||
Shell,
|
||||
SQL,
|
||||
};
|
||||
|
||||
StringView language_to_string(Language);
|
||||
|
|
|
@ -21,7 +21,6 @@ enum class ProcessType {
|
|||
Chrome,
|
||||
WebContent,
|
||||
WebWorker,
|
||||
SQLServer,
|
||||
RequestServer,
|
||||
ImageDecoder,
|
||||
};
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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 };
|
||||
};
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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) =|
|
||||
}
|
|
@ -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) => ()
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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();
|
||||
}
|
Loading…
Reference in a new issue