#StackBounty: #c++ #c++11 #file-system #database Writing my own DBMS: Storing databases

Bounty: 50

I’ve been interested in writing something meaningful in C++ for a long time, yet I had a hard not picking Java or C# for a new project… Now I’ve found something for which C++ seems to be the right tool: Writing a Database Management System.

I’m going to show you the code for the first step: Storing databases.

The code is written in Microsoft Visual C++ 17 and I’m trying to have code that is as modern as possible. I also didn’t intend to build a serialization library, it just happened. This library exports its methods through a DLL.


stdafx.h

#pragma once

#include "targetver.h"

#define WIN32_LEAN_AND_MEAN             // Exclude rarely-used stuff from Windows headers
// Windows Header Files:
#include <windows.h>

#include <string>
#include <vector>
#include <fstream>
#include <filesystem>
#include <optional>

#ifdef DBMS_EXPORTS
#define DBMS_EXPORTS_API __declspec(dllexport)
#else
#define DBMS_EXPORTS_API __declspec(dllimport)
#endif

targetver.h

#pragma once

// Including SDKDDKVer.h defines the highest available Windows platform.

// If you wish to build your application for a previous Windows platform, include WinSDKVer.h and
// set the _WIN32_WINNT macro to the platform you wish to support before including SDKDDKVer.h.

#include <SDKDDKVer.h>

DBMS.h

#pragma once

#include "stdafx.h"
#include "Database.h"

namespace DBMS
{    
    class DBMS
    {
    public:
        DBMS_EXPORTS_API DBMS(const std::string file);

        DBMS_EXPORTS_API Database& CreateDatabase(const std::string file, const std::string name);

        DBMS_EXPORTS_API std::optional<std::reference_wrapper<const Database>> GetDatabaseByFile(const std::string file) const;

        DBMS_EXPORTS_API std::optional<std::reference_wrapper<const Database>> GetDatabaseByName(const std::string name) const;

        DBMS_EXPORTS_API void DeleteDatabase(const Database& database);

    private:
        const std::string file;
        std::vector<Database> databases;

        void SaveToDisk() const;
    };
}

Database.h

#pragma once

#include "stdafx.h"

namespace DBMS
{
    class Database
    {
    public:
        DBMS_EXPORTS_API Database(const std::string file, const std::string name);

        DBMS_EXPORTS_API std::string GetFile() const;

        DBMS_EXPORTS_API std::string GetName() const;

        DBMS_EXPORTS_API bool operator==(const Database& other) const;

        DBMS_EXPORTS_API bool operator!=(const Database& other) const;

    private:
        std::string file;
        std::string name;
    };
}

Serializer.h

#pragma once

#include "stdafx.h"
#include "Database.h"
#include <iostream>

namespace DBMS
{
    template <typename T>
    class Serializer
    {
    public:
        static void Serialize(std::ofstream& out, const T& elem)
        {
            static_assert(std::is_trivially_copyable_v<T>, "standard template is only defined for trivially copyable values");
            static_assert(!std::is_pointer_v<T>, "standard template is not defined for pointers of trivially copyable values");
            static_assert(!std::is_array_v<T>, "standard template is not defined for arrays of trivially copyable values");
            out.write(reinterpret_cast<const char*>(&elem), sizeof(T));
        }

        static T Deserialize(std::ifstream& in)
        {
            static_assert(std::is_trivially_copyable_v<T>, "standard template is only defined for trivially copyable values");
            T elem;
            in.read(reinterpret_cast<char*>(&elem), sizeof(T));
            return elem;
        }
    };

    template <typename T>
    class Serializer<T*>
    {
    public:
        static void Serialize(std::ofstream& out, const T* const& elem, size_t length)
        {
            static_assert(std::is_trivially_copyable_v<T>, "standard template is only defined for trivially copyable values");
            out.write(reinterpret_cast<const char*>(elem), sizeof(T) * length);
        }

        static T* Deserialize(std::ifstream& in, size_t length)
        {
            static_assert(std::is_trivially_copyable_v<T>, "standard template is only defined for trivially copyable values");
            auto elements = std::make_unique<T[]>(length);
            T* raw = elements.get();
            in.read(reinterpret_cast<char*>(raw), sizeof(T) * length);
            elements.release();
            return raw;
        }
    };

    template <typename E>
    class Serializer<std::vector<E>>
    {
    public:
        static void Serialize(std::ofstream& out, const std::vector<E>& vector)
        {
            Serializer<uint32_t>::Serialize(out, vector.size());

            for (const E& elem : vector)
            {
                Serializer<E>::Serialize(out, elem);
            }
        }

