├── README.md ├── d ├── dub.selections.json ├── dub.json └── source │ └── app.d ├── kotlin ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── .gitignore ├── .gitattributes ├── app │ ├── src │ │ ├── test │ │ │ └── kotlin │ │ │ │ └── rosetta │ │ │ │ └── db │ │ │ │ └── AppTest.kt │ │ └── main │ │ │ ├── resources │ │ │ └── log4j2.xml │ │ │ └── kotlin │ │ │ └── rosetta │ │ │ └── db │ │ │ └── App.kt │ └── build.gradle.kts ├── settings.gradle.kts ├── gradlew.bat └── gradlew └── cpp ├── .clang-format ├── CMakeLists.txt ├── CMakePresets.json ├── src └── database.cpp └── .clang-tidy /README.md: -------------------------------------------------------------------------------- 1 | # rosetta-db 2 | -------------------------------------------------------------------------------- /d/dub.selections.json: -------------------------------------------------------------------------------- 1 | { 2 | "fileVersion": 1, 3 | "versions": { 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /kotlin/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GavinRay97/rosetta-db/HEAD/kotlin/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /kotlin/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore Gradle project-specific cache directory 2 | .gradle 3 | 4 | # Ignore Gradle build output directory 5 | build 6 | -------------------------------------------------------------------------------- /d/dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "authors": [ 3 | "user" 4 | ], 5 | "copyright": "Copyright © 2022, user", 6 | "description": "A minimal D application.", 7 | "license": "proprietary", 8 | "name": "bustub-dlang", 9 | "dflags": [] 10 | } -------------------------------------------------------------------------------- /kotlin/.gitattributes: -------------------------------------------------------------------------------- 1 | # 2 | # https://help.github.com/articles/dealing-with-line-endings/ 3 | # 4 | # Linux start script should use lf 5 | /gradlew text eol=lf 6 | 7 | # These are Windows script files and should use crlf 8 | *.bat text eol=crlf 9 | 10 | -------------------------------------------------------------------------------- /kotlin/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-rc-1-bin.zip 4 | networkTimeout=10000 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /kotlin/app/src/test/kotlin/rosetta/db/AppTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * This Kotlin source file was generated by the Gradle 'init' task. 3 | */ 4 | package rosetta.db 5 | 6 | import kotlin.test.Test 7 | import kotlin.test.assertNotNull 8 | 9 | class AppTest { 10 | @Test fun appHasAGreeting() { 11 | val classUnderTest = App() 12 | assertNotNull(classUnderTest.greeting, "app should have a greeting") 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /kotlin/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * This file was generated by the Gradle 'init' task. 3 | * 4 | * The settings file is used to specify which projects to include in your build. 5 | * 6 | * Detailed information about configuring a multi-project build in Gradle can be found 7 | * in the user manual at https://docs.gradle.org/8.0-rc-1/userguide/multi_project_builds.html 8 | * This project uses @Incubating APIs which are subject to change. 9 | */ 10 | 11 | rootProject.name = "rosetta-db" 12 | include("app") 13 | -------------------------------------------------------------------------------- /kotlin/app/src/main/resources/log4j2.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /cpp/.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: Mozilla 2 | 3 | # Left-pointer alignment 4 | PointerAlignment: Left 5 | ColumnLimit: 100 6 | 7 | # Width of lines 8 | IndentWidth: 2 9 | NamespaceIndentation: All 10 | 11 | AllowShortBlocksOnASingleLine: Always 12 | AllowShortCaseLabelsOnASingleLine: true 13 | AllowShortEnumsOnASingleLine: true 14 | AllowShortFunctionsOnASingleLine: All 15 | # AllowShortIfStatementsOnASingleLine: WithoutElse 16 | AllowShortLambdasOnASingleLine: All 17 | 18 | AlignAfterOpenBracket: Align 19 | AlignConsecutiveAssignments: true 20 | AlignConsecutiveDeclarations: true 21 | AlignEscapedNewlines: Right 22 | AlignOperands: true 23 | AlignTrailingComments: true 24 | 25 | # Newlines between each distinct definition (struct, namespace, method, variables, etc) 26 | SeparateDefinitionBlocks: Always 27 | SpaceAfterCStyleCast: true 28 | SpaceBeforeCpp11BracedList: true 29 | 30 | # Space after { } initializers 31 | Cpp11BracedListStyle: false 32 | 33 | PackConstructorInitializers: Never 34 | 35 | BreakBeforeBraces: Custom 36 | BraceWrapping: 37 | AfterClass: true 38 | AfterControlStatement: true 39 | AfterEnum: true 40 | AfterFunction: true 41 | AfterNamespace: true 42 | AfterObjCDeclaration: true 43 | AfterStruct: true 44 | AfterUnion: true 45 | AfterExternBlock: true 46 | BeforeCatch: true 47 | BeforeElse: true 48 | IndentBraces: false 49 | SplitEmptyFunction: true 50 | SplitEmptyRecord: true 51 | SplitEmptyNamespace: true 52 | -------------------------------------------------------------------------------- /kotlin/app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * This file was generated by the Gradle 'init' task. 3 | * 4 | * This generated file contains a sample Kotlin application project to get you started. 5 | * For more details take a look at the 'Building Java & JVM projects' chapter in the Gradle 6 | * User Manual available at https://docs.gradle.org/8.0-rc-1/userguide/building_java_projects.html 7 | * This project uses @Incubating APIs which are subject to change. 8 | */ 9 | 10 | plugins { 11 | // Apply the org.jetbrains.kotlin.jvm Plugin to add support for Kotlin. 12 | id("org.jetbrains.kotlin.jvm") version "1.8.0" 13 | 14 | // Apply the application plugin to add support for building a CLI application in Java. 15 | application 16 | } 17 | 18 | repositories { 19 | // Use Maven Central for resolving dependencies. 20 | mavenCentral() 21 | } 22 | 23 | java { 24 | toolchain { 25 | languageVersion.set(JavaLanguageVersion.of(19)) 26 | } 27 | } 28 | 29 | dependencies { 30 | // This dependency is used by the application. 31 | implementation("com.google.guava:guava:31.1-jre") 32 | implementation("io.github.microutils:kotlin-logging-jvm:3.0.4") 33 | implementation("org.apache.logging.log4j:log4j-slf4j2-impl:2.19.0") 34 | } 35 | 36 | testing { 37 | suites { 38 | // Configure the built-in test suite 39 | val test by getting(JvmTestSuite::class) { 40 | // Use Kotlin Test test framework 41 | useKotlinTest("1.8.0") 42 | } 43 | } 44 | } 45 | 46 | application { 47 | // Define the main class for the application. 48 | mainClass.set("rosetta.db.AppKt") 49 | } 50 | -------------------------------------------------------------------------------- /cpp/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | project(rosetta-db) 2 | cmake_minimum_required(VERSION 3.20) 3 | 4 | # Set RANGES_BUILD_TESTS to OFF to disable tests for tl::ranges library 5 | set(RANGES_BUILD_TESTS OFF CACHE BOOL "Build tests for tl::ranges" FORCE) 6 | set(SPDLOG_USE_STD_FORMAT OFF CACHE BOOL "Use std::format for fmt" FORCE) 7 | 8 | Include(FetchContent) 9 | 10 | FetchContent_Declare( 11 | Catch2 12 | GIT_REPOSITORY https://github.com/catchorg/Catch2 13 | GIT_TAG devel # For GCC 13 support, due to missing #include for 14 | ) 15 | FetchContent_MakeAvailable(Catch2) 16 | 17 | FetchContent_Declare( 18 | spdlog 19 | GIT_REPOSITORY https://github.com/gabime/spdlog 20 | GIT_TAG v1.11.0 21 | ) 22 | FetchContent_MakeAvailable(spdlog) 23 | 24 | FetchContent_Declare( 25 | backward 26 | GIT_REPOSITORY https://github.com/damageboy/backward-cpp 27 | GIT_TAG master # Fork which has proper CMake ALIAS target 28 | ) 29 | FetchContent_MakeAvailable(backward) 30 | 31 | FetchContent_Declare( 32 | tl-ranges 33 | GIT_REPOSITORY https://github.com/TartanLlama/ranges 34 | GIT_TAG main 35 | ) 36 | FetchContent_MakeAvailable(tl-ranges) 37 | 38 | FetchContent_Declare( 39 | libassert 40 | GIT_REPOSITORY https://github.com/jeremy-rifkin/libassert 41 | GIT_TAG v1.1 42 | ) 43 | FetchContent_MakeAvailable(libassert) 44 | 45 | FetchContent_Declare( 46 | ring_span_lite 47 | GIT_REPOSITORY https://github.com/martinmoene/ring-span-lite 48 | GIT_TAG master 49 | ) 50 | FetchContent_MakeAvailable(ring_span_lite) 51 | 52 | 53 | 54 | # Add source files 55 | file(GLOB_RECURSE SOURCES "src/*.cpp") 56 | add_executable(rosetta-db ${SOURCES}) 57 | target_include_directories(rosetta-db 58 | PRIVATE 59 | ${CMAKE_CURRENT_SOURCE_DIR}/src 60 | ) 61 | target_compile_options(rosetta-db 62 | PRIVATE 63 | $ENV{CXX_FLAGS} 64 | ) 65 | target_link_libraries(rosetta-db 66 | PRIVATE 67 | spdlog::spdlog 68 | Backward::Backward bfd dl 69 | tl::ranges 70 | assert dl 71 | ) 72 | 73 | # Configure Catch2 test discovery 74 | # list(APPEND CMAKE_MODULE_PATH ${catch2_SOURCE_DIR}/extras) 75 | # include(CTest) 76 | # include(Catch) 77 | # catch_discover_tests(rosetta-db) 78 | 79 | # Copy compile_commands.json to root directory if changed 80 | add_custom_command(TARGET rosetta-db POST_BUILD 81 | COMMAND ${CMAKE_COMMAND} -E copy_if_different 82 | ${CMAKE_CURRENT_BINARY_DIR}/compile_commands.json 83 | ${CMAKE_CURRENT_SOURCE_DIR}/compile_commands.json 84 | ) -------------------------------------------------------------------------------- /kotlin/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | @rem This is normally unused 30 | set APP_BASE_NAME=%~n0 31 | set APP_HOME=%DIRNAME% 32 | 33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 35 | 36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 38 | 39 | @rem Find java.exe 40 | if defined JAVA_HOME goto findJavaFromJavaHome 41 | 42 | set JAVA_EXE=java.exe 43 | %JAVA_EXE% -version >NUL 2>&1 44 | if %ERRORLEVEL% equ 0 goto execute 45 | 46 | echo. 47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 48 | echo. 49 | echo Please set the JAVA_HOME variable in your environment to match the 50 | echo location of your Java installation. 51 | 52 | goto fail 53 | 54 | :findJavaFromJavaHome 55 | set JAVA_HOME=%JAVA_HOME:"=% 56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 57 | 58 | if exist "%JAVA_EXE%" goto execute 59 | 60 | echo. 61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 62 | echo. 63 | echo Please set the JAVA_HOME variable in your environment to match the 64 | echo location of your Java installation. 65 | 66 | goto fail 67 | 68 | :execute 69 | @rem Setup the command line 70 | 71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 72 | 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if %ERRORLEVEL% equ 0 goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | set EXIT_CODE=%ERRORLEVEL% 85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 87 | exit /b %EXIT_CODE% 88 | 89 | :mainEnd 90 | if "%OS%"=="Windows_NT" endlocal 91 | 92 | :omega 93 | -------------------------------------------------------------------------------- /cpp/CMakePresets.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "cmakeMinimumRequired": { 4 | "major": 3, 5 | "minor": 23, 6 | "patch": 0 7 | }, 8 | "configurePresets": [ 9 | { 10 | "name": "default", 11 | "displayName": "Default Config", 12 | "description": "Default build using Ninja generator", 13 | "generator": "Ninja", 14 | "binaryDir": "${sourceDir}/build/default", 15 | "cacheVariables": { 16 | "CMAKE_EXPORT_COMPILE_COMMANDS": "ON", 17 | "CMAKE_COLOR_DIAGNOSTICS": "ON", 18 | "CMAKE_C_STANDARD": "23", 19 | "CMAKE_CXX_STANDARD": "23", 20 | "CMAKE_CXX_EXTENSIONS": "ON", 21 | "CMAKE_CXX_STANDARD_REQUIRED": "OFF", 22 | "CMAKE_CXX_COMPILER_LAUNCHER": "ccache", 23 | "CMAKE_C_COMPILER_LAUNCHER": "ccache", 24 | "CMAKE_LINK_WHAT_YOU_USE": "ON" 25 | }, 26 | "environment": { 27 | "COMMON_DEFINES": "-D_GLIBCXX_ASSERTIONS -D_GLIBCXX_CONCEPT_CHECKS -D_GLIBCXX_USE_CHAR8_T -D__USE_GNU", 28 | "COMMON_CXX_FLAGS": "$env{COMMON_DEFINES} -Wall -Wextra -Werror -Wno-unused-variable -pipe -ftrivial-auto-var-init=zero -fPIC", 29 | "DEBUG_DEFINES": "-DDEBUG -D_GLIBCXX_DEBUG", 30 | "DEBUG_CXX_FLAGS": "$env{DEBUG_DEFINES} -O0 -g -ggdb3 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -fstandalone-debug", 31 | "RELEASE_DEFINES": "-D_FORTIFY_SOURCE=3", 32 | "RELEASE_CXX_FLAGS": "$env{RELEASE_DEFINES} -Ofast -march=native -mtune=native -flto", 33 | "COMMON_SANITIZER_DEFINES": "-D_GLIBCXX_SANITIZE_STD_ALLOCATOR -D_GLIBCXX_SANITIZE_VECTOR", 34 | "COMMON_SANITIZER_FLAGS": "$env{COMMON_SANITIZER_DEFINES} -fsanitize=address,undefined -fsanitize-address-use-after-scope", 35 | "GCC_LINKER_FLAGS": "-Wl,-rpath='/opt/gcc-latest/lib64' -Wl,-rpath='/opt/gcc-latest/lib'", 36 | "GCC_SANITIZER_FLAGS": "$env{COMMON_SANITIZER_FLAGS}", 37 | "LLVM_SANITIZER_FLAGS": "$env{COMMON_SANITIZER_FLAGS} -fsanitize=function,integer,nullability", 38 | "CLANG_ONLY_CXX_FLAGS": "-Wthread-safety -Wthread-safety-beta -Wthread-safety-verbose --gcc-toolchain=/opt/gcc-latest -Warray-bounds-pointer-arithmetic -Wbind-to-temporary-copy -Wcalled-once-parameter -Wcast-align -Wconditional-uninitialized -Wconsumed -Wcovered-switch-default -Wexplicit-ownership-type -Wheader-hygiene -Widiomatic-parentheses -Wloop-analysis -Wmain -Wmethod-signatures -Wmissing-noreturn -Wmissing-variable-declarations -Wnarrowing -Wnon-gcc -Wnullable-to-nonnull-conversion -Wover-aligned -Woverriding-method-mismatch -Wpacked -Wpadded -Wpartial-availability -Wstatic-in-inline -Wsuggest-destructor-override -Wsuggest-override -Wsuper-class-method-mismatch -Wtautological-constant-in-range-compare -Wunaligned-access -Wundefined-func-template -Wunreachable-code-aggressive -Wunused-member-function -Wunused-template -Wno-padded -Wno-sign-conversion -Wno-implicit-int-float-conversion" 39 | }, 40 | "vendor": {} 41 | }, 42 | { 43 | "name": "debug", 44 | "displayName": "Debug Build", 45 | "description": "Debug build using Ninja generator", 46 | "generator": "Ninja", 47 | "inherits": "default", 48 | "binaryDir": "${sourceDir}/build/debug", 49 | "cacheVariables": { 50 | "CMAKE_BUILD_TYPE": "Debug", 51 | "CMAKE_C_FLAGS": "$env{COMMON_CXX_FLAGS} $env{DEBUG_CXX_FLAGS}", 52 | "CMAKE_CXX_FLAGS": "$env{COMMON_CXX_FLAGS} $env{DEBUG_CXX_FLAGS}" 53 | } 54 | }, 55 | { 56 | "name": "debug-gcc-fanalyzer", 57 | "displayName": "Debug GCC with -fanalyzer", 58 | "description": "Debug build with GCC and -fanalyzer", 59 | "inherits": "default", 60 | "binaryDir": "${sourceDir}/build/debug", 61 | "cacheVariables": { 62 | "CMAKE_BUILD_TYPE": "Debug", 63 | "CMAKE_C_COMPILER": "/opt/gcc-latest/bin/gcc", 64 | "CMAKE_CXX_COMPILER": "/opt/gcc-latest/bin/g++", 65 | "CMAKE_C_FLAGS": "$env{COMMON_CXX_FLAGS} $env{DEBUG_CXX_FLAGS} $env{COMMON_SANITIZER_FLAGS} -fanalyzer", 66 | "CMAKE_CXX_FLAGS": "$env{COMMON_CXX_FLAGS} $env{DEBUG_CXX_FLAGS} $env{COMMON_SANITIZER_FLAGS} -fanalyzer" 67 | } 68 | }, 69 | { 70 | "name": "release", 71 | "displayName": "Release Build", 72 | "description": "Release build using Ninja generator", 73 | "inherits": "default", 74 | "binaryDir": "${sourceDir}/build/release", 75 | "cacheVariables": { 76 | "CMAKE_BUILD_TYPE": "Release", 77 | "CMAKE_C_FLAGS": "$env{COMMON_CXX_FLAGS} $env{RELEASE_C_FLAGS}", 78 | "CMAKE_CXX_FLAGS": "$env{COMMON_CXX_FLAGS} $env{RELEASE_CXX_FLAGS}" 79 | } 80 | }, 81 | { 82 | "name": "sanitize-llvm", 83 | "displayName": "Sanitize Build", 84 | "description": "Sanitize build using Ninja generator", 85 | "inherits": "default", 86 | "binaryDir": "${sourceDir}/build/sanitize-llvm", 87 | "cacheVariables": { 88 | "CMAKE_BUILD_TYPE": "Debug", 89 | "CMAKE_C_COMPILER": "/home/user/projects/llvm-project/build/bin/clang-16", 90 | "CMAKE_CXX_COMPILER": "/home/user/projects/llvm-project/build/bin/clang++", 91 | "CMAKE_EXE_LINKER_FLAGS": "-fuse-ld=lld $env{LLVM_SANITIZER_FLAGS}", 92 | "CMAKE_MODULE_LINKER_FLAGS": "-fuse-ld=lld $env{LLVM_SANITIZER_FLAGS}", 93 | "CMAKE_SHARED_LINKER_FLAGS": "-fuse-ld=lld $env{LLVM_SANITIZER_FLAGS}" 94 | }, 95 | "environment": { 96 | "CXX_FLAGS": "$env{COMMON_CXX_FLAGS} $env{DEBUG_CXX_FLAGS} $env{LLVM_SANITIZER_FLAGS} $env{CLANG_ONLY_CXX_FLAGS}" 97 | } 98 | }, 99 | { 100 | "name": "sanitize-gcc", 101 | "displayName": "Sanitize Build with GCC", 102 | "description": "Sanitize build using Ninja generator and GCC", 103 | "inherits": "default", 104 | "binaryDir": "${sourceDir}/build/sanitize-gcc", 105 | "cacheVariables": { 106 | "CMAKE_BUILD_TYPE": "Debug", 107 | "CMAKE_C_COMPILER": "/opt/gcc-latest/bin/gcc", 108 | "CMAKE_CXX_COMPILER": "/opt/gcc-latest/bin/g++", 109 | "CMAKE_EXE_LINKER_FLAGS": "$env{GCC_LINKER_FLAGS} $env{GCC_SANITIZER_FLAGS}", 110 | "CMAKE_MODULE_LINKER_FLAGS": "$env{GCC_LINKER_FLAGS} $env{GCC_SANITIZER_FLAGS}", 111 | "CMAKE_SHARED_LINKER_FLAGS": "$env{GCC_LINKER_FLAGS} $env{GCC_SANITIZER_FLAGS}" 112 | }, 113 | "environment": { 114 | "CXX_FLAGS": "$env{COMMON_CXX_FLAGS} $env{DEBUG_CXX_FLAGS} $env{GCC_SANITIZER_FLAGS} -Og" 115 | } 116 | } 117 | ], 118 | "buildPresets": [ 119 | { 120 | "name": "default", 121 | "configurePreset": "default" 122 | } 123 | ], 124 | "testPresets": [ 125 | { 126 | "name": "default", 127 | "configurePreset": "default", 128 | "output": { 129 | "outputOnFailure": true 130 | }, 131 | "execution": { 132 | "noTestsAction": "error", 133 | "stopOnFailure": true 134 | } 135 | } 136 | ], 137 | "vendor": {} 138 | } -------------------------------------------------------------------------------- /kotlin/gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | # This is normally unused 84 | # shellcheck disable=SC2034 85 | APP_BASE_NAME=${0##*/} 86 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit 87 | 88 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 89 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 118 | 119 | 120 | # Determine the Java command to use to start the JVM. 121 | if [ -n "$JAVA_HOME" ] ; then 122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 123 | # IBM's JDK on AIX uses strange locations for the executables 124 | JAVACMD=$JAVA_HOME/jre/sh/java 125 | else 126 | JAVACMD=$JAVA_HOME/bin/java 127 | fi 128 | if [ ! -x "$JAVACMD" ] ; then 129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 130 | 131 | Please set the JAVA_HOME variable in your environment to match the 132 | location of your Java installation." 133 | fi 134 | else 135 | JAVACMD=java 136 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 137 | 138 | Please set the JAVA_HOME variable in your environment to match the 139 | location of your Java installation." 140 | fi 141 | 142 | # Increase the maximum file descriptors if we can. 143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 144 | case $MAX_FD in #( 145 | max*) 146 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 147 | # shellcheck disable=SC3045 148 | MAX_FD=$( ulimit -H -n ) || 149 | warn "Could not query maximum file descriptor limit" 150 | esac 151 | case $MAX_FD in #( 152 | '' | soft) :;; #( 153 | *) 154 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 155 | # shellcheck disable=SC3045 156 | ulimit -n "$MAX_FD" || 157 | warn "Could not set maximum file descriptor limit to $MAX_FD" 158 | esac 159 | fi 160 | 161 | # Collect all arguments for the java command, stacking in reverse order: 162 | # * args from the command line 163 | # * the main class name 164 | # * -classpath 165 | # * -D...appname settings 166 | # * --module-path (only if needed) 167 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 168 | 169 | # For Cygwin or MSYS, switch paths to Windows format before running java 170 | if "$cygwin" || "$msys" ; then 171 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 172 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 173 | 174 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 175 | 176 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 177 | for arg do 178 | if 179 | case $arg in #( 180 | -*) false ;; # don't mess with options #( 181 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 182 | [ -e "$t" ] ;; #( 183 | *) false ;; 184 | esac 185 | then 186 | arg=$( cygpath --path --ignore --mixed "$arg" ) 187 | fi 188 | # Roll the args list around exactly as many times as the number of 189 | # args, so each arg winds up back in the position where it started, but 190 | # possibly modified. 191 | # 192 | # NB: a `for` loop captures its iteration list before it begins, so 193 | # changing the positional parameters here affects neither the number of 194 | # iterations, nor the values presented in `arg`. 195 | shift # remove old arg 196 | set -- "$@" "$arg" # push replacement arg 197 | done 198 | fi 199 | 200 | # Collect all arguments for the java command; 201 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of 202 | # shell script including quotes and variable substitutions, so put them in 203 | # double quotes to make sure that they get re-expanded; and 204 | # * put everything else in single quotes, so that it's not re-expanded. 205 | 206 | set -- \ 207 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 208 | -classpath "$CLASSPATH" \ 209 | org.gradle.wrapper.GradleWrapperMain \ 210 | "$@" 211 | 212 | # Stop when "xargs" is not available. 213 | if ! command -v xargs >/dev/null 2>&1 214 | then 215 | die "xargs is not available" 216 | fi 217 | 218 | # Use "xargs" to parse quoted args. 219 | # 220 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 221 | # 222 | # In Bash we could simply go: 223 | # 224 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 225 | # set -- "${ARGS[@]}" "$@" 226 | # 227 | # but POSIX shell has neither arrays nor command substitution, so instead we 228 | # post-process each arg (as a line of input to sed) to backslash-escape any 229 | # character that might be a shell metacharacter, then use eval to reverse 230 | # that process (while maintaining the separation between arguments), and wrap 231 | # the whole thing up as a single "set" statement. 232 | # 233 | # This will of course break if any of these variables contains a newline or 234 | # an unmatched quote. 235 | # 236 | 237 | eval "set -- $( 238 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 239 | xargs -n1 | 240 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 241 | tr '\n' ' ' 242 | )" '"$@"' 243 | 244 | exec "$JAVACMD" "$@" 245 | -------------------------------------------------------------------------------- /d/source/app.d: -------------------------------------------------------------------------------- 1 | import core.atomic; 2 | import core.stdc.string; 3 | import core.sys.linux.sys.mman; 4 | import core.sys.posix.fcntl; 5 | import core.sys.posix.unistd; 6 | import core.sync.rwmutex; 7 | 8 | import std; 9 | import std.experimental.logger; 10 | 11 | //////////////////////////////////////////// 12 | // CONSTANTS 13 | //////////////////////////////////////////// 14 | 15 | enum PAGE_SIZE = 4096; 16 | enum BUF_POOL_NUM_PAGES = 1000; 17 | 18 | //////////////////////////////////////////// 19 | // TYPEDEFS 20 | //////////////////////////////////////////// 21 | 22 | alias Page = ubyte[PAGE_SIZE]; // A view of the bytes in a page 23 | 24 | alias lsn_t = uint64_t; // A log sequence number 25 | alias frame_idx_t = size_t; // A frame index 26 | 27 | alias db_id_t = uint32_t; // A database ID 28 | alias table_id_t = uint32_t; // A table ID 29 | alias page_num_t = uint32_t; // A page number 30 | alias slot_num_t = uint32_t; // A slot number 31 | 32 | //////////////////////////////////////////// 33 | // HELPER FUNCTIONS 34 | //////////////////////////////////////////// 35 | 36 | struct TableLocation 37 | { 38 | db_id_t db_id; 39 | table_id_t table_id; 40 | } 41 | 42 | // Equivalent to "RelFileLocator" in PostgreSQL, where "page_num" is the "block number" 43 | struct PageLocation 44 | { 45 | db_id_t db_id; 46 | table_id_t table_id; 47 | page_num_t page_num; 48 | } 49 | 50 | struct RecordID 51 | { 52 | page_num_t page_num; 53 | slot_num_t slot_num; 54 | } 55 | 56 | //////////////////////////////////////////// 57 | // HEAP PAGE LAYOUT 58 | //////////////////////////////////////////// 59 | 60 | enum HEAP_PAGE_MAX_SLOTS = (PAGE_SIZE - HeapPageHeader.sizeof) / HeapPageSlot.sizeof; 61 | 62 | struct HeapPageHeader 63 | { 64 | lsn_t lsn; 65 | page_num_t page_id; 66 | uint32_t num_slots; 67 | uint32_t free_space; 68 | uint32_t num_records; 69 | } 70 | 71 | struct HeapPageSlot 72 | { 73 | uint16_t offset; 74 | uint32_t length; 75 | } 76 | 77 | align(PAGE_SIZE) union HeapPage 78 | { 79 | ubyte[PAGE_SIZE] data; 80 | struct 81 | { 82 | HeapPageHeader header; 83 | HeapPageSlot[HEAP_PAGE_MAX_SLOTS] slots; 84 | } 85 | 86 | // Tombstone marker for deleted records 87 | static ushort TOMBSTONE = 0xFFFF; 88 | 89 | size_t get_free_space() 90 | { 91 | return header.free_space - HeapPageHeader.sizeof - HeapPageSlot.sizeof * header.num_slots; 92 | } 93 | 94 | ubyte[] get_record(slot_num_t slot_num) 95 | { 96 | infof("Getting record %d", slot_num); 97 | assert(slot_num < header.num_slots); 98 | auto slot = slots[slot_num]; 99 | infof("Record offset: %d", slot.offset); 100 | if (slot.offset == TOMBSTONE) 101 | { 102 | infof("Record is deleted"); 103 | return null; 104 | } 105 | return data[slot.offset .. slot.offset + slot.length]; 106 | } 107 | 108 | RecordID insert_record(ubyte[] record) 109 | { 110 | infof("Inserting record of length %d", record.length); 111 | assert(record.length <= get_free_space(), "Record is too large to fit on page"); 112 | slot_num_t slot_num = header.num_slots++; 113 | // Get a pointer to the slot 114 | auto slot = &slots[slot_num]; 115 | slot.offset = cast(ushort)(header.free_space - record.length); 116 | slot.length = cast(uint) record.length; 117 | infof("Record offset: %d", slot.offset); 118 | infof("Record length: %d", slot.length); 119 | memcpy(data.ptr + slot.offset, record.ptr, record.length); 120 | header.free_space -= record.length; 121 | header.num_records++; 122 | return RecordID(header.page_id, slot_num); 123 | } 124 | 125 | void delete_record(slot_num_t slot_num) 126 | { 127 | assert(slot_num < header.num_slots); 128 | auto slot = &slots[slot_num]; 129 | slot.offset = TOMBSTONE; 130 | header.free_space += slot.length; 131 | header.num_records--; 132 | } 133 | 134 | void inititalize(page_num_t page_id) 135 | { 136 | header.lsn = 0; 137 | header.page_id = page_id; 138 | header.num_slots = 0; 139 | header.free_space = PAGE_SIZE; 140 | header.num_records = 0; 141 | } 142 | } 143 | 144 | static assert(HeapPage.sizeof == PAGE_SIZE); 145 | 146 | //////////////////////////////////////////// 147 | // DATABASE PAGE LAYOUT 148 | //////////////////////////////////////////// 149 | 150 | union DatabasePage 151 | { 152 | HeapPage heap_page; 153 | } 154 | 155 | //////////////////////////////////////////// 156 | // DISK MANAGER 157 | //////////////////////////////////////////// 158 | 159 | interface IDiskManager 160 | { 161 | void read_page(PageLocation page_location, Page* page); 162 | void write_page(PageLocation page_location, Page* page); 163 | } 164 | 165 | class DiskManager : IDiskManager 166 | { 167 | private 168 | { 169 | static DB_ROOT = "db"; 170 | // Map of (database ID, table ID) to file descriptor 171 | int[TableLocation] fd_map; 172 | } 173 | 174 | string get_db_file_path(db_id_t db_id) 175 | { 176 | return format("%s/%d", DB_ROOT, db_id); 177 | } 178 | 179 | string get_table_file_path(TableLocation table_location) 180 | { 181 | string db_file_path = get_db_file_path(table_location.db_id); 182 | return format("%s/%d", db_file_path, table_location.table_id); 183 | } 184 | 185 | private int open_table(TableLocation table_location) 186 | { 187 | infof("Reading table %d from database %d", table_location.table_id, table_location.db_id); 188 | 189 | auto db_dir = get_db_file_path(table_location.db_id); 190 | auto table_file = get_table_file_path(table_location); 191 | 192 | mkdirRecurse(db_dir); 193 | 194 | auto fd = open(table_file.ptr, O_RDWR | O_CREAT | O_DIRECT, octal!644); 195 | assert(fd != -1); 196 | 197 | return fd; 198 | } 199 | 200 | override void read_page(PageLocation page_location, Page* page) 201 | { 202 | infof("Reading page %d from table %d in database %d", 203 | page_location.page_num, page_location.table_id, page_location.db_id); 204 | 205 | auto table_location = TableLocation(page_location.db_id, page_location.table_id); 206 | if (table_location !in fd_map) 207 | { 208 | fd_map[table_location] = open_table(table_location); 209 | } 210 | 211 | auto fd = fd_map[table_location]; 212 | auto offset = page_location.page_num * PAGE_SIZE; 213 | auto bytes_read = pread(fd, page, PAGE_SIZE, offset); 214 | 215 | // Special case for existing but empty file -- return all zeros 216 | if (bytes_read == 0) 217 | { 218 | memset(page, 0, PAGE_SIZE); 219 | return; 220 | } 221 | 222 | assert(bytes_read == PAGE_SIZE); 223 | } 224 | 225 | override void write_page(PageLocation page_location, Page* page) 226 | { 227 | infof("Writing page %d to table %d in database %d", 228 | page_location.page_num, page_location.table_id, page_location.db_id); 229 | 230 | auto table_location = TableLocation(page_location.db_id, page_location.table_id); 231 | if (table_location !in fd_map) 232 | { 233 | fd_map[table_location] = open_table(table_location); 234 | } 235 | 236 | auto fd = fd_map[table_location]; 237 | auto offset = page_location.page_num * PAGE_SIZE; 238 | auto bytes_written = pwrite(fd, page, PAGE_SIZE, offset); 239 | assert(bytes_written == PAGE_SIZE); 240 | } 241 | } 242 | 243 | //////////////////////////////////////////// 244 | // EVICTION POLICY 245 | //////////////////////////////////////////// 246 | 247 | interface IEvictionPolicy 248 | { 249 | frame_idx_t choose_victim_frame(); // Returns the frame index of the victim frame 250 | void frame_pinned(frame_idx_t frame_idx); // Callback for when a frame is pinned 251 | void frame_unpinned(frame_idx_t frame_idx); // Callback for when a frame is unpinned 252 | } 253 | 254 | class ClockEvictionPolicy : IEvictionPolicy 255 | { 256 | private 257 | { 258 | frame_idx_t clock_hand; 259 | bool[BUF_POOL_NUM_PAGES] is_pinned; 260 | bool[BUF_POOL_NUM_PAGES] is_referenced; 261 | } 262 | 263 | this() 264 | { 265 | clock_hand = 0; 266 | } 267 | 268 | frame_idx_t choose_victim_frame() 269 | { 270 | while (true) 271 | { 272 | if (!is_pinned[clock_hand] && !is_referenced[clock_hand]) 273 | { 274 | return clock_hand; 275 | } 276 | 277 | is_referenced[clock_hand] = false; 278 | clock_hand = (clock_hand + 1) % BUF_POOL_NUM_PAGES; 279 | } 280 | } 281 | 282 | void frame_pinned(frame_idx_t frame_idx) 283 | { 284 | is_pinned[frame_idx] = true; 285 | } 286 | 287 | void frame_unpinned(frame_idx_t frame_idx) 288 | { 289 | is_pinned[frame_idx] = false; 290 | } 291 | } 292 | 293 | //////////////////////////////////////////// 294 | // BUFFER POOL 295 | //////////////////////////////////////////// 296 | 297 | interface IBufferPool 298 | { 299 | Page* fetch_page(PageLocation page_location); 300 | void unpin_page(PageLocation page_location, bool is_dirty = false); 301 | void flush_page(PageLocation page_location); 302 | void flush_all_pages(); 303 | } 304 | 305 | class BufferPool : IBufferPool 306 | { 307 | private 308 | { 309 | align(PAGE_SIZE) ubyte[PAGE_SIZE * BUF_POOL_NUM_PAGES] data; 310 | 311 | // Struct-of-Arrays for the buffer pool frames metadata 312 | // Atomic because multiple threads may be accessing the same frame 313 | int[BUF_POOL_NUM_PAGES] pin_count; 314 | bool[BUF_POOL_NUM_PAGES] is_dirty; 315 | bool[BUF_POOL_NUM_PAGES] ref_bit; 316 | 317 | // Free list 318 | frame_idx_t[] free_list; 319 | 320 | // Lookup table for page IDs -> frame indices (aka the "page table") 321 | frame_idx_t[PageLocation] page_table; 322 | PageLocation[frame_idx_t] reverse_page_table; // Used for eviction only, to look up the page location of a frame 323 | ReadWriteMutex page_table_mutex; 324 | 325 | IEvictionPolicy eviction_policy = new ClockEvictionPolicy(); 326 | IDiskManager disk_manager = new DiskManager(); 327 | } 328 | 329 | this() 330 | { 331 | mmap(&data, data.sizeof, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); 332 | page_table_mutex = new ReadWriteMutex(); 333 | 334 | // Initialize the free list 335 | foreach (i; 0 .. BUF_POOL_NUM_PAGES) 336 | { 337 | free_list ~= i; 338 | } 339 | } 340 | 341 | //////////////////////////////////////////// 342 | // INVARIANTS 343 | //////////////////////////////////////////// 344 | 345 | invariant 346 | { 347 | // Pin count is non-negative 348 | foreach (i; 0 .. BUF_POOL_NUM_PAGES) 349 | { 350 | assert(pin_count[i] >= 0); 351 | } 352 | 353 | // Free list contains only valid frame indices 354 | foreach (i; 0 .. free_list.length) 355 | { 356 | assert(free_list[i] >= 0 && free_list[i] < BUF_POOL_NUM_PAGES); 357 | } 358 | 359 | // Free list contains no duplicates 360 | foreach (i; 0 .. free_list.length) 361 | { 362 | foreach (j; 0 .. free_list.length) 363 | { 364 | if (i != j) 365 | { 366 | assert(free_list[i] != free_list[j]); 367 | } 368 | } 369 | } 370 | 371 | // Free list contains no frames that are pinned 372 | foreach (i; 0 .. free_list.length) 373 | { 374 | assert(pin_count[free_list[i]] == 0); 375 | } 376 | 377 | // Free list contains no frames that are dirty 378 | foreach (i; 0 .. free_list.length) 379 | { 380 | assert(!is_dirty[free_list[i]]); 381 | } 382 | 383 | // Free list contains no frames that are referenced 384 | foreach (i; 0 .. free_list.length) 385 | { 386 | assert(!ref_bit[free_list[i]]); 387 | } 388 | 389 | // Page table contains only valid frame indices 390 | foreach (page_location, frame_idx; page_table) 391 | { 392 | assert(frame_idx >= 0 && frame_idx < BUF_POOL_NUM_PAGES); 393 | } 394 | 395 | // Page table contains no duplicates 396 | foreach (page_location, frame_idx; page_table) 397 | { 398 | foreach (page_location2, frame_idx2; page_table) 399 | { 400 | if (page_location != page_location2) 401 | { 402 | assert(frame_idx != frame_idx2); 403 | } 404 | } 405 | } 406 | } 407 | 408 | //////////////////////////////////////////// 409 | // PRIVATE METHODS 410 | //////////////////////////////////////////// 411 | 412 | private Page* get_frame(frame_idx_t frame_idx) 413 | { 414 | auto ptr = data.ptr + (frame_idx * PAGE_SIZE); 415 | return cast(Page*) ptr; 416 | } 417 | 418 | // Pin a frame 419 | private void pin_frame(frame_idx_t frame_idx) 420 | { 421 | pin_count[frame_idx]++; 422 | eviction_policy.frame_pinned(frame_idx); 423 | } 424 | 425 | // Unpin a frame 426 | private void unpin_frame(frame_idx_t frame_idx) 427 | { 428 | pin_count[frame_idx]--; 429 | eviction_policy.frame_unpinned(frame_idx); 430 | } 431 | 432 | private void add_page_table_entry(PageLocation page_location, frame_idx_t frame_idx) 433 | { 434 | page_table[page_location] = frame_idx; 435 | reverse_page_table[frame_idx] = page_location; 436 | } 437 | 438 | private void remove_page_table_entry(PageLocation page_location) 439 | { 440 | auto frame_idx = page_table[page_location]; 441 | page_table.remove(page_location); 442 | reverse_page_table[frame_idx] = PageLocation.init; 443 | } 444 | 445 | private frame_idx_t pop_free_frame() 446 | { 447 | assert(free_list.length > 0); 448 | frame_idx_t frame_idx = free_list.front; 449 | free_list.popFront(); 450 | return frame_idx; 451 | } 452 | 453 | private frame_idx_t get_free_frame() 454 | { 455 | if (free_list.length > 0) 456 | { 457 | return pop_free_frame(); 458 | } 459 | else 460 | { 461 | return eviction_policy.choose_victim_frame(); 462 | } 463 | } 464 | 465 | private void add_free_frame(frame_idx_t frame_idx) 466 | { 467 | free_list ~= frame_idx; 468 | } 469 | 470 | //////////////////////////////////////////// 471 | // PUBLIC METHODS 472 | //////////////////////////////////////////// 473 | 474 | override Page* fetch_page(PageLocation page_location) 475 | { 476 | // Acquire a read lock (latch) on the page table 477 | synchronized (page_table_mutex.reader) 478 | { 479 | // Check if the page is already in the buffer pool 480 | if (page_location in page_table) 481 | { 482 | // Get the frame index 483 | auto frame_idx = page_table[page_location]; 484 | 485 | // Pin the frame 486 | pin_frame(frame_idx); 487 | 488 | // Return the frame 489 | return get_frame(frame_idx); 490 | } 491 | } 492 | 493 | // Acquire a write lock (latch) on the page table 494 | synchronized (page_table_mutex.writer) 495 | { 496 | // Get a free frame 497 | auto frame_idx = get_free_frame(); 498 | 499 | // Read the page from disk 500 | disk_manager.read_page(page_location, get_frame(frame_idx)); 501 | 502 | // Add the page to the page table 503 | add_page_table_entry(page_location, frame_idx); 504 | 505 | // Pin the frame 506 | pin_frame(frame_idx); 507 | 508 | // Return the frame 509 | return get_frame(frame_idx); 510 | } 511 | } 512 | 513 | override void unpin_page(PageLocation page_location, bool dirty = false) 514 | { 515 | // Acquire a read lock (latch) on the page table 516 | // Get the frame index 517 | frame_idx_t frame_idx; 518 | synchronized (page_table_mutex.reader) 519 | { 520 | frame_idx = page_table[page_location]; 521 | } 522 | 523 | // Unpin the frame 524 | unpin_frame(frame_idx); 525 | 526 | // Mark the frame as dirty if necessary 527 | is_dirty[frame_idx] |= dirty; 528 | } 529 | 530 | override void flush_page(PageLocation page_location) 531 | { 532 | // Acquire a read lock (latch) on the page table 533 | // Get the frame index 534 | frame_idx_t frame_idx; 535 | synchronized (page_table_mutex.reader) 536 | { 537 | frame_idx = page_table[page_location]; 538 | } 539 | 540 | // Write the page to disk 541 | disk_manager.write_page(page_location, get_frame(frame_idx)); 542 | 543 | // Mark the frame as clean 544 | is_dirty[frame_idx] = false; 545 | } 546 | 547 | override void flush_all_pages() 548 | { 549 | // Acquire a read lock (latch) on the page table 550 | synchronized (page_table_mutex.reader) 551 | { 552 | // Iterate over all pages in the page table 553 | foreach (page_location, frame_idx; page_table) 554 | { 555 | // Write the page to disk 556 | disk_manager.write_page(page_location, get_frame(frame_idx)); 557 | } 558 | } 559 | 560 | // Mark all frames as clean 561 | is_dirty[] = false; 562 | } 563 | 564 | } 565 | 566 | //////////////////////////////////////////// 567 | // HEAP FILE ACCESS 568 | //////////////////////////////////////////// 569 | 570 | class HeapFile 571 | { 572 | private 573 | { 574 | BufferPool* buffer_pool; 575 | DiskManager* disk_manager; 576 | db_id_t db_id; 577 | table_id_t table_id; 578 | page_num_t num_pages; 579 | } 580 | 581 | this(BufferPool* buffer_pool, DiskManager* disk_manager, db_id_t db_id, table_id_t table_id) 582 | { 583 | infof("HeapFile: Opening file for database %d, table %d", db_id, table_id); 584 | this.buffer_pool = buffer_pool; 585 | this.disk_manager = disk_manager; 586 | this.db_id = db_id; 587 | this.table_id = table_id; 588 | 589 | // Get the number of pages in the file 590 | auto heap_file_path = disk_manager.get_table_file_path(TableLocation(db_id, table_id)); 591 | auto heap_file = DirEntry(heap_file_path); 592 | this.num_pages = heap_file.statBuf.st_size.to!(uint) / PAGE_SIZE; 593 | 594 | // If the file is empty, add a new page 595 | infof("HeapFile: File has %d pages", num_pages); 596 | if (num_pages == 0) 597 | { 598 | infof("HeapFile: Adding new page to empty file"); 599 | add_page(); 600 | } 601 | } 602 | 603 | //////////////////////////////////////////// 604 | // PRIVATE METHODS 605 | //////////////////////////////////////////// 606 | 607 | private PageLocation get_page_location(page_num_t page_num) 608 | { 609 | return PageLocation(db_id, table_id, page_num); 610 | } 611 | 612 | private Page* get_page(page_num_t page_num) 613 | { 614 | return buffer_pool.fetch_page(get_page_location(page_num)); 615 | } 616 | 617 | private void unpin_page(page_num_t page_num, bool dirty = false) 618 | { 619 | buffer_pool.unpin_page(get_page_location(page_num), dirty); 620 | } 621 | 622 | private void flush_page(page_num_t page_num) 623 | { 624 | buffer_pool.flush_page(get_page_location(page_num)); 625 | } 626 | 627 | private void add_page() 628 | { 629 | // Create a new page 630 | ubyte[PAGE_SIZE] page_data; 631 | HeapPage* heap_page = cast(HeapPage*) page_data.ptr; 632 | heap_page.inititalize(num_pages); 633 | 634 | // Write the page to disk 635 | disk_manager.write_page(get_page_location(num_pages), &page_data); 636 | 637 | // Increment the number of pages 638 | num_pages++; 639 | } 640 | 641 | // A "get_page" method used for testing/debugging 642 | Page* debug_get_page(page_num_t page_num) 643 | { 644 | return get_page(page_num); 645 | } 646 | 647 | //////////////////////////////////////////// 648 | // PUBLIC METHODS 649 | //////////////////////////////////////////// 650 | 651 | void insert_record(ubyte[] record) 652 | { 653 | infof("HeapFile: Inserting record of length %d", record.length); 654 | // Get the last page 655 | auto page = get_page(num_pages - 1); 656 | HeapPage* heap_page = cast(HeapPage*) page; 657 | 658 | // Check if the page has enough space for the record 659 | if (heap_page.get_free_space() >= record.length) 660 | { 661 | infof("HeapFile: Inserting record into existing page"); 662 | 663 | // Insert the record into the page 664 | heap_page.insert_record(record); 665 | 666 | // Unpin the page 667 | unpin_page(num_pages - 1, true); 668 | } 669 | else 670 | { 671 | infof("HeapFile: Adding new page to insert record"); 672 | 673 | // Unpin the page 674 | unpin_page(num_pages - 1); 675 | 676 | // Create a new page 677 | ubyte[PAGE_SIZE] page_data; 678 | HeapPage* new_heap_page = cast(HeapPage*) page_data; 679 | 680 | // Insert the record into the page 681 | new_heap_page.insert_record(record); 682 | 683 | // Increment the number of pages 684 | num_pages++; 685 | 686 | // Unpin the page 687 | unpin_page(num_pages - 1, true); 688 | } 689 | } 690 | 691 | void delete_record(RecordID record_id) 692 | { 693 | // Get the page 694 | auto page = get_page(record_id.page_num); 695 | HeapPage* heap_page = cast(HeapPage*) page; 696 | 697 | // Delete the record 698 | heap_page.delete_record(record_id.slot_num); 699 | 700 | // Unpin the page 701 | unpin_page(record_id.page_num, true); 702 | } 703 | 704 | ubyte[] get_record(RecordID record_id) 705 | { 706 | infof("Getting record %d from page %d", record_id.slot_num, record_id.page_num); 707 | 708 | // Get the page 709 | auto page = get_page(record_id.page_num); 710 | HeapPage* heap_page = cast(HeapPage*) page; 711 | 712 | // Get the record 713 | auto record = heap_page.get_record(record_id.slot_num); 714 | 715 | // Unpin the page 716 | unpin_page(record_id.page_num); 717 | 718 | return record; 719 | } 720 | 721 | void flush_all_pages() 722 | { 723 | // Flush all pages 724 | buffer_pool.flush_all_pages(); 725 | } 726 | } 727 | 728 | //////////////////////////////////////////// 729 | // MAIN 730 | //////////////////////////////////////////// 731 | 732 | void main() 733 | { 734 | auto disk_manager = new DiskManager(); 735 | 736 | // Initialize the buffer pool 737 | auto buffer_pool = new BufferPool(); 738 | scope (exit) 739 | { 740 | buffer_pool.flush_all_pages(); 741 | } 742 | 743 | // Create a heap file 744 | auto heap_file = new HeapFile(&buffer_pool, &disk_manager, 0, 0); 745 | 746 | // Insert a record 747 | ubyte[] record = cast(ubyte[]) "Hello, world!".dup; 748 | heap_file.insert_record(record); 749 | 750 | // Insert another record 751 | ubyte[] record2 = cast(ubyte[]) "Hello, world! 2".dup; 752 | heap_file.insert_record(record2); 753 | 754 | // Get the records 755 | auto first_record = heap_file.get_record(RecordID(0, 0)); 756 | auto second_record = heap_file.get_record(RecordID(0, 1)); 757 | 758 | // Print the records 759 | writeln(cast(string) first_record); 760 | writeln(cast(string) second_record); 761 | 762 | // Delete the first record 763 | heap_file.delete_record(RecordID(0, 0)); 764 | 765 | // Check that the first record is gone 766 | auto first_record2 = heap_file.get_record(RecordID(0, 0)); 767 | writeln(cast(string) first_record2); 768 | assert(first_record2 is null); 769 | 770 | // Check that the second record is still there 771 | auto second_record2 = heap_file.get_record(RecordID(0, 1)); 772 | writeln(cast(string) second_record2); 773 | } 774 | -------------------------------------------------------------------------------- /cpp/src/database.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | #include 21 | #include 22 | 23 | //////////////////////////////////////////////////////////////////////////////// 24 | // CONSTANTS 25 | //////////////////////////////////////////////////////////////////////////////// 26 | 27 | static const std::filesystem::path DATABASE_PATH = "/tmp/mini-sql-engine"; 28 | 29 | constexpr size_t PAGE_SIZE = 4096; 30 | constexpr size_t BUFFER_POOL_SIZE = 1000; 31 | 32 | //////////////////////////////////////////////////////////////////////////////// 33 | // TYPEDEFS 34 | //////////////////////////////////////////////////////////////////////////////// 35 | 36 | using lsn_t = uint64_t; 37 | using txn_id_t = uint32_t; 38 | using page_id_t = uint32_t; 39 | using table_id_t = uint32_t; 40 | using db_id_t = uint32_t; 41 | using frame_idx_t = size_t; 42 | 43 | // Slotted Pages 44 | using slot_id_t = uint16_t; 45 | using offset_t = uint16_t; 46 | using length_t = uint16_t; 47 | 48 | // Page buffers 49 | using Page = std::span; 50 | using PageConst = std::span; 51 | 52 | // Record buffers 53 | using Record = std::span; 54 | using RecordConst = std::span; 55 | 56 | using read_lock = std::shared_lock; 57 | using write_lock = std::unique_lock; 58 | 59 | //////////////////////////////////////////////////////////////////////////////// 60 | // HELPER FUNCTIONS 61 | //////////////////////////////////////////////////////////////////////////////// 62 | 63 | inline void 64 | hash_combine(std::size_t& seed) 65 | { 66 | } 67 | 68 | template 69 | inline void 70 | hash_combine(std::size_t& seed, const T& v, Rest... rest) 71 | { 72 | std::hash hasher; 73 | seed ^= hasher(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2); 74 | hash_combine(seed, rest...); 75 | } 76 | 77 | // Create a string-literal for std::byte 78 | constexpr std::byte 79 | operator""_b(char c) 80 | { 81 | return std::byte { static_cast(c) }; 82 | } 83 | 84 | constexpr std::span 85 | operator""_b(const char* str, size_t size) 86 | { 87 | std::byte* bytes = new std::byte[size]; 88 | for (size_t i = 0; i < size; i++) 89 | { 90 | bytes[i] = std::byte { static_cast(str[i]) }; 91 | } 92 | return std::span(bytes, size); 93 | } 94 | 95 | //////////////////////////////////////////////////////////////////////////////// 96 | // STRUCTS 97 | //////////////////////////////////////////////////////////////////////////////// 98 | 99 | enum class PageType : uint8_t 100 | { 101 | HeapPage = 0, 102 | FSMPage = 1, 103 | IndexPage = 2, 104 | }; 105 | 106 | static constexpr std::string 107 | get_page_type_extension(PageType page_type) 108 | { 109 | switch (page_type) 110 | { 111 | case PageType::HeapPage: return "heap"; 112 | case PageType::FSMPage: return "fsm"; 113 | case PageType::IndexPage: return "index"; 114 | } 115 | std::unreachable(); 116 | } 117 | 118 | struct PageLocation 119 | { 120 | PageType page_type; 121 | db_id_t db_id; 122 | table_id_t table_id; 123 | page_id_t page_id; 124 | 125 | auto operator<=>(const PageLocation&) const = default; 126 | 127 | static constexpr size_t hash(const PageLocation& page_location) 128 | { 129 | size_t seed = 0; 130 | hash_combine(seed, 131 | page_location.page_type, 132 | page_location.db_id, 133 | page_location.table_id, 134 | page_location.page_id); 135 | return seed; 136 | } 137 | }; 138 | 139 | struct RecordID 140 | { 141 | page_id_t page_id; 142 | slot_id_t slot_id; 143 | 144 | auto operator<=>(const RecordID&) const = default; 145 | }; 146 | 147 | //////////////////////////////////////////////////////////////////////////////// 148 | // HEAP PAGE 149 | //////////////////////////////////////////////////////////////////////////////// 150 | 151 | struct HeapPageHeader 152 | { 153 | page_id_t page_id; 154 | lsn_t lsn; 155 | uint16_t num_records; 156 | uint16_t free_space_offset; 157 | }; 158 | 159 | struct HeapPageSlot 160 | { 161 | offset_t offset; 162 | length_t length; 163 | 164 | auto operator<=>(const HeapPageSlot&) const = default; 165 | }; 166 | 167 | union HeapPage 168 | { 169 | std::byte data[PAGE_SIZE]; 170 | 171 | struct 172 | { 173 | HeapPageHeader header; 174 | HeapPageSlot slots[(PAGE_SIZE - sizeof(HeapPageHeader)) / sizeof(HeapPageSlot)]; 175 | }; 176 | 177 | void init(page_id_t page_id); 178 | 179 | std::optional get_record(slot_id_t slot_id); 180 | std::optional insert_record(RecordConst record); 181 | void delete_record(slot_id_t slot_id); 182 | 183 | size_t get_free_space(); 184 | bool has_enough_free_space(size_t size); 185 | }; 186 | 187 | static_assert(sizeof(HeapPage) == PAGE_SIZE); 188 | 189 | union DatabasePage 190 | { 191 | HeapPage heap_page; 192 | }; 193 | 194 | void 195 | HeapPage::init(page_id_t page_id) 196 | 197 | { 198 | SPDLOG_INFO("Initializing page {}", page_id); 199 | SPDLOG_INFO(" free_space_offset [before] = {}", header.free_space_offset); 200 | header.page_id = page_id; 201 | header.lsn = 0; 202 | header.num_records = 0; 203 | header.free_space_offset = PAGE_SIZE; 204 | SPDLOG_INFO(" free_space_offset [after] = {}", header.free_space_offset); 205 | } 206 | 207 | std::optional 208 | HeapPage::get_record(slot_id_t slot_id) 209 | { 210 | SPDLOG_INFO("Getting record with slot_id {} from page {}", slot_id, header.page_id); 211 | 212 | if (slot_id >= header.num_records) 213 | { 214 | return std::nullopt; 215 | } 216 | 217 | auto slot = slots[slot_id]; 218 | return Record(data + slot.offset, slot.length); 219 | } 220 | 221 | std::optional 222 | HeapPage::insert_record(RecordConst record) 223 | { 224 | SPDLOG_INFO("Inserting record of size {} into page {}", record.size(), header.page_id); 225 | 226 | if (!has_enough_free_space(record.size())) 227 | { 228 | SPDLOG_INFO(" Not enough free space in page {}", header.page_id); 229 | return std::nullopt; 230 | } 231 | 232 | if (header.free_space_offset == 65523) 233 | { 234 | header.free_space_offset = PAGE_SIZE; 235 | } 236 | 237 | SPDLOG_INFO("header.page_id = {}", header.page_id); 238 | SPDLOG_INFO("header.num_records = {}", header.num_records); 239 | SPDLOG_INFO("header.free_space_offset = {}", header.free_space_offset); 240 | 241 | auto slot_id = header.num_records; 242 | uint16_t offset = header.free_space_offset - record.size_bytes(); 243 | uint16_t length = record.size(); 244 | SPDLOG_INFO( 245 | " Inserting record into slot {} at offset {} with length {}", slot_id, offset, length); 246 | 247 | std::memcpy(data + offset, record.data(), record.size()); 248 | 249 | slots[slot_id] = { offset, length }; 250 | header.num_records++; 251 | header.free_space_offset = offset; 252 | 253 | return RecordID { header.page_id, slot_id }; 254 | } 255 | 256 | void 257 | HeapPage::delete_record(slot_id_t slot_id) 258 | { 259 | if (slot_id >= header.num_records) 260 | { 261 | throw std::runtime_error("Invalid slot id"); 262 | } 263 | 264 | auto slot = slots[slot_id]; 265 | std::memset(data + slot.offset, 0, slot.length); 266 | 267 | slots[slot_id] = { 0, 0 }; 268 | header.num_records--; 269 | } 270 | 271 | size_t 272 | HeapPage::get_free_space() 273 | { 274 | return header.free_space_offset - sizeof(HeapPageHeader) - 275 | sizeof(HeapPageSlot) * header.num_records; 276 | } 277 | 278 | bool 279 | HeapPage::has_enough_free_space(size_t size) 280 | { 281 | return get_free_space() >= size; 282 | } 283 | 284 | //////////////////////////////////////////////////////////////////////////////// 285 | // INTERFACES 286 | //////////////////////////////////////////////////////////////////////////////// 287 | 288 | class IDiskManager 289 | { 290 | 291 | public: 292 | virtual ~IDiskManager() = default; 293 | 294 | virtual void read_page(PageLocation page_location, DatabasePage* page) = 0; 295 | virtual void write_page(PageLocation pagpage_locatione_id, DatabasePage* page) = 0; 296 | 297 | virtual std::filesystem::path get_page_filepath(PageLocation page_location) = 0; 298 | }; 299 | 300 | class IBufferPool 301 | { 302 | 303 | public: 304 | virtual ~IBufferPool() = default; 305 | 306 | virtual DatabasePage* fetch_page(PageLocation page_location) = 0; 307 | virtual void unpin(PageLocation page_location, bool is_dirty) = 0; 308 | 309 | virtual void flush(PageLocation page_location) = 0; 310 | virtual void flush_all() = 0; 311 | }; 312 | 313 | class IHeapTable 314 | { 315 | 316 | public: 317 | virtual ~IHeapTable() = default; 318 | 319 | virtual std::optional insert_record(RecordConst record) = 0; 320 | virtual std::optional get_record(RecordID record_id) = 0; 321 | virtual void delete_record(RecordID record_id) = 0; 322 | 323 | virtual void scan(std::function callback) = 0; 324 | }; 325 | 326 | class IDatabase 327 | { 328 | 329 | public: 330 | virtual ~IDatabase() = default; 331 | 332 | virtual IHeapTable* get_table(table_id_t table_id) = 0; 333 | virtual IHeapTable* create_table() = 0; 334 | }; 335 | 336 | //////////////////////////////////////////////////////////////////////////////// 337 | // DISK MANAGER 338 | //////////////////////////////////////////////////////////////////////////////// 339 | 340 | class DiskManager : public IDiskManager 341 | { 342 | private: 343 | std::filesystem::path m_db_path; 344 | std::unordered_map m_fd_cache; 345 | std::shared_mutex m_fd_cache_mutex; 346 | 347 | // Initializes the database directory 348 | void init_db_dir(const std::filesystem::path& db_path); 349 | // Gets or creates a file descriptor for the given path 350 | int get_or_create_fd(const std::filesystem::path& path); 351 | 352 | public: 353 | explicit DiskManager(std::filesystem::path db_path = DATABASE_PATH); 354 | 355 | void read_page(PageLocation page_location, DatabasePage* page) override; 356 | void write_page(PageLocation page_location, DatabasePage* page) override; 357 | 358 | std::filesystem::path get_page_filepath(PageLocation page_location) override; 359 | }; 360 | 361 | DiskManager::DiskManager(std::filesystem::path db_path) 362 | : m_db_path(std::move(db_path)) 363 | { 364 | SPDLOG_INFO("Creating disk manager"); 365 | SPDLOG_INFO("Database path: {}", m_db_path.string()); 366 | init_db_dir(m_db_path); 367 | } 368 | 369 | void 370 | DiskManager::init_db_dir(const std::filesystem::path& db_path) 371 | { 372 | SPDLOG_INFO("Initializing disk manager"); 373 | if (!std::filesystem::exists(db_path)) 374 | { 375 | SPDLOG_INFO("Creating database directory: {}", db_path.string()); 376 | std::filesystem::create_directories(db_path); 377 | } 378 | } 379 | 380 | std::filesystem::path 381 | DiskManager::get_page_filepath(PageLocation page_location) 382 | { 383 | SPDLOG_INFO("Getting page file path for ({}:{}:{})", 384 | page_location.db_id, 385 | page_location.table_id, 386 | page_location.page_id); 387 | 388 | auto extension = get_page_type_extension(page_location.page_type); 389 | 390 | return std::format("{}/{}/{}/table.{}", 391 | m_db_path.string(), 392 | page_location.db_id, 393 | page_location.table_id, 394 | extension); 395 | } 396 | 397 | int 398 | DiskManager::get_or_create_fd(const std::filesystem::path& path) 399 | { 400 | std::filesystem::path db_path = path; 401 | // Read lock the cache 402 | { 403 | std::shared_lock fd_cache_lock(m_fd_cache_mutex); 404 | if (m_fd_cache.contains(db_path)) 405 | { 406 | return m_fd_cache[db_path]; 407 | } 408 | } 409 | // Write lock the cache 410 | { 411 | std::unique_lock fd_cache_write_lock(m_fd_cache_mutex); 412 | if (m_fd_cache.contains(db_path)) 413 | { 414 | return m_fd_cache[db_path]; 415 | } 416 | 417 | // Check if the file exists 418 | if (!std::filesystem::exists(db_path)) 419 | { 420 | SPDLOG_INFO("Creating file: {}", db_path.string()); 421 | std::filesystem::create_directories(db_path.parent_path()); 422 | } 423 | 424 | int fd = open(db_path.c_str(), O_RDWR | O_CREAT | O_CLOEXEC | O_DIRECT, 0666); 425 | if (fd == -1) 426 | { 427 | throw std::runtime_error("Failed to open file"); 428 | } 429 | 430 | m_fd_cache[db_path] = fd; 431 | return fd; 432 | } 433 | } 434 | 435 | void 436 | DiskManager::read_page(PageLocation page_location, DatabasePage* page) 437 | { 438 | std::filesystem::path path = get_page_filepath(page_location); 439 | int fd = get_or_create_fd(path); 440 | 441 | ssize_t bytes_read = pread(fd, page, PAGE_SIZE, 0); 442 | if (bytes_read == 0) 443 | { 444 | // Special case for empty files 445 | std::memset(page, 0, PAGE_SIZE); 446 | return; 447 | } 448 | if (bytes_read != PAGE_SIZE) 449 | { 450 | throw std::runtime_error("Failed to read page"); 451 | } 452 | } 453 | 454 | void 455 | DiskManager::write_page(PageLocation page_location, DatabasePage* page) 456 | { 457 | std::filesystem::path path = get_page_filepath(page_location); 458 | int fd = get_or_create_fd(path); 459 | 460 | ssize_t bytes_written = pwrite(fd, page, PAGE_SIZE, 0); 461 | if (bytes_written != PAGE_SIZE) 462 | { 463 | throw std::runtime_error("Failed to write page"); 464 | } 465 | } 466 | 467 | //////////////////////////////////////////////////////////////////////////////// 468 | // BUFFER POOL 469 | //////////////////////////////////////////////////////////////////////////////// 470 | 471 | class BufferPool : public IBufferPool 472 | { 473 | using page_table_t = std::unordered_map; 474 | 475 | private: 476 | alignas(PAGE_SIZE) std::byte m_buffer[BUFFER_POOL_SIZE][PAGE_SIZE]; 477 | IDiskManager* m_disk_manager; 478 | page_table_t m_page_table; 479 | std::shared_mutex m_page_table_mutex; 480 | bool m_is_dirty[BUFFER_POOL_SIZE]; 481 | bool m_reference_bit[BUFFER_POOL_SIZE]; 482 | int pin_count[BUFFER_POOL_SIZE]; 483 | std::deque m_free_frames; 484 | frame_idx_t m_clock_hand = 0; 485 | 486 | DatabasePage* get_page(frame_idx_t frame_idx); 487 | frame_idx_t evict_page(); // Evicts a page from either the free list or the clock hand 488 | void remove_page_from_page_table(frame_idx_t frame_idx); 489 | 490 | public: 491 | explicit BufferPool(IDiskManager* disk_manager); 492 | 493 | DatabasePage* fetch_page(PageLocation page_location) override; 494 | void unpin(PageLocation page_location, bool is_dirty) override; 495 | 496 | void flush(PageLocation page_location) override; 497 | void flush_all() override; 498 | }; 499 | 500 | BufferPool::BufferPool(IDiskManager* disk_manager) 501 | : m_disk_manager(disk_manager) 502 | , m_page_table(BUFFER_POOL_SIZE, &PageLocation::hash) 503 | , m_free_frames(BUFFER_POOL_SIZE) 504 | { 505 | mmap(m_buffer, 506 | BUFFER_POOL_SIZE * PAGE_SIZE, 507 | PROT_READ | PROT_WRITE, 508 | MAP_ANONYMOUS | MAP_PRIVATE, 509 | -1, 510 | 0); 511 | for (int i = 0; i < BUFFER_POOL_SIZE; i++) 512 | { 513 | m_free_frames.push_back(i); 514 | } 515 | } 516 | 517 | DatabasePage* 518 | BufferPool::get_page(frame_idx_t frame_idx) 519 | { 520 | return reinterpret_cast(m_buffer[frame_idx]); 521 | } 522 | 523 | void 524 | BufferPool::remove_page_from_page_table(frame_idx_t frame_idx) 525 | { 526 | for (auto it = m_page_table.begin(); it != m_page_table.end(); it++) 527 | { 528 | if (it->second == frame_idx) 529 | { 530 | m_page_table.erase(it); 531 | if (m_is_dirty[frame_idx]) 532 | { 533 | m_disk_manager->write_page(it->first, get_page(frame_idx)); 534 | m_is_dirty[frame_idx] = false; 535 | } 536 | return; 537 | } 538 | } 539 | } 540 | 541 | frame_idx_t 542 | BufferPool::evict_page() 543 | { 544 | // Evict a page from the free list 545 | if (!m_free_frames.empty()) 546 | { 547 | frame_idx_t frame_idx = m_free_frames.front(); 548 | m_free_frames.pop_front(); 549 | return frame_idx; 550 | } 551 | 552 | // Evict a page from the clock hand 553 | while (true) 554 | { 555 | if (pin_count[m_clock_hand] == 0) 556 | { 557 | if (m_reference_bit[m_clock_hand]) 558 | { 559 | m_reference_bit[m_clock_hand] = false; 560 | } 561 | else 562 | { 563 | // Remove the page from the page table 564 | { 565 | std::unique_lock page_table_lock(m_page_table_mutex); 566 | remove_page_from_page_table(m_clock_hand); 567 | } 568 | return m_clock_hand; 569 | } 570 | } 571 | m_clock_hand = (m_clock_hand + 1) % BUFFER_POOL_SIZE; 572 | } 573 | } 574 | 575 | DatabasePage* 576 | BufferPool::fetch_page(PageLocation page_location) 577 | { 578 | // Check if the page is already in the buffer pool 579 | { 580 | std::shared_lock page_table_lock(m_page_table_mutex); 581 | if (m_page_table.contains(page_location)) 582 | { 583 | frame_idx_t frame_idx = m_page_table[page_location]; 584 | m_reference_bit[frame_idx] = true; 585 | pin_count[frame_idx]++; 586 | return get_page(frame_idx); 587 | } 588 | } 589 | // If not, evict a page and load the new page into the buffer pool 590 | frame_idx_t frame_idx = evict_page(); 591 | m_disk_manager->read_page(page_location, get_page(frame_idx)); 592 | { 593 | std::unique_lock page_table_lock(m_page_table_mutex); 594 | m_page_table[page_location] = frame_idx; 595 | } 596 | m_reference_bit[frame_idx] = true; 597 | pin_count[frame_idx]++; 598 | return get_page(frame_idx); 599 | } 600 | 601 | void 602 | BufferPool::unpin(PageLocation page_location, bool is_dirty) 603 | { 604 | std::shared_lock page_table_lock(m_page_table_mutex); 605 | frame_idx_t frame_idx = m_page_table[page_location]; 606 | pin_count[frame_idx]--; 607 | if (is_dirty) 608 | { 609 | m_is_dirty[frame_idx] = true; 610 | } 611 | } 612 | 613 | void 614 | BufferPool::flush(PageLocation page_location) 615 | { 616 | std::shared_lock page_table_lock(m_page_table_mutex); 617 | frame_idx_t frame_idx = m_page_table[page_location]; 618 | if (m_is_dirty[frame_idx]) 619 | { 620 | m_disk_manager->write_page(page_location, get_page(frame_idx)); 621 | m_is_dirty[frame_idx] = false; 622 | } 623 | } 624 | 625 | void 626 | BufferPool::flush_all() 627 | { 628 | for (auto& [page_location, frame_idx] : m_page_table) 629 | { 630 | flush(page_location); 631 | } 632 | } 633 | 634 | //////////////////////////////////////////////////////////////////////////////// 635 | // IHEAP TABLE 636 | //////////////////////////////////////////////////////////////////////////////// 637 | 638 | class HeapTable : public IHeapTable 639 | { 640 | private: 641 | IDatabase* m_database; 642 | IBufferPool* m_buffer_pool; 643 | db_id_t m_db_id; 644 | table_id_t m_table_id; 645 | page_id_t m_next_page_id; 646 | 647 | public: 648 | HeapTable(IDatabase* database, 649 | IBufferPool* buffer_pool, 650 | db_id_t db_id, 651 | table_id_t table_id, 652 | page_id_t next_page_id); 653 | 654 | std::optional insert_record(RecordConst record) override; 655 | std::optional get_record(RecordID record_id) override; 656 | void delete_record(RecordID record_id) override; 657 | 658 | void scan(std::function callback) override; 659 | }; 660 | 661 | HeapTable::HeapTable(IDatabase* database, 662 | IBufferPool* buffer_pool, 663 | db_id_t db_id, 664 | table_id_t table_id, 665 | page_id_t next_page_id) 666 | : m_database(database) 667 | , m_buffer_pool(buffer_pool) 668 | , m_db_id(db_id) 669 | , m_table_id(table_id) 670 | , m_next_page_id(next_page_id) 671 | { 672 | SPDLOG_INFO("Creating heap table for ({}:{})", db_id, table_id); 673 | 674 | // If the table is empty, create the first page 675 | if (m_next_page_id == 0) 676 | { 677 | PageLocation page_location = { PageType::HeapPage, m_db_id, m_table_id, m_next_page_id }; 678 | DatabasePage* page = m_buffer_pool->fetch_page(page_location); 679 | HeapPage* heap_page = &page->heap_page; 680 | heap_page->init(0); 681 | } 682 | } 683 | 684 | std::optional 685 | HeapTable::insert_record(RecordConst record) 686 | { 687 | SPDLOG_INFO("Inserting record into ({}:{})", m_db_id, m_table_id); 688 | 689 | PageLocation page_location = { PageType::HeapPage, m_db_id, m_table_id, m_next_page_id }; 690 | DatabasePage* page = m_buffer_pool->fetch_page(page_location); 691 | HeapPage* heap_page = &page->heap_page; 692 | 693 | if (!heap_page->has_enough_free_space(record.size_bytes())) 694 | { 695 | m_next_page_id++; 696 | page_location.page_id = m_next_page_id; 697 | page = m_buffer_pool->fetch_page(page_location); 698 | heap_page = &page->heap_page; 699 | } 700 | 701 | slot_id_t slot_id = heap_page->header.num_records; 702 | RecordID record_id { .page_id = page_location.page_id, .slot_id = slot_id }; 703 | 704 | heap_page->insert_record(record); 705 | m_buffer_pool->unpin(page_location, true); 706 | 707 | return record_id; 708 | } 709 | 710 | std::optional 711 | HeapTable::get_record(RecordID record_id) 712 | { 713 | SPDLOG_INFO("Getting record from ({}:{})", m_db_id, m_table_id); 714 | 715 | PageLocation page_location = { PageType::HeapPage, m_db_id, m_table_id, record_id.page_id }; 716 | DatabasePage* page = m_buffer_pool->fetch_page(page_location); 717 | HeapPage* heap_page = &page->heap_page; 718 | 719 | if (record_id.slot_id >= heap_page->header.num_records) 720 | { 721 | return std::nullopt; 722 | } 723 | 724 | std::optional record = heap_page->get_record(record_id.slot_id); 725 | m_buffer_pool->unpin(page_location, false); 726 | 727 | return record; 728 | } 729 | 730 | void 731 | HeapTable::delete_record(RecordID record_id) 732 | { 733 | SPDLOG_INFO("Deleting record from ({}:{})", m_db_id, m_table_id); 734 | 735 | PageLocation page_location = { PageType::HeapPage, m_db_id, m_table_id, record_id.page_id }; 736 | DatabasePage* page = m_buffer_pool->fetch_page(page_location); 737 | HeapPage* heap_page = &page->heap_page; 738 | 739 | if (record_id.slot_id >= heap_page->header.num_records) 740 | { 741 | return; 742 | } 743 | 744 | heap_page->delete_record(record_id.slot_id); 745 | m_buffer_pool->unpin(page_location, true); 746 | } 747 | 748 | void 749 | HeapTable::scan(std::function callback) 750 | { 751 | SPDLOG_INFO("Scanning records from ({}:{})", m_db_id, m_table_id); 752 | 753 | for (page_id_t page_id = 0; page_id <= m_next_page_id; page_id++) 754 | { 755 | PageLocation page_location = { PageType::HeapPage, m_db_id, m_table_id, page_id }; 756 | DatabasePage* page = m_buffer_pool->fetch_page(page_location); 757 | HeapPage* heap_page = &page->heap_page; 758 | 759 | for (slot_id_t slot_id = 0; slot_id < heap_page->header.num_records; slot_id++) 760 | { 761 | RecordID record_id { .page_id = page_id, .slot_id = slot_id }; 762 | std::optional record = heap_page->get_record(slot_id); 763 | if (record) 764 | { 765 | callback(record_id, *record); 766 | } 767 | } 768 | 769 | m_buffer_pool->unpin(page_location, false); 770 | } 771 | } 772 | 773 | //////////////////////////////////////////////////////////////////////////////// 774 | // DATABASE 775 | //////////////////////////////////////////////////////////////////////////////// 776 | 777 | class Database : public IDatabase 778 | { 779 | private: 780 | IBufferPool* m_buffer_pool; 781 | db_id_t m_db_id; 782 | std::unordered_map m_tables; 783 | 784 | public: 785 | Database(IBufferPool* buffer_pool, db_id_t db_id); 786 | 787 | IHeapTable* get_table(table_id_t table_id) override; 788 | IHeapTable* create_table() override; 789 | }; 790 | 791 | Database::Database(IBufferPool* buffer_pool, db_id_t db_id) 792 | : m_buffer_pool(buffer_pool) 793 | , m_db_id(db_id) 794 | { 795 | SPDLOG_INFO("Creating database {}", db_id); 796 | } 797 | 798 | IHeapTable* 799 | Database::get_table(table_id_t table_id) 800 | { 801 | SPDLOG_INFO("Getting table {} from database {}", table_id, m_db_id); 802 | 803 | if (m_tables.contains(table_id)) 804 | { 805 | return m_tables[table_id]; 806 | } 807 | 808 | return nullptr; 809 | } 810 | 811 | IHeapTable* 812 | Database::create_table() 813 | { 814 | SPDLOG_INFO("Creating table in database {}", m_db_id); 815 | 816 | table_id_t table_id = m_tables.size(); 817 | IHeapTable* table = new HeapTable(this, m_buffer_pool, m_db_id, table_id, 0); 818 | m_tables[table_id] = table; 819 | 820 | return table; 821 | } 822 | 823 | int 824 | main() 825 | { 826 | DiskManager disk_manager = DiskManager(); 827 | BufferPool buffer_pool = BufferPool(&disk_manager); 828 | Database database = Database(&buffer_pool, 0); 829 | 830 | IHeapTable* table = database.create_table(); 831 | table->insert_record({ "Hello, world!"_b }); 832 | 833 | table->scan([](RecordID record_id, RecordConst record) { 834 | SPDLOG_INFO("RecordID: ({}, {})", record_id.page_id, record_id.slot_id); 835 | }); 836 | 837 | table->insert_record({ "Hello, world 123!"_b }); 838 | 839 | table->scan([](RecordID record_id, RecordConst record) { 840 | SPDLOG_INFO("RecordID: ({}, {})", record_id.page_id, record_id.slot_id); 841 | }); 842 | } -------------------------------------------------------------------------------- /cpp/.clang-tidy: -------------------------------------------------------------------------------- 1 | Checks: < 2 | clang-diagnostic*, 3 | clang-analyzer*, 4 | *, 5 | -fuchsia*, 6 | -llvm*, 7 | -altera-id-dependent-backward-branch, 8 | -altera-unroll-loops, 9 | -concurrency-mt-unsafe, 10 | -cppcoreguidelines-avoid-c-arrays, 11 | -cppcoreguidelines-avoid-do-while, 12 | -cppcoreguidelines-avoid-magic-numbers, 13 | -cppcoreguidelines-pro-bounds-array-to-pointer-decay, 14 | -cppcoreguidelines-pro-bounds-constant-array-index, 15 | -cppcoreguidelines-pro-type-union-access, 16 | -cppcoreguidelines-pro-type-vararg, 17 | -google-readability-casting, 18 | -hicpp-avoid-c-arrays, 19 | -hicpp-no-array-decay, 20 | -hicpp-signed-bitwise, 21 | -hicpp-vararg, 22 | -misc-non-private-member-variables-in-classes, 23 | -modernize-avoid-c-arrays, 24 | -modernize-use-trailing-return-type, 25 | -readability-identifier-length, 26 | -readability-magic-numbers, 27 | WarningsAsErrors: "" 28 | HeaderFilterRegex: "" 29 | AnalyzeTemporaryDtors: false 30 | FormatStyle: none 31 | User: user 32 | CheckOptions: 33 | abseil-cleanup-ctad.IncludeStyle: llvm 34 | abseil-string-find-startswith.AbseilStringsMatchHeader: "absl/strings/match.h" 35 | abseil-string-find-startswith.IncludeStyle: llvm 36 | abseil-string-find-startswith.StringLikeClasses: "::std::basic_string" 37 | abseil-string-find-str-contains.AbseilStringsMatchHeader: "absl/strings/match.h" 38 | abseil-string-find-str-contains.IncludeStyle: llvm 39 | abseil-string-find-str-contains.StringLikeClasses: "::std::basic_string;::std::basic_string_view;::absl::string_view" 40 | altera-single-work-item-barrier.AOCVersion: "1600" 41 | altera-struct-pack-align.MaxConfiguredAlignment: "128" 42 | altera-unroll-loops.MaxLoopIterations: "100" 43 | android-comparison-in-temp-failure-retry.RetryMacros: TEMP_FAILURE_RETRY 44 | bugprone-argument-comment.CommentBoolLiterals: "0" 45 | bugprone-argument-comment.CommentCharacterLiterals: "0" 46 | bugprone-argument-comment.CommentFloatLiterals: "0" 47 | bugprone-argument-comment.CommentIntegerLiterals: "0" 48 | bugprone-argument-comment.CommentNullPtrs: "0" 49 | bugprone-argument-comment.CommentStringLiterals: "0" 50 | bugprone-argument-comment.CommentUserDefinedLiterals: "0" 51 | bugprone-argument-comment.IgnoreSingleArgument: "0" 52 | bugprone-argument-comment.StrictMode: "0" 53 | bugprone-assert-side-effect.AssertMacros: assert,NSAssert,NSCAssert 54 | bugprone-assert-side-effect.CheckFunctionCalls: "false" 55 | bugprone-assert-side-effect.IgnoredFunctions: __builtin_expect 56 | bugprone-dangling-handle.HandleClasses: "std::basic_string_view;std::experimental::basic_string_view" 57 | bugprone-dynamic-static-initializers.HeaderFileExtensions: ";h;hh;hpp;hxx" 58 | bugprone-easily-swappable-parameters.IgnoredParameterNames: '"";iterator;Iterator;begin;Begin;end;End;first;First;last;Last;lhs;LHS;rhs;RHS' 59 | bugprone-easily-swappable-parameters.IgnoredParameterTypeSuffixes: "bool;Bool;_Bool;it;It;iterator;Iterator;inputit;InputIt;forwardit;ForwardIt;bidirit;BidirIt;constiterator;const_iterator;Const_Iterator;Constiterator;ConstIterator;RandomIt;randomit;random_iterator;ReverseIt;reverse_iterator;reverse_const_iterator;ConstReverseIterator;Const_Reverse_Iterator;const_reverse_iterator;Constreverseiterator;constreverseiterator" 60 | bugprone-easily-swappable-parameters.MinimumLength: "2" 61 | bugprone-easily-swappable-parameters.ModelImplicitConversions: "true" 62 | bugprone-easily-swappable-parameters.NamePrefixSuffixSilenceDissimilarityTreshold: "1" 63 | bugprone-easily-swappable-parameters.QualifiersMix: "false" 64 | bugprone-easily-swappable-parameters.SuppressParametersUsedTogether: "true" 65 | bugprone-exception-escape.FunctionsThatShouldNotThrow: "" 66 | bugprone-exception-escape.IgnoredExceptions: "" 67 | bugprone-implicit-widening-of-multiplication-result.IncludeStyle: llvm 68 | bugprone-implicit-widening-of-multiplication-result.UseCXXHeadersInCppSources: "true" 69 | bugprone-implicit-widening-of-multiplication-result.UseCXXStaticCastsInCppSources: "true" 70 | bugprone-misplaced-widening-cast.CheckImplicitCasts: "false" 71 | bugprone-narrowing-conversions.IgnoreConversionFromTypes: "" 72 | bugprone-narrowing-conversions.PedanticMode: "false" 73 | bugprone-narrowing-conversions.WarnOnEquivalentBitWidth: "true" 74 | bugprone-narrowing-conversions.WarnOnFloatingPointNarrowingConversion: "true" 75 | bugprone-narrowing-conversions.WarnOnIntegerNarrowingConversion: "true" 76 | bugprone-narrowing-conversions.WarnOnIntegerToFloatingPointNarrowingConversion: "true" 77 | bugprone-narrowing-conversions.WarnWithinTemplateInstantiation: "false" 78 | bugprone-not-null-terminated-result.WantToUseSafeFunctions: "true" 79 | bugprone-reserved-identifier.AggressiveDependentMemberLookup: "false" 80 | bugprone-reserved-identifier.AllowedIdentifiers: "" 81 | bugprone-reserved-identifier.Invert: "false" 82 | bugprone-signal-handler.AsyncSafeFunctionSet: POSIX 83 | bugprone-signed-char-misuse.CharTypdefsToIgnore: "" 84 | bugprone-signed-char-misuse.DiagnoseSignedUnsignedCharComparisons: "true" 85 | bugprone-sizeof-expression.WarnOnSizeOfCompareToConstant: "true" 86 | bugprone-sizeof-expression.WarnOnSizeOfConstant: "true" 87 | bugprone-sizeof-expression.WarnOnSizeOfIntegerExpression: "false" 88 | bugprone-sizeof-expression.WarnOnSizeOfPointerToAggregate: "true" 89 | bugprone-sizeof-expression.WarnOnSizeOfThis: "true" 90 | bugprone-string-constructor.LargeLengthThreshold: "8388608" 91 | bugprone-string-constructor.StringNames: "::std::basic_string;::std::basic_string_view" 92 | bugprone-string-constructor.WarnOnLargeLength: "true" 93 | bugprone-stringview-nullptr.IncludeStyle: llvm 94 | bugprone-suspicious-enum-usage.StrictMode: "false" 95 | bugprone-suspicious-include.HeaderFileExtensions: ";h;hh;hpp;hxx" 96 | bugprone-suspicious-include.ImplementationFileExtensions: "c;cc;cpp;cxx" 97 | bugprone-suspicious-missing-comma.MaxConcatenatedTokens: "5" 98 | bugprone-suspicious-missing-comma.RatioThreshold: "0.200000" 99 | bugprone-suspicious-missing-comma.SizeThreshold: "5" 100 | bugprone-suspicious-string-compare.StringCompareLikeFunctions: "" 101 | bugprone-suspicious-string-compare.WarnOnImplicitComparison: "true" 102 | bugprone-suspicious-string-compare.WarnOnLogicalNotComparison: "false" 103 | bugprone-too-small-loop-variable.MagnitudeBitsUpperLimit: "16" 104 | bugprone-unhandled-self-assignment.WarnOnlyIfThisHasSuspiciousField: "true" 105 | bugprone-unused-return-value.CheckedFunctions: "::std::async;::std::launder;::std::remove;::std::remove_if;::std::unique;::std::unique_ptr::release;::std::basic_string::empty;::std::vector::empty;::std::back_inserter;::std::distance;::std::find;::std::find_if;::std::inserter;::std::lower_bound;::std::make_pair;::std::map::count;::std::map::find;::std::map::lower_bound;::std::multimap::equal_range;::std::multimap::upper_bound;::std::set::count;::std::set::find;::std::setfill;::std::setprecision;::std::setw;::std::upper_bound;::std::vector::at;::bsearch;::ferror;::feof;::isalnum;::isalpha;::isblank;::iscntrl;::isdigit;::isgraph;::islower;::isprint;::ispunct;::isspace;::isupper;::iswalnum;::iswprint;::iswspace;::isxdigit;::memchr;::memcmp;::strcmp;::strcoll;::strncmp;::strpbrk;::strrchr;::strspn;::strstr;::wcscmp;::access;::bind;::connect;::difftime;::dlsym;::fnmatch;::getaddrinfo;::getopt;::htonl;::htons;::iconv_open;::inet_addr;::isascii;::isatty;::mmap;::newlocale;::openat;::pathconf;::pthread_equal;::pthread_getspecific;::pthread_mutex_trylock;::readdir;::readlink;::recvmsg;::regexec;::scandir;::semget;::setjmp;::shm_open;::shmget;::sigismember;::strcasecmp;::strsignal;::ttyname" 106 | cert-dcl16-c.IgnoreMacros: "true" 107 | cert-dcl16-c.NewSuffixes: "L;LL;LU;LLU" 108 | cert-dcl37-c.AggressiveDependentMemberLookup: "false" 109 | cert-dcl37-c.AllowedIdentifiers: "" 110 | cert-dcl37-c.Invert: "false" 111 | cert-dcl51-cpp.AggressiveDependentMemberLookup: "false" 112 | cert-dcl51-cpp.AllowedIdentifiers: "" 113 | cert-dcl51-cpp.Invert: "false" 114 | cert-dcl59-cpp.HeaderFileExtensions: ";h;hh;hpp;hxx" 115 | cert-err09-cpp.CheckThrowTemporaries: "true" 116 | cert-err09-cpp.MaxSize: "-1" 117 | cert-err09-cpp.WarnOnLargeObjects: "false" 118 | cert-err33-c.CheckedFunctions: "::aligned_alloc;::asctime_s;::at_quick_exit;::atexit;::bsearch;::bsearch_s;::btowc;::c16rtomb;::c32rtomb;::calloc;::clock;::cnd_broadcast;::cnd_init;::cnd_signal;::cnd_timedwait;::cnd_wait;::ctime_s;::fclose;::fflush;::fgetc;::fgetpos;::fgets;::fgetwc;::fopen;::fopen_s;::fprintf;::fprintf_s;::fputc;::fputs;::fputwc;::fputws;::fread;::freopen;::freopen_s;::fscanf;::fscanf_s;::fseek;::fsetpos;::ftell;::fwprintf;::fwprintf_s;::fwrite;::fwscanf;::fwscanf_s;::getc;::getchar;::getenv;::getenv_s;::gets_s;::getwc;::getwchar;::gmtime;::gmtime_s;::localtime;::localtime_s;::malloc;::mbrtoc16;::mbrtoc32;::mbsrtowcs;::mbsrtowcs_s;::mbstowcs;::mbstowcs_s;::memchr;::mktime;::mtx_init;::mtx_lock;::mtx_timedlock;::mtx_trylock;::mtx_unlock;::printf_s;::putc;::putwc;::raise;::realloc;::remove;::rename;::scanf;::scanf_s;::setlocale;::setvbuf;::signal;::snprintf;::snprintf_s;::sprintf;::sprintf_s;::sscanf;::sscanf_s;::strchr;::strerror_s;::strftime;::strpbrk;::strrchr;::strstr;::strtod;::strtof;::strtoimax;::strtok;::strtok_s;::strtol;::strtold;::strtoll;::strtoul;::strtoull;::strtoumax;::strxfrm;::swprintf;::swprintf_s;::swscanf;::swscanf_s;::thrd_create;::thrd_detach;::thrd_join;::thrd_sleep;::time;::timespec_get;::tmpfile;::tmpfile_s;::tmpnam;::tmpnam_s;::tss_create;::tss_get;::tss_set;::ungetc;::ungetwc;::vfprintf;::vfprintf_s;::vfscanf;::vfscanf_s;::vfwprintf;::vfwprintf_s;::vfwscanf;::vfwscanf_s;::vprintf_s;::vscanf;::vscanf_s;::vsnprintf;::vsnprintf_s;::vsprintf;::vsprintf_s;::vsscanf;::vsscanf_s;::vswprintf;::vswprintf_s;::vswscanf;::vswscanf_s;::vwprintf_s;::vwscanf;::vwscanf_s;::wcrtomb;::wcschr;::wcsftime;::wcspbrk;::wcsrchr;::wcsrtombs;::wcsrtombs_s;::wcsstr;::wcstod;::wcstof;::wcstoimax;::wcstok;::wcstok_s;::wcstol;::wcstold;::wcstoll;::wcstombs;::wcstombs_s;::wcstoul;::wcstoull;::wcstoumax;::wcsxfrm;::wctob;::wctrans;::wctype;::wmemchr;::wprintf_s;::wscanf;::wscanf_s;" 119 | cert-err61-cpp.CheckThrowTemporaries: "true" 120 | cert-err61-cpp.MaxSize: "-1" 121 | cert-err61-cpp.WarnOnLargeObjects: "false" 122 | cert-msc32-c.DisallowedSeedTypes: "time_t,std::time_t" 123 | cert-msc51-cpp.DisallowedSeedTypes: "time_t,std::time_t" 124 | cert-msc54-cpp.AsyncSafeFunctionSet: POSIX 125 | cert-oop54-cpp.WarnOnlyIfThisHasSuspiciousField: "false" 126 | cert-oop57-cpp.MemCmpNames: "" 127 | cert-oop57-cpp.MemCpyNames: "" 128 | cert-oop57-cpp.MemSetNames: "" 129 | cert-sig30-c.AsyncSafeFunctionSet: POSIX 130 | cert-str34-c.CharTypdefsToIgnore: "" 131 | cert-str34-c.DiagnoseSignedUnsignedCharComparisons: "false" 132 | concurrency-mt-unsafe.FunctionSet: any 133 | cppcoreguidelines-avoid-do-while.IgnoreMacros: "false" 134 | cppcoreguidelines-avoid-magic-numbers.IgnoreAllFloatingPointValues: "false" 135 | cppcoreguidelines-avoid-magic-numbers.IgnoreBitFieldsWidths: "true" 136 | cppcoreguidelines-avoid-magic-numbers.IgnoredFloatingPointValues: "1.0;100.0;" 137 | cppcoreguidelines-avoid-magic-numbers.IgnoredIntegerValues: "1;2;3;4;" 138 | cppcoreguidelines-avoid-magic-numbers.IgnorePowersOf2IntegerValues: "false" 139 | cppcoreguidelines-explicit-virtual-functions.AllowOverrideAndFinal: "false" 140 | cppcoreguidelines-explicit-virtual-functions.FinalSpelling: final 141 | cppcoreguidelines-explicit-virtual-functions.IgnoreDestructors: "false" 142 | cppcoreguidelines-explicit-virtual-functions.OverrideSpelling: override 143 | cppcoreguidelines-init-variables.IncludeStyle: llvm 144 | cppcoreguidelines-init-variables.MathHeader: "" 145 | cppcoreguidelines-macro-usage.AllowedRegexp: "^DEBUG_*" 146 | cppcoreguidelines-macro-usage.CheckCapsOnly: "false" 147 | cppcoreguidelines-macro-usage.IgnoreCommandLineMacros: "true" 148 | cppcoreguidelines-narrowing-conversions.IgnoreConversionFromTypes: "" 149 | cppcoreguidelines-narrowing-conversions.PedanticMode: "false" 150 | cppcoreguidelines-narrowing-conversions.WarnOnEquivalentBitWidth: "true" 151 | cppcoreguidelines-narrowing-conversions.WarnOnFloatingPointNarrowingConversion: "true" 152 | cppcoreguidelines-narrowing-conversions.WarnOnIntegerNarrowingConversion: "true" 153 | cppcoreguidelines-narrowing-conversions.WarnOnIntegerToFloatingPointNarrowingConversion: "true" 154 | cppcoreguidelines-narrowing-conversions.WarnWithinTemplateInstantiation: "false" 155 | cppcoreguidelines-no-malloc.Allocations: "::malloc;::calloc" 156 | cppcoreguidelines-no-malloc.Deallocations: "::free" 157 | cppcoreguidelines-no-malloc.Reallocations: "::realloc" 158 | cppcoreguidelines-non-private-member-variables-in-classes.IgnoreClassesWithAllMemberVariablesBeingPublic: "true" 159 | cppcoreguidelines-non-private-member-variables-in-classes.IgnorePublicMemberVariables: "false" 160 | cppcoreguidelines-owning-memory.LegacyResourceConsumers: "::free;::realloc;::freopen;::fclose" 161 | cppcoreguidelines-owning-memory.LegacyResourceProducers: "::malloc;::aligned_alloc;::realloc;::calloc;::fopen;::freopen;::tmpfile" 162 | cppcoreguidelines-prefer-member-initializer.UseAssignment: "false" 163 | cppcoreguidelines-pro-bounds-constant-array-index.GslHeader: "" 164 | cppcoreguidelines-pro-bounds-constant-array-index.IncludeStyle: llvm 165 | cppcoreguidelines-pro-type-member-init.IgnoreArrays: "false" 166 | cppcoreguidelines-pro-type-member-init.UseAssignment: "false" 167 | cppcoreguidelines-special-member-functions.AllowMissingMoveFunctions: "false" 168 | cppcoreguidelines-special-member-functions.AllowMissingMoveFunctionsWhenCopyIsDeleted: "false" 169 | cppcoreguidelines-special-member-functions.AllowSoleDefaultDtor: "false" 170 | fuchsia-header-anon-namespaces.HeaderFileExtensions: ";h;hh;hpp;hxx" 171 | google-build-namespaces.HeaderFileExtensions: ";h;hh;hpp;hxx" 172 | google-global-names-in-headers.HeaderFileExtensions: ";h;hh;hpp;hxx" 173 | google-readability-braces-around-statements.ShortStatementLines: "1" 174 | google-readability-function-size.BranchThreshold: "4294967295" 175 | google-readability-function-size.LineThreshold: "4294967295" 176 | google-readability-function-size.NestingThreshold: "4294967295" 177 | google-readability-function-size.ParameterThreshold: "4294967295" 178 | google-readability-function-size.StatementThreshold: "800" 179 | google-readability-function-size.VariableThreshold: "4294967295" 180 | google-readability-namespace-comments.ShortNamespaceLines: "10" 181 | google-readability-namespace-comments.SpacesBeforeComments: "2" 182 | google-runtime-int.SignedTypePrefix: int 183 | google-runtime-int.TypeSuffix: "" 184 | google-runtime-int.UnsignedTypePrefix: uint 185 | hicpp-braces-around-statements.ShortStatementLines: "0" 186 | hicpp-deprecated-headers.CheckHeaderFile: "false" 187 | hicpp-function-size.BranchThreshold: "4294967295" 188 | hicpp-function-size.LineThreshold: "4294967295" 189 | hicpp-function-size.NestingThreshold: "4294967295" 190 | hicpp-function-size.ParameterThreshold: "4294967295" 191 | hicpp-function-size.StatementThreshold: "800" 192 | hicpp-function-size.VariableThreshold: "4294967295" 193 | hicpp-member-init.IgnoreArrays: "false" 194 | hicpp-member-init.UseAssignment: "false" 195 | hicpp-move-const-arg.CheckMoveToConstRef: "true" 196 | hicpp-move-const-arg.CheckTriviallyCopyableMove: "true" 197 | hicpp-multiway-paths-covered.WarnOnMissingElse: "false" 198 | hicpp-no-malloc.Allocations: "::malloc;::calloc" 199 | hicpp-no-malloc.Deallocations: "::free" 200 | hicpp-no-malloc.Reallocations: "::realloc" 201 | hicpp-signed-bitwise.IgnorePositiveIntegerLiterals: "false" 202 | hicpp-special-member-functions.AllowMissingMoveFunctions: "false" 203 | hicpp-special-member-functions.AllowMissingMoveFunctionsWhenCopyIsDeleted: "false" 204 | hicpp-special-member-functions.AllowSoleDefaultDtor: "false" 205 | hicpp-uppercase-literal-suffix.IgnoreMacros: "true" 206 | hicpp-uppercase-literal-suffix.NewSuffixes: "" 207 | hicpp-use-auto.MinTypeNameLength: "5" 208 | hicpp-use-auto.RemoveStars: "false" 209 | hicpp-use-emplace.ContainersWithPush: "::std::stack;::std::queue;::std::priority_queue" 210 | hicpp-use-emplace.ContainersWithPushBack: "::std::vector;::std::list;::std::deque" 211 | hicpp-use-emplace.ContainersWithPushFront: "::std::forward_list;::std::list;::std::deque" 212 | hicpp-use-emplace.EmplacyFunctions: "vector::emplace_back;vector::emplace;deque::emplace;deque::emplace_front;deque::emplace_back;forward_list::emplace_after;forward_list::emplace_front;list::emplace;list::emplace_back;list::emplace_front;set::emplace;set::emplace_hint;map::emplace;map::emplace_hint;multiset::emplace;multiset::emplace_hint;multimap::emplace;multimap::emplace_hint;unordered_set::emplace;unordered_set::emplace_hint;unordered_map::emplace;unordered_map::emplace_hint;unordered_multiset::emplace;unordered_multiset::emplace_hint;unordered_multimap::emplace;unordered_multimap::emplace_hint;stack::emplace;queue::emplace;priority_queue::emplace" 213 | hicpp-use-emplace.IgnoreImplicitConstructors: "false" 214 | hicpp-use-emplace.SmartPointers: "::std::shared_ptr;::std::unique_ptr;::std::auto_ptr;::std::weak_ptr" 215 | hicpp-use-emplace.TupleMakeFunctions: "::std::make_pair;::std::make_tuple" 216 | hicpp-use-emplace.TupleTypes: "::std::pair;::std::tuple" 217 | hicpp-use-equals-default.IgnoreMacros: "true" 218 | hicpp-use-equals-delete.IgnoreMacros: "true" 219 | hicpp-use-noexcept.ReplacementString: "" 220 | hicpp-use-noexcept.UseNoexceptFalse: "true" 221 | hicpp-use-nullptr.NullMacros: "" 222 | hicpp-use-override.AllowOverrideAndFinal: "false" 223 | hicpp-use-override.FinalSpelling: final 224 | hicpp-use-override.IgnoreDestructors: "false" 225 | hicpp-use-override.OverrideSpelling: override 226 | llvm-else-after-return.WarnOnConditionVariables: "false" 227 | llvm-else-after-return.WarnOnUnfixable: "false" 228 | llvm-header-guard.HeaderFileExtensions: ";h;hh;hpp;hxx" 229 | llvm-namespace-comment.ShortNamespaceLines: "1" 230 | llvm-namespace-comment.SpacesBeforeComments: "1" 231 | llvm-qualified-auto.AddConstToQualified: "false" 232 | llvmlibc-restrict-system-libc-headers.Includes: "-*" 233 | misc-const-correctness.AnalyzeReferences: "true" 234 | misc-const-correctness.AnalyzeValues: "true" 235 | misc-const-correctness.TransformPointersAsValues: "false" 236 | misc-const-correctness.TransformReferences: "true" 237 | misc-const-correctness.TransformValues: "true" 238 | misc-const-correctness.WarnPointersAsValues: "false" 239 | misc-definitions-in-headers.HeaderFileExtensions: ";h;hh;hpp;hxx" 240 | misc-definitions-in-headers.UseHeaderFileExtension: "true" 241 | misc-non-private-member-variables-in-classes.IgnoreClassesWithAllMemberVariablesBeingPublic: "false" 242 | misc-non-private-member-variables-in-classes.IgnorePublicMemberVariables: "false" 243 | misc-throw-by-value-catch-by-reference.CheckThrowTemporaries: "true" 244 | misc-throw-by-value-catch-by-reference.MaxSize: "-1" 245 | misc-throw-by-value-catch-by-reference.WarnOnLargeObjects: "false" 246 | misc-uniqueptr-reset-release.IncludeStyle: llvm 247 | misc-unused-parameters.StrictMode: "false" 248 | modernize-avoid-bind.PermissiveParameterList: "false" 249 | modernize-deprecated-headers.CheckHeaderFile: "false" 250 | modernize-loop-convert.IncludeStyle: llvm 251 | modernize-loop-convert.MakeReverseRangeFunction: "" 252 | modernize-loop-convert.MakeReverseRangeHeader: "" 253 | modernize-loop-convert.MaxCopySize: "16" 254 | modernize-loop-convert.MinConfidence: reasonable 255 | modernize-loop-convert.NamingStyle: CamelCase 256 | modernize-loop-convert.UseCxx20ReverseRanges: "true" 257 | modernize-make-shared.IgnoreDefaultInitialization: "true" 258 | modernize-make-shared.IgnoreMacros: "true" 259 | modernize-make-shared.IncludeStyle: llvm 260 | modernize-make-shared.MakeSmartPtrFunction: "std::make_shared" 261 | modernize-make-shared.MakeSmartPtrFunctionHeader: "" 262 | modernize-make-unique.IgnoreDefaultInitialization: "true" 263 | modernize-make-unique.IgnoreMacros: "true" 264 | modernize-make-unique.IncludeStyle: llvm 265 | modernize-make-unique.MakeSmartPtrFunction: "std::make_unique" 266 | modernize-make-unique.MakeSmartPtrFunctionHeader: "" 267 | modernize-pass-by-value.IncludeStyle: llvm 268 | modernize-pass-by-value.ValuesOnly: "false" 269 | modernize-raw-string-literal.DelimiterStem: lit 270 | modernize-raw-string-literal.ReplaceShorterLiterals: "false" 271 | modernize-replace-auto-ptr.IncludeStyle: llvm 272 | modernize-replace-disallow-copy-and-assign-macro.MacroName: DISALLOW_COPY_AND_ASSIGN 273 | modernize-replace-random-shuffle.IncludeStyle: llvm 274 | modernize-use-auto.MinTypeNameLength: "5" 275 | modernize-use-auto.RemoveStars: "false" 276 | modernize-use-bool-literals.IgnoreMacros: "true" 277 | modernize-use-default-member-init.IgnoreMacros: "true" 278 | modernize-use-default-member-init.UseAssignment: "false" 279 | modernize-use-emplace.ContainersWithPush: "::std::stack;::std::queue;::std::priority_queue" 280 | modernize-use-emplace.ContainersWithPushBack: "::std::vector;::std::list;::std::deque" 281 | modernize-use-emplace.ContainersWithPushFront: "::std::forward_list;::std::list;::std::deque" 282 | modernize-use-emplace.EmplacyFunctions: "vector::emplace_back;vector::emplace;deque::emplace;deque::emplace_front;deque::emplace_back;forward_list::emplace_after;forward_list::emplace_front;list::emplace;list::emplace_back;list::emplace_front;set::emplace;set::emplace_hint;map::emplace;map::emplace_hint;multiset::emplace;multiset::emplace_hint;multimap::emplace;multimap::emplace_hint;unordered_set::emplace;unordered_set::emplace_hint;unordered_map::emplace;unordered_map::emplace_hint;unordered_multiset::emplace;unordered_multiset::emplace_hint;unordered_multimap::emplace;unordered_multimap::emplace_hint;stack::emplace;queue::emplace;priority_queue::emplace" 283 | modernize-use-emplace.IgnoreImplicitConstructors: "false" 284 | modernize-use-emplace.SmartPointers: "::std::shared_ptr;::std::unique_ptr;::std::auto_ptr;::std::weak_ptr" 285 | modernize-use-emplace.TupleMakeFunctions: "::std::make_pair;::std::make_tuple" 286 | modernize-use-emplace.TupleTypes: "::std::pair;::std::tuple" 287 | modernize-use-equals-default.IgnoreMacros: "true" 288 | modernize-use-equals-delete.IgnoreMacros: "true" 289 | modernize-use-nodiscard.ReplacementString: "[[nodiscard]]" 290 | modernize-use-noexcept.ReplacementString: "" 291 | modernize-use-noexcept.UseNoexceptFalse: "true" 292 | modernize-use-nullptr.NullMacros: "NULL" 293 | modernize-use-override.AllowOverrideAndFinal: "false" 294 | modernize-use-override.FinalSpelling: final 295 | modernize-use-override.IgnoreDestructors: "false" 296 | modernize-use-override.OverrideSpelling: override 297 | modernize-use-transparent-functors.SafeMode: "false" 298 | modernize-use-using.IgnoreMacros: "true" 299 | objc-forbidden-subclassing.ForbiddenSuperClassNames: "ABNewPersonViewController;ABPeoplePickerNavigationController;ABPersonViewController;ABUnknownPersonViewController;NSHashTable;NSMapTable;NSPointerArray;NSPointerFunctions;NSTimer;UIActionSheet;UIAlertView;UIImagePickerController;UITextInputMode;UIWebView" 300 | openmp-exception-escape.IgnoredExceptions: "" 301 | performance-faster-string-find.StringLikeClasses: "::std::basic_string;::std::basic_string_view" 302 | performance-for-range-copy.AllowedTypes: "" 303 | performance-for-range-copy.WarnOnAllAutoCopies: "false" 304 | performance-inefficient-string-concatenation.StrictMode: "false" 305 | performance-inefficient-vector-operation.EnableProto: "false" 306 | performance-inefficient-vector-operation.VectorLikeClasses: "::std::vector" 307 | performance-move-const-arg.CheckMoveToConstRef: "true" 308 | performance-move-const-arg.CheckTriviallyCopyableMove: "true" 309 | performance-no-automatic-move.AllowedTypes: "" 310 | performance-type-promotion-in-math-fn.IncludeStyle: llvm 311 | performance-unnecessary-copy-initialization.AllowedTypes: "" 312 | performance-unnecessary-copy-initialization.ExcludedContainerTypes: "" 313 | performance-unnecessary-value-param.AllowedTypes: "" 314 | performance-unnecessary-value-param.IncludeStyle: llvm 315 | portability-restrict-system-includes.Includes: "*" 316 | portability-simd-intrinsics.Std: "" 317 | portability-simd-intrinsics.Suggest: "false" 318 | readability-avoid-const-params-in-decls.IgnoreMacros: "true" 319 | readability-braces-around-statements.ShortStatementLines: "0" 320 | readability-const-return-type.IgnoreMacros: "true" 321 | readability-else-after-return.WarnOnConditionVariables: "true" 322 | readability-else-after-return.WarnOnUnfixable: "true" 323 | readability-function-cognitive-complexity.DescribeBasicIncrements: "true" 324 | readability-function-cognitive-complexity.IgnoreMacros: "false" 325 | readability-function-cognitive-complexity.Threshold: "25" 326 | readability-function-size.BranchThreshold: "4294967295" 327 | readability-function-size.LineThreshold: "4294967295" 328 | readability-function-size.NestingThreshold: "4294967295" 329 | readability-function-size.ParameterThreshold: "4294967295" 330 | readability-function-size.StatementThreshold: "800" 331 | readability-function-size.VariableThreshold: "4294967295" 332 | readability-identifier-length.IgnoredExceptionVariableNames: "^[e]$" 333 | readability-identifier-length.IgnoredLoopCounterNames: "^[ijk_]$" 334 | readability-identifier-length.IgnoredParameterNames: "^[n]$" 335 | readability-identifier-length.IgnoredVariableNames: "" 336 | readability-identifier-length.MinimumExceptionNameLength: "2" 337 | readability-identifier-length.MinimumLoopCounterNameLength: "2" 338 | readability-identifier-length.MinimumParameterNameLength: "3" 339 | readability-identifier-length.MinimumVariableNameLength: "3" 340 | readability-identifier-naming.AggressiveDependentMemberLookup: "false" 341 | readability-identifier-naming.GetConfigPerFile: "true" 342 | readability-identifier-naming.IgnoreFailedSplit: "false" 343 | readability-identifier-naming.IgnoreMainLikeFunctions: "false" 344 | readability-implicit-bool-conversion.AllowIntegerConditions: "false" 345 | readability-implicit-bool-conversion.AllowPointerConditions: "false" 346 | readability-inconsistent-declaration-parameter-name.IgnoreMacros: "true" 347 | readability-inconsistent-declaration-parameter-name.Strict: "false" 348 | readability-magic-numbers.IgnoreAllFloatingPointValues: "false" 349 | readability-magic-numbers.IgnoreBitFieldsWidths: "true" 350 | readability-magic-numbers.IgnoredFloatingPointValues: "1.0;100.0;" 351 | readability-magic-numbers.IgnoredIntegerValues: "1;2;3;4;" 352 | readability-magic-numbers.IgnorePowersOf2IntegerValues: "false" 353 | readability-qualified-auto.AddConstToQualified: "true" 354 | readability-redundant-declaration.IgnoreMacros: "true" 355 | readability-redundant-member-init.IgnoreBaseInCopyConstructors: "false" 356 | readability-redundant-smartptr-get.IgnoreMacros: "true" 357 | readability-redundant-string-init.StringNames: "::std::basic_string_view;::std::basic_string" 358 | readability-simplify-boolean-expr.ChainedConditionalAssignment: "false" 359 | readability-simplify-boolean-expr.ChainedConditionalReturn: "false" 360 | readability-simplify-boolean-expr.SimplifyDeMorgan: "true" 361 | readability-simplify-boolean-expr.SimplifyDeMorganRelaxed: "false" 362 | readability-simplify-subscript-expr.Types: "::std::basic_string;::std::basic_string_view;::std::vector;::std::array" 363 | readability-static-accessed-through-instance.NameSpecifierNestingThreshold: "3" 364 | readability-suspicious-call-argument.Abbreviation: "true" 365 | readability-suspicious-call-argument.Abbreviations: "arr=array;cnt=count;idx=index;src=source;stmt=statement;cpy=copy;dest=destination;dist=distancedst=distance;ptr=pointer;wdth=width;str=string;ln=line;srv=server;attr=attribute;ref=reference;buf=buffer;col=column;nr=number;vec=vector;len=length;elem=element;val=value;i=index;var=variable;hght=height;cl=client;num=number;pos=position;lst=list;addr=address" 366 | readability-suspicious-call-argument.Dice: "true" 367 | readability-suspicious-call-argument.DiceDissimilarBelow: "60" 368 | readability-suspicious-call-argument.DiceSimilarAbove: "70" 369 | readability-suspicious-call-argument.Equality: "true" 370 | readability-suspicious-call-argument.JaroWinkler: "true" 371 | readability-suspicious-call-argument.JaroWinklerDissimilarBelow: "75" 372 | readability-suspicious-call-argument.JaroWinklerSimilarAbove: "85" 373 | readability-suspicious-call-argument.Levenshtein: "true" 374 | readability-suspicious-call-argument.LevenshteinDissimilarBelow: "50" 375 | readability-suspicious-call-argument.LevenshteinSimilarAbove: "66" 376 | readability-suspicious-call-argument.MinimumIdentifierNameLength: "3" 377 | readability-suspicious-call-argument.Prefix: "true" 378 | readability-suspicious-call-argument.PrefixDissimilarBelow: "25" 379 | readability-suspicious-call-argument.PrefixSimilarAbove: "30" 380 | readability-suspicious-call-argument.Substring: "true" 381 | readability-suspicious-call-argument.SubstringDissimilarBelow: "40" 382 | readability-suspicious-call-argument.SubstringSimilarAbove: "50" 383 | readability-suspicious-call-argument.Suffix: "true" 384 | readability-suspicious-call-argument.SuffixDissimilarBelow: "25" 385 | readability-suspicious-call-argument.SuffixSimilarAbove: "30" 386 | readability-uniqueptr-delete-release.PreferResetCall: "false" 387 | readability-uppercase-literal-suffix.IgnoreMacros: "true" 388 | readability-uppercase-literal-suffix.NewSuffixes: "" 389 | zircon-temporary-objects.Names: "" 390 | -------------------------------------------------------------------------------- /kotlin/app/src/main/kotlin/rosetta/db/App.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * This Kotlin source file was generated by the Gradle 'init' task. 3 | */ 4 | package rosetta.db 5 | 6 | import com.sun.nio.file.ExtendedOpenOption 7 | import mu.KotlinLogging 8 | import java.io.File 9 | import java.lang.foreign.MemoryLayout 10 | import java.lang.foreign.MemoryLayout.PathElement 11 | import java.lang.foreign.MemorySegment 12 | import java.lang.foreign.MemorySession 13 | import java.lang.foreign.ValueLayout 14 | import java.lang.invoke.VarHandle 15 | import java.nio.channels.FileChannel 16 | import java.nio.file.OpenOption 17 | import java.nio.file.Path 18 | import java.nio.file.StandardOpenOption 19 | import java.util.concurrent.ConcurrentHashMap 20 | import java.util.concurrent.locks.ReentrantReadWriteLock 21 | import kotlin.experimental.and 22 | import kotlin.experimental.or 23 | 24 | const val PAGE_SIZE = 4096 25 | const val BUFFER_POOL_PAGES = 1000 26 | 27 | object Constants { 28 | val JAVA_SHORT_UNALIGNED = ValueLayout.JAVA_SHORT.withBitAlignment(8) 29 | val JAVA_INT_UNALIGNED = ValueLayout.JAVA_INT.withBitAlignment(8) 30 | val JAVA_LONG_UNALIGNED = ValueLayout.JAVA_LONG.withBitAlignment(8) 31 | } 32 | 33 | data class TableLocation( 34 | val dbId: Int, 35 | val tableId: Int 36 | ) 37 | 38 | data class PageLocation( 39 | val dbId: Int, 40 | val tableId: Int, 41 | val pageId: Int 42 | ) 43 | 44 | data class RecordId( 45 | val pageId: Int, 46 | val slotId: Int 47 | ) 48 | 49 | interface IDiskManager { 50 | fun getDatabaseDirectory(dbId: Int): Path 51 | fun createDatabaseDirectory(dbId: Int): Path 52 | fun removeDatabaseDirectory(dbId: Int): Boolean 53 | 54 | fun getTableFilePath(tableLocation: TableLocation): Path 55 | fun createTableFile(tableLocation: TableLocation): Path 56 | fun removeTableFile(tableLocation: TableLocation): Boolean 57 | fun openTableFile(tableLocation: TableLocation): FileChannel 58 | 59 | fun readPage(pageLocation: PageLocation, page: MemorySegment) 60 | fun writePage(pageLocation: PageLocation, page: MemorySegment) 61 | 62 | fun getNumPages(tableLocation: TableLocation): Int 63 | fun allocatePage(tableLocation: TableLocation): Int 64 | } 65 | 66 | object DiskManager : IDiskManager { 67 | // The root of the database directory 68 | const val DB_ROOT = "/tmp/mini-sql-engine" 69 | private val logger = KotlinLogging.logger {} 70 | 71 | private val fcCache = ConcurrentHashMap() 72 | private val OPEN_OPTIONS: Set = 73 | setOf(StandardOpenOption.READ, StandardOpenOption.WRITE, StandardOpenOption.CREATE, ExtendedOpenOption.DIRECT) 74 | 75 | 76 | override fun getDatabaseDirectory(dbId: Int): Path { 77 | return Path.of(DB_ROOT, "db_$dbId") 78 | } 79 | 80 | override fun getTableFilePath(tableLocation: TableLocation): Path { 81 | return Path.of(DB_ROOT, "db_${tableLocation.dbId}", "table_${tableLocation.tableId}") 82 | } 83 | 84 | 85 | // Gets the file for a table, creating the directory if it doesn't exist 86 | private fun getOrCreateTableFile(tableLocation: TableLocation): FileChannel { 87 | return fcCache.computeIfAbsent(tableLocation) { 88 | val tablePath = getTableFilePath(tableLocation) 89 | // Create the directory if it doesn't exist 90 | tablePath.parent.toFile().mkdirs() 91 | FileChannel.open(tablePath, OPEN_OPTIONS) 92 | } 93 | } 94 | 95 | override fun createDatabaseDirectory(dbId: Int): Path { 96 | logger.info { "Creating database directory for dbId $dbId" } 97 | val dbPath = getDatabaseDirectory(dbId) 98 | if (!dbPath.toFile().exists()) { 99 | dbPath.toFile().mkdirs() 100 | } 101 | return dbPath 102 | } 103 | 104 | override fun removeDatabaseDirectory(dbId: Int): Boolean { 105 | logger.info { "Removing database directory for dbId $dbId" } 106 | val dbPath = getDatabaseDirectory(dbId) 107 | return dbPath.toFile().deleteRecursively() 108 | } 109 | 110 | override fun createTableFile(tableLocation: TableLocation): Path { 111 | logger.info { "Creating table file for table $tableLocation" } 112 | val tablePath = getTableFilePath(tableLocation) 113 | if (!tablePath.toFile().exists()) { 114 | tablePath.toFile().createNewFile() 115 | } 116 | return tablePath 117 | } 118 | 119 | override fun removeTableFile(tableLocation: TableLocation): Boolean { 120 | logger.info { "Removing table file for table $tableLocation" } 121 | val tablePath = getTableFilePath(tableLocation) 122 | return tablePath.toFile().delete() 123 | } 124 | 125 | override fun openTableFile(tableLocation: TableLocation): FileChannel { 126 | logger.info { "Opening table file for table $tableLocation" } 127 | return getOrCreateTableFile(tableLocation) 128 | } 129 | 130 | override fun readPage(pageLocation: PageLocation, page: MemorySegment) { 131 | logger.info { "Reading page $pageLocation" } 132 | val fc = getOrCreateTableFile(TableLocation(pageLocation.dbId, pageLocation.tableId)) 133 | val alignedBuffer = page.asByteBuffer().alignedSlice(PAGE_SIZE) // Align to PAGE_SIZE for Direct I/O (O_DIRECT) 134 | val readBytes = fc.read(alignedBuffer, pageLocation.pageId * PAGE_SIZE.toLong()) 135 | logger.info { "Read $readBytes bytes" } 136 | // Special case for empty files, fill the buffer with 0's 137 | if (readBytes == 0) { 138 | page.fill((0).toByte()) 139 | return 140 | } 141 | if (readBytes != PAGE_SIZE) { 142 | throw IllegalStateException("Read $readBytes bytes, expected $PAGE_SIZE") 143 | } 144 | } 145 | 146 | override fun writePage(pageLocation: PageLocation, page: MemorySegment) { 147 | logger.info { "Writing page $pageLocation" } 148 | val fc = getOrCreateTableFile(TableLocation(pageLocation.dbId, pageLocation.tableId)) 149 | val alignedBuffer = page.asByteBuffer().alignedSlice(PAGE_SIZE) // Align to PAGE_SIZE for Direct I/O (O_DIRECT) 150 | val writtenBytes = fc.write(alignedBuffer, pageLocation.pageId * PAGE_SIZE.toLong()) 151 | logger.info { "Wrote $writtenBytes bytes" } 152 | } 153 | 154 | // TODO: Should this be named "getNumPagesZeroIndexed"? 155 | override fun getNumPages(tableLocation: TableLocation): Int { 156 | logger.info { "Getting number of pages for table $tableLocation" } 157 | val fc = getOrCreateTableFile(tableLocation) 158 | val numPages = (fc.size() / PAGE_SIZE).toInt() 159 | // TODO: This feels gross, revisit this if you get a better idea 160 | return when (numPages) { 161 | 0 -> 0 162 | else -> numPages - 1 // Pages are 0-indexed 163 | } 164 | } 165 | 166 | override fun allocatePage(tableLocation: TableLocation): Int { 167 | logger.info { "Allocating page for table $tableLocation" } 168 | val fc = getOrCreateTableFile(tableLocation) 169 | val newPageId = getNumPages(tableLocation) 170 | val emptyBuffer = MemorySegment.allocateNative(PAGE_SIZE.toLong(), MemorySession.global()) 171 | val alignedBuffer = emptyBuffer.asByteBuffer().alignedSlice(PAGE_SIZE) 172 | fc.write(alignedBuffer) 173 | return newPageId 174 | } 175 | } 176 | 177 | 178 | // Eviction policy interface for the buffer pool 179 | // Has callback methods for when a page is pinned or unpinned 180 | // The eviction policy should implement the logic for which page to evict 181 | interface IEvictionPolicy { 182 | fun pagePinned(pageId: Int) 183 | fun pageUnpinned(pageId: Int) 184 | fun evictPage(): Int 185 | } 186 | 187 | // LRU eviction policy 188 | class LRUEvictionPolicy : IEvictionPolicy { 189 | private val logger = KotlinLogging.logger {} 190 | 191 | private val pageIds = ArrayDeque() 192 | 193 | override fun pagePinned(pageId: Int) { 194 | logger.info { "Page $pageId pinned" } 195 | pageIds.remove(pageId) 196 | pageIds.addLast(pageId) 197 | } 198 | 199 | override fun pageUnpinned(pageId: Int) { 200 | logger.info { "Page $pageId unpinned" } 201 | pageIds.remove(pageId) 202 | pageIds.addFirst(pageId) 203 | } 204 | 205 | override fun evictPage(): Int { 206 | logger.info { "Evicting page" } 207 | return pageIds.removeFirst() 208 | } 209 | } 210 | 211 | class ClockEvictionPolicy : IEvictionPolicy { 212 | private val logger = KotlinLogging.logger {} 213 | 214 | private var clockHand = 0 215 | private val refBit = BooleanArray(BUFFER_POOL_PAGES) 216 | 217 | override fun pagePinned(pageId: Int) { 218 | logger.info { "Page $pageId pinned" } 219 | refBit[pageId] = true 220 | } 221 | 222 | override fun pageUnpinned(pageId: Int) { 223 | logger.info { "Page $pageId unpinned" } 224 | refBit[pageId] = false 225 | } 226 | 227 | override fun evictPage(): Int { 228 | logger.info { "Evicting page" } 229 | while (true) { 230 | if (!refBit[clockHand]) { 231 | val pageId = clockHand 232 | clockHand = (clockHand + 1) % BUFFER_POOL_PAGES 233 | return pageId 234 | } 235 | refBit[clockHand] = false 236 | clockHand = (clockHand + 1) % BUFFER_POOL_PAGES 237 | } 238 | } 239 | } 240 | 241 | interface IBufferPool { 242 | fun fetchPage(pageLocation: PageLocation): MemorySegment 243 | fun unpinPage(pageLocation: PageLocation, isDirty: Boolean) 244 | fun flushPage(pageLocation: PageLocation) 245 | fun flushAllPages(dbId: Int) 246 | 247 | // Run an action using a page from the buffer pool. Calls "fetchPage" and "unpinPage" automatically 248 | // The "isDirty" flag is passed to "unpinPage" with the boolean returned by the action 249 | fun withPage(pageLocation: PageLocation, action: (MemorySegment) -> Pair): T { 250 | fetchPage(pageLocation) 251 | val (result, isDirty) = action(fetchPage(pageLocation)) 252 | unpinPage(pageLocation, isDirty) 253 | return result 254 | } 255 | 256 | fun withPage(pageLocation: PageLocation, willDirty: Boolean = false, action: (MemorySegment) -> T): T { 257 | return withPage(pageLocation) { page -> Pair(action(page), willDirty) } 258 | } 259 | } 260 | 261 | fun ReentrantReadWriteLock.withReadLock(action: () -> T): T { 262 | this.readLock().lock() 263 | try { 264 | return action() 265 | } finally { 266 | this.readLock().unlock() 267 | } 268 | } 269 | 270 | fun ReentrantReadWriteLock.withWriteLock(action: () -> T): T { 271 | this.writeLock().lock() 272 | try { 273 | return action() 274 | } finally { 275 | this.writeLock().unlock() 276 | } 277 | } 278 | 279 | class BufferPool( 280 | private val diskManager: IDiskManager, 281 | private val evictionPolicy: IEvictionPolicy 282 | ) : IBufferPool { 283 | private val logger = KotlinLogging.logger {} 284 | 285 | private val bufferPool: MemorySegment 286 | private val pages: Array 287 | private val freeList: ArrayDeque 288 | 289 | private val pinCount = IntArray(BUFFER_POOL_PAGES) 290 | private val isDirty = BooleanArray(BUFFER_POOL_PAGES) 291 | private val refBit = BooleanArray(BUFFER_POOL_PAGES) 292 | private val pageTable = HashMap() 293 | private val pageTableRwLock = ReentrantReadWriteLock() 294 | 295 | init { 296 | bufferPool = MemorySegment.allocateNative(PAGE_SIZE * BUFFER_POOL_PAGES.toLong(), MemorySession.global()) 297 | pages = Array(BUFFER_POOL_PAGES) { bufferPool.asSlice((it * PAGE_SIZE).toLong(), PAGE_SIZE.toLong()) } 298 | freeList = ArrayDeque((0 until BUFFER_POOL_PAGES).toList()) 299 | } 300 | 301 | private fun pinPage(pageLocation: PageLocation) { 302 | logger.info { "Pinning page $pageLocation" } 303 | val pageId = pageTableRwLock.withReadLock { 304 | pageTable[pageLocation] 305 | } 306 | if (pageId != null) { 307 | pinCount[pageId]++ 308 | refBit[pageId] = true 309 | evictionPolicy.pagePinned(pageId) 310 | } 311 | } 312 | 313 | override fun fetchPage(pageLocation: PageLocation): MemorySegment { 314 | logger.info { "Fetching page $pageLocation" } 315 | // First, check if the page is already in the buffer pool 316 | val pageId = pageTableRwLock.withReadLock { 317 | pageTable[pageLocation] 318 | } 319 | if (pageId != null) { 320 | pinPage(pageLocation) 321 | return pages[pageId] 322 | } 323 | // If the page is not in the buffer pool, we need to evict a page 324 | val evictedPageId = evictPage() 325 | // Then, we need to read the page from disk 326 | diskManager.readPage(pageLocation, pages[evictedPageId]) 327 | // Finally, we need to update the page table 328 | pageTableRwLock.withWriteLock { 329 | pageTable[pageLocation] = evictedPageId 330 | } 331 | isDirty[evictedPageId] = false 332 | pinPage(pageLocation) 333 | return pages[evictedPageId] 334 | } 335 | 336 | private fun evictPage(): Int { 337 | logger.info { "Evicting page" } 338 | return evictionPolicy.evictPage() 339 | } 340 | 341 | override fun unpinPage(pageLocation: PageLocation, isDirty: Boolean) { 342 | logger.info { "Unpinning page $pageLocation" } 343 | val pageId = pageTableRwLock.withReadLock { pageTable[pageLocation] }!! 344 | pinCount[pageId]-- 345 | this.isDirty[pageId] = this.isDirty[pageId] || isDirty 346 | evictionPolicy.pageUnpinned(pageId) 347 | refBit[pageId] = true 348 | } 349 | 350 | override fun flushPage(pageLocation: PageLocation) { 351 | logger.info { "Flushing page $pageLocation" } 352 | val pageId = pageTableRwLock.withReadLock { pageTable[pageLocation] }!! 353 | if (isDirty[pageId]) { 354 | diskManager.writePage(pageLocation, pages[pageId]) 355 | isDirty[pageId] = false 356 | } 357 | 358 | freeList.addLast(pageId) 359 | pageTable.remove(pageLocation) 360 | 361 | // We don't call "evictionPolicy.pageUnpinned(pageId)" here because we are evicting the page 362 | // and not unpinning it 363 | pinCount[pageId] = 0 364 | isDirty[pageId] = false 365 | refBit[pageId] = false 366 | } 367 | 368 | override fun flushAllPages(dbId: Int) { 369 | logger.info { "Flushing all pages for dbId $dbId" } 370 | pageTableRwLock.withWriteLock { 371 | pageTable.keys.filter { it.dbId == dbId }.forEach { flushPage(it) } 372 | } 373 | } 374 | } 375 | 376 | 377 | // Accessor Methods for Heap Page memory layout 378 | object HeapPage { 379 | private val logger = KotlinLogging.logger {} 380 | 381 | private const val PAGE_LSN_OFFSET = 0L 382 | private const val PAGE_ID_OFFSET = 8L 383 | private const val PAGE_NUM_SLOTS_OFFSET = 12L 384 | private const val PAGE_FREE_SPACE_OFFSET = 16L 385 | private const val PAGE_HEADER_SIZE = 20L 386 | private const val PAGE_SLOT_SIZE = 4L 387 | private const val PAGE_MAX_TUPLE_SIZE = PAGE_SIZE - PAGE_HEADER_SIZE - PAGE_SLOT_SIZE 388 | private const val DELETED_RECORD_FLAG = 0x8000.toShort() 389 | 390 | fun getLSN(page: MemorySegment): Long { 391 | return page.get(Constants.JAVA_LONG_UNALIGNED, PAGE_LSN_OFFSET) 392 | } 393 | 394 | fun setLSN(page: MemorySegment, lsn: Long) { 395 | page.set(Constants.JAVA_LONG_UNALIGNED, PAGE_LSN_OFFSET, lsn) 396 | } 397 | 398 | fun getPageId(page: MemorySegment): Int { 399 | return page.get(Constants.JAVA_INT_UNALIGNED, PAGE_ID_OFFSET) 400 | } 401 | 402 | fun setPageId(page: MemorySegment, pageId: Int) { 403 | page.set(Constants.JAVA_INT_UNALIGNED, PAGE_ID_OFFSET, pageId) 404 | } 405 | 406 | fun getNumSlots(page: MemorySegment): Short { 407 | return page.get(Constants.JAVA_SHORT_UNALIGNED, PAGE_NUM_SLOTS_OFFSET) 408 | } 409 | 410 | fun setNumSlots(page: MemorySegment, numSlots: Short) { 411 | page.set(Constants.JAVA_SHORT_UNALIGNED, PAGE_NUM_SLOTS_OFFSET, numSlots) 412 | } 413 | 414 | fun getFreeSpace(page: MemorySegment): Short { 415 | return page.get(Constants.JAVA_SHORT_UNALIGNED, PAGE_FREE_SPACE_OFFSET) 416 | } 417 | 418 | fun setFreeSpace(page: MemorySegment, freeSpace: Short) { 419 | page.set(Constants.JAVA_SHORT_UNALIGNED, PAGE_FREE_SPACE_OFFSET, freeSpace) 420 | } 421 | 422 | fun getSlotOffset(page: MemorySegment, slotId: Int): Short { 423 | return page.get(Constants.JAVA_SHORT_UNALIGNED, PAGE_HEADER_SIZE + slotId * PAGE_SLOT_SIZE) 424 | } 425 | 426 | fun setSlotOffset(page: MemorySegment, slotId: Short, offset: Short) { 427 | page.set(Constants.JAVA_SHORT_UNALIGNED, PAGE_HEADER_SIZE + slotId * PAGE_SLOT_SIZE, offset) 428 | } 429 | 430 | fun getSlotLength(page: MemorySegment, slotId: Int): Short { 431 | return page.get(Constants.JAVA_SHORT_UNALIGNED, PAGE_HEADER_SIZE + slotId * PAGE_SLOT_SIZE + 2) 432 | } 433 | 434 | fun setSlotLength(page: MemorySegment, slotId: Short, length: Short) { 435 | page.set(Constants.JAVA_SHORT_UNALIGNED, PAGE_HEADER_SIZE + slotId * PAGE_SLOT_SIZE + 2, length) 436 | } 437 | 438 | fun getTuple(page: MemorySegment, slotId: Int): MemorySegment { 439 | val offset = getSlotOffset(page, slotId) 440 | val length = getSlotLength(page, slotId) 441 | return page.asSlice(offset.toLong(), length.toLong()) 442 | } 443 | 444 | fun insertTuple(page: MemorySegment, tuple: MemorySegment): RecordId { 445 | logger.info { 446 | "Inserting tuple: [page id=${getPageId(page)}, tuple size=${tuple.byteSize()}, " + 447 | "free space=${getFreeSpace(page)}, num slots=${getNumSlots(page)}]" 448 | } 449 | 450 | val numSlots = getNumSlots(page) 451 | val freeSpace = getFreeSpace(page) 452 | val tupleSize = tuple.byteSize() 453 | if (tupleSize > PAGE_MAX_TUPLE_SIZE) { 454 | throw IllegalArgumentException("Tuple size is too large") 455 | } 456 | if (tupleSize > freeSpace) { 457 | throw IllegalArgumentException("Not enough space") 458 | } 459 | // Copy the tuple to the page 460 | val offset = freeSpace - tupleSize 461 | MemorySegment.copy(tuple, 0, page, offset, tupleSize) 462 | // Update the page header 463 | setSlotOffset(page, numSlots, offset.toShort()) 464 | setSlotLength(page, numSlots, tupleSize.toShort()) 465 | setNumSlots(page, (numSlots + 1).toShort()) 466 | setFreeSpace(page, (freeSpace - tupleSize).toShort()) 467 | return RecordId(getPageId(page), numSlots.toInt()) 468 | } 469 | 470 | fun deleteTuple(page: MemorySegment, slotId: Short) { 471 | val freeSpace = getFreeSpace(page) 472 | val length = getSlotLength(page, slotId.toInt()) 473 | // Update the page header 474 | setSlotOffset(page, slotId, DELETED_RECORD_FLAG) 475 | setSlotLength(page, slotId, 0) 476 | setFreeSpace(page, (freeSpace + length).toShort()) 477 | // We clean up deleted tuples lazily, so we don't need to do anything here 478 | } 479 | 480 | fun isDeleted(page: MemorySegment, slotId: Short): Boolean { 481 | return getSlotOffset(page, slotId.toInt()) == DELETED_RECORD_FLAG 482 | } 483 | 484 | fun getRecordId(page: MemorySegment, slotId: Int): RecordId { 485 | return RecordId(getPageId(page), slotId) 486 | } 487 | 488 | fun initializeEmptyPage(page: MemorySegment, pageId: Int) { 489 | setLSN(page, 0) 490 | setPageId(page, pageId) 491 | setNumSlots(page, 0) 492 | setFreeSpace(page, PAGE_SIZE.toShort()) 493 | } 494 | } 495 | 496 | 497 | object HeapPage2 { 498 | private val logger = KotlinLogging.logger {} 499 | private const val DELETED_RECORD_FLAG = 0x8000.toShort() 500 | 501 | val PAGE_LAYOUT = MemoryLayout.structLayout( 502 | Constants.JAVA_INT_UNALIGNED.withName("LSN"), 503 | Constants.JAVA_INT_UNALIGNED.withName("pageId"), 504 | Constants.JAVA_SHORT_UNALIGNED.withName("numSlots"), 505 | Constants.JAVA_SHORT_UNALIGNED.withName("freeSpace"), 506 | Constants.JAVA_INT_UNALIGNED.withName("reserved"), 507 | MemoryLayout.sequenceLayout( 508 | 4096 / 8, MemoryLayout.structLayout( 509 | Constants.JAVA_SHORT_UNALIGNED.withName("offset"), 510 | Constants.JAVA_SHORT_UNALIGNED.withName("length"), 511 | ) 512 | ).withName("slots") 513 | ) 514 | 515 | val LSN_VH: VarHandle = PAGE_LAYOUT.varHandle(PathElement.groupElement("LSN")) 516 | val PAGE_ID_VH = PAGE_LAYOUT.varHandle(PathElement.groupElement("pageId")) 517 | val NUM_SLOTS_VH = PAGE_LAYOUT.varHandle(PathElement.groupElement("numSlots")) 518 | val FREE_SPACE_VH = PAGE_LAYOUT.varHandle(PathElement.groupElement("freeSpace")) 519 | 520 | val SLOT_OFFSET_VH = PAGE_LAYOUT.varHandle( 521 | PathElement.groupElement("slots"), 522 | PathElement.sequenceElement(), 523 | PathElement.groupElement("offset") 524 | ) 525 | val SLOT_LENGTH_VH = PAGE_LAYOUT.varHandle( 526 | PathElement.groupElement("slots"), 527 | PathElement.sequenceElement(), 528 | PathElement.groupElement("length") 529 | ) 530 | 531 | fun getLSN(page: MemorySegment): Long = LSN_VH.get(page) as Long 532 | fun setLSN(page: MemorySegment, lsn: Long): Unit = LSN_VH.set(page, lsn.toInt()) 533 | 534 | fun getPageId(page: MemorySegment): Int = PAGE_ID_VH.get(page) as Int 535 | fun setPageId(page: MemorySegment, pageId: Int) = PAGE_ID_VH.set(page, pageId) 536 | 537 | fun getNumSlots(page: MemorySegment): Short = NUM_SLOTS_VH.get(page) as Short 538 | fun setNumSlots(page: MemorySegment, numSlots: Short) = NUM_SLOTS_VH.set(page, numSlots) 539 | 540 | fun getFreeSpace(page: MemorySegment): Short = FREE_SPACE_VH.get(page) as Short 541 | fun setFreeSpace(page: MemorySegment, freeSpace: Short) = FREE_SPACE_VH.set(page, freeSpace) 542 | 543 | fun getSlotOffset(page: MemorySegment, slotId: Int): Short = SLOT_OFFSET_VH.get(page, slotId) as Short 544 | fun setSlotOffset(page: MemorySegment, slotId: Short, offset: Short) = SLOT_OFFSET_VH.set(page, slotId, offset) 545 | 546 | fun getSlotLength(page: MemorySegment, slotId: Int): Short = SLOT_LENGTH_VH.get(page, slotId) as Short 547 | fun setSlotLength(page: MemorySegment, slotId: Short, length: Short) = SLOT_LENGTH_VH.set(page, slotId, length) 548 | 549 | fun getTuple(page: MemorySegment, slotId: Int): MemorySegment { 550 | val offset = getSlotOffset(page, slotId) 551 | val length = getSlotLength(page, slotId) 552 | return page.asSlice(offset.toLong(), length.toLong()) 553 | } 554 | 555 | fun insertTuple(page: MemorySegment, tuple: MemorySegment): RecordId { 556 | logger.info { 557 | "Inserting tuple: [page id=${getPageId(page)}, tuple size=${tuple.byteSize()}, " + 558 | "free space=${getFreeSpace(page)}, num slots=${getNumSlots(page)}]" 559 | } 560 | 561 | val numSlots = getNumSlots(page) 562 | val freeSpace = getFreeSpace(page) 563 | val tupleSize = tuple.byteSize() 564 | if (tupleSize > freeSpace) { 565 | throw IllegalArgumentException("Not enough space") 566 | } 567 | // Copy the tuple to the page 568 | val offset = freeSpace - tupleSize 569 | MemorySegment.copy(tuple, 0, page, offset, tupleSize) 570 | // Update the page header 571 | setSlotOffset(page, numSlots, offset.toShort()) 572 | setSlotLength(page, numSlots, tupleSize.toShort()) 573 | setNumSlots(page, (numSlots + 1).toShort()) 574 | setFreeSpace(page, (freeSpace - tupleSize).toShort()) 575 | return RecordId(getPageId(page), numSlots.toInt()) 576 | } 577 | 578 | fun deleteTuple(page: MemorySegment, slotId: Short) { 579 | val freeSpace = getFreeSpace(page) 580 | val length = getSlotLength(page, slotId.toInt()) 581 | // Update the page header 582 | setSlotOffset(page, slotId, DELETED_RECORD_FLAG) 583 | setSlotLength(page, slotId, 0) 584 | setFreeSpace(page, (freeSpace + length).toShort()) 585 | // We clean up deleted tuples lazily, so we don't need to do anything here 586 | } 587 | 588 | fun isDeleted(page: MemorySegment, slotId: Short): Boolean { 589 | return getSlotOffset(page, slotId.toInt()) == DELETED_RECORD_FLAG 590 | } 591 | 592 | fun getRecordId(page: MemorySegment, slotId: Int): RecordId { 593 | return RecordId(getPageId(page), slotId) 594 | } 595 | 596 | fun initializeEmptyPage(page: MemorySegment, pageId: Int) { 597 | setLSN(page, 0) 598 | setPageId(page, pageId) 599 | setNumSlots(page, 0) 600 | setFreeSpace(page, PAGE_SIZE.toShort()) 601 | } 602 | } 603 | 604 | 605 | class HeapFile( 606 | val bufferPool: IBufferPool, 607 | val diskManager: IDiskManager, 608 | val tableLocation: TableLocation, 609 | ) { 610 | private var numPages: Int 611 | 612 | private val logger = KotlinLogging.logger {} 613 | 614 | init { 615 | logger.info { "Initializing HeapFile class for table $tableLocation" } 616 | numPages = diskManager.getNumPages(tableLocation) 617 | 618 | logger.info { "Found $numPages pages in file" } 619 | if (numPages == 0) { 620 | logger.info { "Table $tableLocation is empty, creating a new page" } 621 | val location = getPageLocation(0) 622 | val page = bufferPool.fetchPage(location) 623 | HeapPage2.initializeEmptyPage(page, 0) 624 | bufferPool.unpinPage(location, true) 625 | } 626 | } 627 | 628 | private fun getPageLocation(pageId: Int): PageLocation { 629 | return PageLocation(tableLocation.dbId, tableLocation.tableId, pageId) 630 | } 631 | 632 | fun deleteFile() { 633 | diskManager.removeTableFile(tableLocation) 634 | } 635 | 636 | fun getNumPages(): Int { 637 | return numPages 638 | } 639 | 640 | fun getFreeSpace(pageId: Int): Int { 641 | val location = getPageLocation(pageId) 642 | val page = bufferPool.fetchPage(location) 643 | val freeSpace = HeapPage2.getFreeSpace(page) 644 | bufferPool.unpinPage(location, false) 645 | return freeSpace.toInt() 646 | } 647 | 648 | fun getTuple(recordId: RecordId): MemorySegment { 649 | val location = getPageLocation(recordId.pageId) 650 | val page = bufferPool.fetchPage(location) 651 | val tuple = HeapPage2.getTuple(page, recordId.slotId) 652 | bufferPool.unpinPage(location, false) 653 | return tuple 654 | } 655 | 656 | fun insertTuple(tuple: MemorySegment): RecordId { 657 | logger.info { "Inserting tuple $tuple into table $tableLocation" } 658 | val location = getPageLocation(numPages) 659 | val page = bufferPool.fetchPage(location) 660 | // Try to insert the tuple into the page 661 | // Catching the exception means that the page is full, so we need to create a new page 662 | return try { 663 | val recordId = HeapPage2.insertTuple(page, tuple) 664 | bufferPool.unpinPage(location, isDirty = true) 665 | recordId 666 | } catch (e: IllegalArgumentException) { 667 | bufferPool.unpinPage(location, isDirty = false) 668 | logger.info { "Page $location is full, creating a new page" } 669 | val newLocation = getPageLocation(numPages) 670 | val newPage = bufferPool.fetchPage(newLocation) 671 | HeapPage2.initializeEmptyPage(newPage, numPages) 672 | bufferPool.unpinPage(newLocation, isDirty = true) 673 | numPages++ 674 | insertTuple(tuple) 675 | } 676 | } 677 | 678 | fun deleteTuple(recordId: RecordId) { 679 | val location = getPageLocation(recordId.pageId) 680 | val page = bufferPool.fetchPage(location) 681 | HeapPage2.deleteTuple(page, recordId.slotId.toShort()) 682 | bufferPool.unpinPage(location, isDirty = true) 683 | } 684 | 685 | fun flushAllPages() { 686 | bufferPool.flushAllPages(dbId = tableLocation.dbId) 687 | } 688 | 689 | fun scan( 690 | callback: (RecordId, MemorySegment) -> Unit, 691 | ) { 692 | logger.info { "Scanning table $tableLocation with total pages: $numPages" } 693 | for (pageId in 0 until numPages + 1) { 694 | logger.info { "Scanning page $pageId" } 695 | val location = getPageLocation(pageId) 696 | val page = bufferPool.fetchPage(location) 697 | val numSlots = HeapPage2.getNumSlots(page) 698 | for (slotId in 0 until numSlots) { 699 | if (!HeapPage2.isDeleted(page, slotId.toShort())) { 700 | val recordId = HeapPage2.getRecordId(page, slotId) 701 | val tuple = HeapPage2.getTuple(page, slotId) 702 | callback(recordId, tuple) 703 | } 704 | } 705 | bufferPool.unpinPage(location, isDirty = false) 706 | } 707 | } 708 | } 709 | 710 | // Free Space Map Page 711 | // The free space map page is a special page that keeps track of the free space in each page of the table 712 | // It is stored as a bitmap, where each 4-bit nibble represents the fullness of a single page 713 | // The first nibble represents the first page, the second nibble represents the second page, etc. 714 | // The free space is encoded in log2 scale, so 0 means the page is full, 1 means the page is half full, etc. 715 | object FreeSpaceMapPage { 716 | const val PAGE_SIZE = 4096 717 | const val NUM_PAGE_ENTRIES_PER_PAGE = 2 * PAGE_SIZE // 2 per byte, 4096 bytes per page 718 | 719 | fun getFreeSpace(page: MemorySegment, pageId: Int): Int { 720 | val byteIndex = pageId / 2 721 | val nibbleIndex = pageId % 2 722 | val byte = page.get(ValueLayout.JAVA_BYTE, byteIndex.toLong()) 723 | val nibble = if (nibbleIndex == 0) { 724 | byte and 0x0F // Mask out the first nibble 725 | } else { 726 | // Shift right by 4 bits to get the second nibble 727 | byte.toInt() shr 4 and 0x0F 728 | } 729 | return 1 shl nibble.toInt() 730 | } 731 | 732 | fun setFreeSpace(page: MemorySegment, pageId: Int, freeSpace: Int) { 733 | val byteIndex = pageId / 2 734 | val nibbleIndex = pageId % 2 735 | val byte = page.get(ValueLayout.JAVA_BYTE, byteIndex.toLong()) 736 | val nibble = (Math.log(freeSpace.toDouble()) / Math.log(2.0)).toInt() 737 | val newByte = if (nibbleIndex == 0) { 738 | // Set the first nibble 739 | byte and 0xF0.toByte() or nibble.toByte() 740 | } else { 741 | // Set the second nibble 742 | byte and 0x0F or (nibble shl 4).toByte() 743 | } 744 | page.set(ValueLayout.JAVA_BYTE, byteIndex.toLong(), newByte) 745 | } 746 | } 747 | 748 | fun freeSpaceMapTest() { 749 | val page = MemorySegment.allocateNative(4096, MemorySession.global()) 750 | FreeSpaceMapPage.setFreeSpace(page, 0, 1) 751 | FreeSpaceMapPage.setFreeSpace(page, 1, 2) 752 | 753 | println("FreeSpaceMapPage.getFreeSpace(page, 0) = " + FreeSpaceMapPage.getFreeSpace(page, 0)) 754 | println("FreeSpaceMapPage.getFreeSpace(page, 1) = " + FreeSpaceMapPage.getFreeSpace(page, 1)) 755 | 756 | // Try to set an invalid free space value 757 | FreeSpaceMapPage.setFreeSpace(page, 0, 9) 758 | // Assert it was only set to the max value 759 | println("FreeSpaceMapPage.getFreeSpace(page, 0) = " + FreeSpaceMapPage.getFreeSpace(page, 0)) 760 | assert(FreeSpaceMapPage.getFreeSpace(page, 0) == 8) 761 | } 762 | 763 | 764 | fun bufferPoolTest() { 765 | val bufferPool = BufferPool(diskManager = DiskManager, evictionPolicy = ClockEvictionPolicy()) 766 | val pageLocation = PageLocation(1, 2, 3) 767 | 768 | val page = bufferPool.fetchPage(pageLocation) 769 | page.asByteBuffer().putInt(0, 42) 770 | 771 | bufferPool.unpinPage(pageLocation, true) 772 | bufferPool.flushPage(pageLocation) 773 | 774 | val page2 = bufferPool.fetchPage(pageLocation) 775 | println(page2.asByteBuffer().int) 776 | } 777 | 778 | fun heapPageTest() { 779 | val bufferPool = BufferPool(diskManager = DiskManager, evictionPolicy = ClockEvictionPolicy()) 780 | val pageLocation = PageLocation(1, 2, 3) 781 | 782 | val page = bufferPool.fetchPage(pageLocation) 783 | // Initialize the Heap Page free space 784 | HeapPage2.setFreeSpace(page, PAGE_SIZE.toShort()) 785 | 786 | // Insert a tuple 787 | val tuple = MemorySegment.allocateNative(4, MemorySession.global()) 788 | tuple.asByteBuffer().putInt(0, 42) 789 | val recordId = HeapPage2.insertTuple(page, tuple) 790 | bufferPool.unpinPage(pageLocation, true) 791 | 792 | // Get the tuple 793 | val page3 = bufferPool.fetchPage(pageLocation) 794 | val tuple2 = HeapPage2.getTuple(page3, recordId.slotId) 795 | println("Got tuple: ${tuple2.asByteBuffer().int}") 796 | } 797 | 798 | fun heapFileTest() { 799 | val bufferPool = BufferPool(diskManager = DiskManager, evictionPolicy = ClockEvictionPolicy()) 800 | 801 | val tuple = MemorySegment.allocateNative(4, MemorySession.global()) 802 | tuple.asByteBuffer().putInt(0, 42) 803 | 804 | // Wipe the current DB_ROOT 805 | val dbRoot = File(DiskManager.DB_ROOT) 806 | dbRoot.deleteRecursively() 807 | 808 | // Create a HeapFile 809 | val heapFile = HeapFile(bufferPool, DiskManager, TableLocation(0, 0)) 810 | assert(heapFile.getNumPages() == 1) 811 | assert(heapFile.getFreeSpace(0) == PAGE_SIZE) 812 | 813 | val recordId1 = heapFile.insertTuple(tuple) 814 | val recordId2 = heapFile.insertTuple(tuple) 815 | println("Inserted tuple at $recordId1") 816 | println("Inserted tuple at $recordId2") 817 | 818 | // Scan the HeapFile 819 | heapFile.scan { recordId, tuple -> 820 | val bb = tuple.asByteBuffer() 821 | println("Scanned tuple: ${bb.int}") 822 | } 823 | 824 | heapFile.flushAllPages() 825 | } 826 | 827 | fun main() { 828 | heapFileTest() 829 | freeSpaceMapTest() 830 | } 831 | --------------------------------------------------------------------------------