├── 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 |
--------------------------------------------------------------------------------