        static std::vector<E> Deserialize(std::ifstream& in)
        {
            auto size = Serializer<uint32_t>::Deserialize(in);
            auto vector = std::vector<E>();
            vector.reserve(size);

            for (uint32_t i = 0; i < size; i++)
            {
                vector.push_back(Serializer<E>::Deserialize(in));
            }

            return vector;
        }
    };

    template <>
    class Serializer<std::string>
    {
    public:
        static void Serialize(std::ofstream& out, const std::string& string)
        {
            Serializer<uint32_t>::Serialize(out, string.length());
            Serializer<const char*>::Serialize(out, string.data(), string.length() + 1);
        }

        static std::string Deserialize(std::ifstream& in)
        {
            auto length = Serializer<uint32_t>::Deserialize(in);
            auto raw = Serializer<char*>::Deserialize(in, length + 1);
            return std::string(raw, length + 1);
        }
    };

    template <>
    class Serializer<Database>
    {
    public:
        static void Serialize(std::ofstream& out, const Database& database)
        {
            Serializer<std::string>::Serialize(out, database.GetFile());
            Serializer<std::string>::Serialize(out, database.GetName());
        }

        static Database Deserialize(std::ifstream& in)
        {
            auto file = Serializer<std::string>::Deserialize(in);
            auto name = Serializer<std::string>::Deserialize(in);
            return Database(file, name);
        }
    };
}

DBMS.cpp

// DBMS.cpp : Defines the exported functions for the DLL application.
//

#include "stdafx.h"
#include "Database.h"
#include "Serializer.h"

namespace DBMS
{
    class DBMS
    {
    public:
        DBMS_EXPORTS_API DBMS(const std::string file) : file(file)
        {
            if (std::experimental::filesystem::exists(file))
            {
                std::ifstream fs(file, std::ios::binary);
                if (fs.is_open())
                {
                    databases = Serializer<std::vector<Database>>::Deserialize(fs);
                }
            }
            else
            {
                // save empty database to disk
                SaveToDisk();
            }
        }

        DBMS_EXPORTS_API Database& CreateDatabase(const std::string file, const std::string name)
        {
            Database database(file, name);
            databases.push_back(database);
            SaveToDisk();
            return databases.back();
        }

        DBMS_EXPORTS_API std::optional<std::reference_wrapper<const Database>> GetDatabaseByFile(const std::string file) const
        {
            for (auto& database : databases)
            {
                if (database.GetFile() == file)
                {
                    return database;
                }
            }
            return std::optional<std::reference_wrapper<const Database>>();
        }

        DBMS_EXPORTS_API std::optional<std::reference_wrapper<const Database>> GetDatabaseByName(const std::string name) const
        {
            for (auto& database : databases)
            {
                if (database.GetName() == name)
                {
                    return database;
                }
            }
            return std::optional<std::reference_wrapper<const Database>>();
        }

        DBMS_EXPORTS_API void DeleteDatabase(const Database& database)
        {
            for (const auto& db : databases)
            {
                if (db == database)
                {
                    auto it = std::find(databases.begin(), databases.end(), database);
                    if (it != databases.end())
                    {
                        databases.erase(it);
                    }
                }
            }
            SaveToDisk();
        }

    private:
        const std::string file;
        std::vector<Database> databases;

        void SaveToDisk() const
        {
            std::ofstream fs(file, std::ios::binary, std::ios::trunc);   // trunc is a dirty hack to not have to be smart
            if (fs.is_open())
            {
                Serializer<std::vector<Database>>::Serialize(fs, databases);
            }
        }
    };
}

Database.cpp

#include "stdafx.h"

namespace DBMS
{
    class Database
    {
    public:
        DBMS_EXPORTS_API Database(const std::string file, const std::string name) : file(file), name(name)
        {

        }

        DBMS_EXPORTS_API std::string GetFile() const
        {
            return file;
        }

        DBMS_EXPORTS_API std::string GetName() const
        {
            return name;
        }

        DBMS_EXPORTS_API bool operator==(const Database& other) const
        {
            return (file == other.file && name == other.name);
        }

        DBMS_EXPORTS_API bool operator!=(const Database& other) const
        {
            return !(*this == other);
        }

    private:
        std::string file;
        std::string name;
    };
}


Get this bounty!!!

Leave a Reply