Building a database from scratch in C: Part 1

Before you begin developing a database, it’s important to make sure you know why you’re building it. If you’re building it for fun, or as a miniature project to learn how databases work, that’s fair enough. However, if you are developing a database because you need one for your project, unless it is so niche that a database does not already exist for it, you should try to find an existing database instead of reinventing the wheel.

Getting the environment set up

Before we go any further, it’s important to get the environment set up. For this, you need to ensure that you have a C compiler installed (MSVC, gcc, etc), CMake, an editor of sorts, and git.

Make sure to create a repository, and get git set up so you can push changes. For this, we will be using a small .gitignore file, containing just 3 items:

# Build directories
build/**
lib/**
bin/**

Build scripts

Now we need a few scripts, just to make it easier to compile the project. We have one for Window, and one for Linux:

@echo off

REM Windows build script
REM ---------------------------------------------------------
REM Options:
REM     build_win.bat "path/to/build" Debug/Release
REM Example:
REM     build_win.bat build Debug

if not exist %1 (
    echo %1 does not exist, creating...
    mkdir %1
)

if not %2 == Debug if not %2 == Release (
    echo %2 is not a valid option
    exit /B 1
)

cd %1

if %2 == Debug (
    echo Building with Debug configuration

    cmake .. -G "Visual Studio 17 2022" -A x64
    cmake --build . --config Debug
) else if %2 == Release (
    echo Building with Release configuration

    cmake .. -G "Visual Studio 17 2022" -A x64
    cmake --build . --config Release
)

cd ..
#!/usr/bin/env bash

# Linux build script
# -------------------------------------------------------
# Options:
#   build_linux.sh "path/to/build" Debug/Release
# Example:
#   build_linux.sh build Debug

set -e

if [ "$2" != "Debug" ] && [ "$2" != "Release" ]; then
    echo "$2 must be either Debug or Release"
    exit 1
fi

if [ ! -d "$1" ]; then
    echo "Creating build path at $1"
    mkdir -p "$1" || { echo "Failed to create directory at $1"; exit 1; }
fi

cd "$1" || exit 1

if [ "$2" = "Debug" ]; then
    echo "Building with Debug configuration"

    cmake .. -DCMAKE_BUILD_TYPE=Debug
    cmake --build . -- -j$(nproc)
elif [ "$2" = "Release" ]; then
    echo "Building with Release configuration"

    cmake .. -DCMAKE_BUILD_TYPE=Release
    cmake --build . -- -j$(nproc)
fi

cd ..

These scripts can be run pretty easily with just build.bat/build.sh build Debug/Release.

CMake configuration

For this, we will be using 3.24 as the minimum CMake version, and C99 as the C standard. We first set the CMake minimum required version with the following:

cmake_minimum_required(VERSION 3.24)

Then, we need to configure the project, you can name it what you’d like, just make sure that there are no spaces in the name (You can swap them for underscores however):

project(
    custom_db
    VERSION 1.0.0
    DESCRIPTION "My custom database"
    LANGUAGES C
)

Now we set a few other details, like C configuration, and output directories:

set(CMAKE_C_STANDARD 99)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set(CMAKE_C_STANDARD_REQUIRED ON)

set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/bin)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/lib)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/lib)

Once all those are set, we can move on to actually setting up the executable, we use the GLOB_RECURSE option on file to select all C file within the source directory, and then put these into add_executable with the project name. After that, we can add target the include directory, so we split off the header file from the source files:

file(GLOB_RECURSE SRC_FILES
    ${CMAKE_CURRENT_SOURCE_DIR}/src/*.c
)

add_executable(${PROJECT_NAME} ${SRC_FILES})

target_include_directories(
    ${PROJECT_NAME}
    PRIVATE
    ${CMAKE_CURRENT_SOURCE_DIR}/include
)

Once this is complete, we can set up some compiler optimisations options:

if(CMAKE_BUILD_TYPE STREQUAL "Release")
    set(OPTIONS_OPTIMIZATION -O2)
elseif(CMAKE_BUILD_TYPE STREQUAL "Debug")
    set(OPTIONS_OPTIMIZATION -g)
elseif(CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo")
    set(OPTIONS_OPTIMIZATION -O2 -g)
elseif(CMAKE_BUILD_TYPE STREQUAL "MinSizeRel")
    set(OPTIONS_OPTIMIZATION -Os)
endif()

After all of that, your final CMakeLists.txt file should look like this:

cmake_minimum_required(VERSION 3.24)

project(
    custom_db
    VERSION 1.0.0
    DESCRIPTION "My custom database"
    LANGUAGES C
)

set(CMAKE_C_STANDARD 99)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set(CMAKE_C_STANDARD_REQUIRED ON)

set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/bin)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/lib)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/lib)

file(GLOB_RECURSE SRC_FILES
    ${CMAKE_CURRENT_SOURCE_DIR}/src/*.c
)

add_executable(${PROJECT_NAME} ${SRC_FILES})

target_include_directories(
    ${PROJECT_NAME}
    PRIVATE
    ${CMAKE_CURRENT_SOURCE_DIR}/include
)

if(CMAKE_BUILD_TYPE STREQUAL "Release")
    set(OPTIONS_OPTIMIZATION -O2)
elseif(CMAKE_BUILD_TYPE STREQUAL "Debug")
    set(OPTIONS_OPTIMIZATION -g)
elseif(CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo")
    set(OPTIONS_OPTIMIZATION -O2 -g)
elseif(CMAKE_BUILD_TYPE STREQUAL "MinSizeRel")
    set(OPTIONS_OPTIMIZATION -Os)
endif()

Series

Part 1: Environment set up (This)
Part 2: Architecture design
Part 3: What is SQL?

Loading


Discover more from WTDawson

Subscribe to get the latest posts sent to your email.

Leave a Reply