Initial commit
commit
37632a207a
|
@ -0,0 +1,2 @@
|
||||||
|
.idea
|
||||||
|
cmake-build*
|
|
@ -0,0 +1,13 @@
|
||||||
|
cmake_minimum_required(VERSION 3.15)
|
||||||
|
project(YeltsinDB C)
|
||||||
|
|
||||||
|
set(CMAKE_C_STANDARD 11)
|
||||||
|
|
||||||
|
include_directories(inc)
|
||||||
|
|
||||||
|
add_library(YeltsinDB STATIC
|
||||||
|
src/ydb.c inc/YeltsinDB/ydb.h
|
||||||
|
inc/YeltsinDB/error_code.h src/table_page.c
|
||||||
|
inc/YeltsinDB/table_page.h
|
||||||
|
inc/YeltsinDB/constants.h
|
||||||
|
inc/YeltsinDB/types.h inc/YeltsinDB/macro.h)
|
|
@ -0,0 +1,3 @@
|
||||||
|
# YeltsinDB
|
||||||
|
|
||||||
|
Storage engine for study project written in C.
|
|
@ -0,0 +1,14 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file constants.h
|
||||||
|
* @brief A header with engine constants.
|
||||||
|
* @todo Further documentation.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define YDB_TABLE_FILE_SIGN "TBL!"
|
||||||
|
#define YDB_TABLE_PAGE_SIZE 65536
|
||||||
|
|
||||||
|
#define YDB_TABLE_PAGE_FLAG_DELETED (1)
|
|
@ -0,0 +1,74 @@
|
||||||
|
#pragma once
|
||||||
|
/**
|
||||||
|
* @file error_code.h
|
||||||
|
* @brief A header with engine's error codes.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @defgroup ydb_error_codes YeltsinDB error codes
|
||||||
|
* The codes for errors that could occur during engine's work.
|
||||||
|
* @{
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The operation has been done successfully.
|
||||||
|
*/
|
||||||
|
#define YDB_ERR_SUCCESS ( 0)
|
||||||
|
/**
|
||||||
|
* @brief The table does *not* exist.
|
||||||
|
*/
|
||||||
|
#define YDB_ERR_TABLE_NOT_EXIST (-1)
|
||||||
|
/**
|
||||||
|
* @brief The table does already exist.
|
||||||
|
*/
|
||||||
|
#define YDB_ERR_TABLE_EXIST (-2)
|
||||||
|
/**
|
||||||
|
* @brief No more memory left in the page.
|
||||||
|
*/
|
||||||
|
#define YDB_ERR_PAGE_NO_MORE_MEM (-3)
|
||||||
|
/**
|
||||||
|
* @brief The table data is corrupted.
|
||||||
|
*/
|
||||||
|
#define YDB_ERR_TABLE_DATA_CORRUPTED (-4)
|
||||||
|
/**
|
||||||
|
* @brief Table data version mismatch.
|
||||||
|
*/
|
||||||
|
#define YDB_ERR_TABLE_DATA_VERSION_MISMATCH (-5)
|
||||||
|
/**
|
||||||
|
* @brief The engine instance is not initialized.
|
||||||
|
*/
|
||||||
|
#define YDB_ERR_INSTANCE_NOT_INITIALIZED (-6)
|
||||||
|
/**
|
||||||
|
* @brief The engine instance is in use.
|
||||||
|
*/
|
||||||
|
#define YDB_ERR_INSTANCE_IN_USE (-7)
|
||||||
|
/**
|
||||||
|
* @brief The engine instance is not in use.
|
||||||
|
*/
|
||||||
|
#define YDB_ERR_INSTANCE_NOT_IN_USE (-8)
|
||||||
|
/**
|
||||||
|
* @brief Page index is out of range.
|
||||||
|
*/
|
||||||
|
#define YDB_ERR_PAGE_INDEX_OUT_OF_RANGE (-9)
|
||||||
|
/**
|
||||||
|
* @brief The page is not initialized.
|
||||||
|
*/
|
||||||
|
#define YDB_ERR_PAGE_NOT_INITIALIZED (-10)
|
||||||
|
/**
|
||||||
|
* @brief The destination pointer is NULL.
|
||||||
|
*/
|
||||||
|
#define YDB_ERR_WRITE_TO_NULLPTR (-11)
|
||||||
|
/**
|
||||||
|
* @brief An attempt to read/write 0 bytes has occurred.
|
||||||
|
*/
|
||||||
|
#define YDB_ERR_ZERO_SIZE_RW (-11)
|
||||||
|
/**
|
||||||
|
* @brief No more pages left in the file.
|
||||||
|
*/
|
||||||
|
#define YDB_ERR_NO_MORE_PAGES (-12)
|
||||||
|
/**
|
||||||
|
* @brief An unknown error has occurred.
|
||||||
|
*/
|
||||||
|
#define YDB_ERR_UNKNOWN (-32768)
|
||||||
|
|
||||||
|
/** @} */ // End of @defgroup
|
|
@ -0,0 +1,9 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file macro.h
|
||||||
|
* @brief The macros used in the engine.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** @brief Return `err` if `x` is null. */
|
||||||
|
#define THROW_IF_NULL(x, err) if (!(x)) return (err)
|
|
@ -0,0 +1,104 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <YeltsinDB/types.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file table_page.h
|
||||||
|
* @brief A header with the definition of table page and functions to work with it.
|
||||||
|
*/
|
||||||
|
|
||||||
|
struct __YDB_TablePage;
|
||||||
|
|
||||||
|
/** @brief A table page type. */
|
||||||
|
typedef struct __YDB_TablePage YDB_TablePage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Allocate a new page.
|
||||||
|
* @param size Page size.
|
||||||
|
* @return A pointer to allocated page.
|
||||||
|
* @sa ydb_page_free()
|
||||||
|
*/
|
||||||
|
YDB_TablePage* ydb_page_alloc(YDB_PageSize size);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief De-allocate page.
|
||||||
|
* @param page Page to be de-allocated.
|
||||||
|
*/
|
||||||
|
void ydb_page_free(YDB_TablePage* page);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Seek page data position to `pos`.
|
||||||
|
* @param page A page.
|
||||||
|
* @param pos A new position (absolute value).
|
||||||
|
* @return Operation status.
|
||||||
|
*
|
||||||
|
* @todo Possible error codes.
|
||||||
|
*/
|
||||||
|
YDB_Error ydb_page_data_seek(YDB_TablePage* page, YDB_PageSize pos);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get current position in the page data.
|
||||||
|
* @param page A page.
|
||||||
|
* @return Page data position.
|
||||||
|
*/
|
||||||
|
YDB_PageSize ydb_page_data_tell(YDB_TablePage* page);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Read `n` bytes from a page to `dst`.
|
||||||
|
* @param src A page with the data to be read.
|
||||||
|
* @param dst A pointer to destination.
|
||||||
|
* @param n An amount of bytes to read.
|
||||||
|
* @return Operation status.
|
||||||
|
*
|
||||||
|
* @todo Possible error codes.
|
||||||
|
*/
|
||||||
|
YDB_Error ydb_page_data_read(YDB_TablePage* src, void* dst, YDB_PageSize n);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Write `n` bytes from `dst` to a page.
|
||||||
|
* @param dst A destination page.
|
||||||
|
* @param src A pointer to the source data.
|
||||||
|
* @param n An amount of bytes to write.
|
||||||
|
* @return Operation status.
|
||||||
|
*
|
||||||
|
* @todo Possible error codes.
|
||||||
|
*/
|
||||||
|
YDB_Error ydb_page_data_write(YDB_TablePage* dst, void* src, YDB_PageSize n);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get page flags.
|
||||||
|
* @param page A page.
|
||||||
|
* @return Page flags.
|
||||||
|
* @sa ydb_page_flags_set()
|
||||||
|
*
|
||||||
|
* Notice that page flags are represented as an integer, so you should apply bit mask to extract them.
|
||||||
|
*/
|
||||||
|
YDB_Flags ydb_page_flags_get(YDB_TablePage* page);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set page flags.
|
||||||
|
* @param page A page.
|
||||||
|
* @param flags New page flags
|
||||||
|
* @sa ydb_page_flags_get()
|
||||||
|
*/
|
||||||
|
void ydb_page_flags_set(YDB_TablePage* page, YDB_Flags flags);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get row count.
|
||||||
|
* @param page A page.
|
||||||
|
* @return Row count.
|
||||||
|
* @sa ydb_page_row_count_set()
|
||||||
|
*/
|
||||||
|
YDB_PageSize ydb_page_row_count_get(YDB_TablePage* page);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set row count.
|
||||||
|
* @param page A page
|
||||||
|
* @param row_count New row count value.
|
||||||
|
* @sa ydb_page_row_count_get()
|
||||||
|
*/
|
||||||
|
void ydb_page_row_count_set(YDB_TablePage* page, YDB_PageSize row_count);
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file types.h
|
||||||
|
* @brief A header with the engine types.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** @brief YeltsinDB error type. */
|
||||||
|
typedef int16_t YDB_Error;
|
||||||
|
/** @brief YeltsinDB data file offset type. */
|
||||||
|
typedef uint64_t YDB_Offset;
|
||||||
|
/** */
|
||||||
|
typedef uint16_t YDB_PageSize;
|
||||||
|
typedef uint8_t YDB_Flags;
|
|
@ -0,0 +1,132 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <YeltsinDB/types.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file ydb.h
|
||||||
|
* @brief The main engine include file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
struct __YDB_Engine;
|
||||||
|
|
||||||
|
/** @brief YDB engine instance type. */
|
||||||
|
typedef struct __YDB_Engine YDB_Engine;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Initialize YeltsinDB instance.
|
||||||
|
*
|
||||||
|
* @sa ydb_terminate_instance()
|
||||||
|
*/
|
||||||
|
YDB_Engine* ydb_init_instance();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Terminate YeltsinDB instance gracefully.
|
||||||
|
*
|
||||||
|
* @param instance A YeltsinDB instance.
|
||||||
|
*/
|
||||||
|
void ydb_terminate_instance(YDB_Engine* instance);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Load table data from file to the instance.
|
||||||
|
*
|
||||||
|
* @param instance A YeltsinDB instance.
|
||||||
|
* @param path A path to the table data file.
|
||||||
|
* @return Operation status.
|
||||||
|
* @sa ydb_create_table(), ydb_unload_table()
|
||||||
|
*
|
||||||
|
* If the instance is not free (has already been loaded with table data), returns #YDB_ERR_INSTANCE_IN_USE.
|
||||||
|
* @todo Possible error codes.
|
||||||
|
*/
|
||||||
|
YDB_Error ydb_load_table(YDB_Engine* instance, const char *path);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Create table data, write a file and load it to the instance.
|
||||||
|
*
|
||||||
|
* @param instance A YeltsinDB instance.
|
||||||
|
* @param path A path to the table data file.
|
||||||
|
* @return Operation status.
|
||||||
|
* @sa ydb_load_table(), ydb_unload_table()
|
||||||
|
*
|
||||||
|
* If the instance is not free (has already been loaded with table data), returns #YDB_ERR_INSTANCE_IN_USE.
|
||||||
|
* @todo Possible error codes.
|
||||||
|
*/
|
||||||
|
YDB_Error ydb_create_table(YDB_Engine* instance, const char *path);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Unload table instance, making it free for further table loading.
|
||||||
|
* @param instance A *busy* YeltsinDB instance.
|
||||||
|
* @return Operation status.
|
||||||
|
*
|
||||||
|
* @todo Possible error codes.
|
||||||
|
*/
|
||||||
|
YDB_Error ydb_unload_table(YDB_Engine* instance);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Seek to `index`th page.
|
||||||
|
* @param instance A YeltsinDB instance.
|
||||||
|
* @param index Page index.
|
||||||
|
* @return Operation status.
|
||||||
|
*
|
||||||
|
* If an error was returned, the state of instance remains the same as it was before execution.
|
||||||
|
* @todo Possible error codes.
|
||||||
|
*/
|
||||||
|
YDB_Error ydb_seek_page(YDB_Engine* instance, int64_t index);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get current page index.
|
||||||
|
* @param instance A YeltsinDB instance.
|
||||||
|
* @return Current page index.
|
||||||
|
* @sa error_code.h
|
||||||
|
*
|
||||||
|
* Could return error codes (negative values).
|
||||||
|
* @todo Possible error codes.
|
||||||
|
*/
|
||||||
|
int64_t ydb_tell_page(YDB_Engine* instance);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Switch to next page.
|
||||||
|
* @param instance A YeltsinDB instance.
|
||||||
|
* @return Operation status.
|
||||||
|
*
|
||||||
|
* @todo Possible error codes.
|
||||||
|
*/
|
||||||
|
YDB_Error ydb_next_page(YDB_Engine* instance);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Replaces current page with `page` argument.
|
||||||
|
* @param instance A YeltsinDB instance.
|
||||||
|
* @param page A page to replace current one.
|
||||||
|
* @return Operation status.
|
||||||
|
*
|
||||||
|
* @todo Possible error codes.
|
||||||
|
*/
|
||||||
|
YDB_Error ydb_replace_page(YDB_Engine* instance, YDB_TablePage* page);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Appends a page to a table.
|
||||||
|
* @param instance A YeltsinDB instance.
|
||||||
|
* @param page A page to be inserted.
|
||||||
|
* @return Operation status.
|
||||||
|
*
|
||||||
|
* @todo Possible error codes.
|
||||||
|
*/
|
||||||
|
YDB_Error ydb_append_page(YDB_Engine* instance, YDB_TablePage* page);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get current page object.
|
||||||
|
* @param instance A YeltsinDB instance.
|
||||||
|
* @return Current page object.
|
||||||
|
*
|
||||||
|
* Returns NULL on error.
|
||||||
|
*/
|
||||||
|
YDB_TablePage* ydb_get_current_page(YDB_Engine* instance);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete current page.
|
||||||
|
* @param instance A YeltsinDB instance.
|
||||||
|
* @return Operation status.
|
||||||
|
*/
|
||||||
|
YDB_Error ydb_delete_current_page(YDB_Engine* instance);
|
||||||
|
|
||||||
|
// TODO: rebuild page offsets, etc.
|
|
@ -0,0 +1,116 @@
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <YeltsinDB/constants.h>
|
||||||
|
#include <YeltsinDB/table_page.h>
|
||||||
|
#include <YeltsinDB/error_code.h>
|
||||||
|
#include <YeltsinDB/macro.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @struct __YDB_TablePage
|
||||||
|
* @brief A struct that defines a page of a table.
|
||||||
|
*/
|
||||||
|
|
||||||
|
struct __YDB_TablePage {
|
||||||
|
char* data; /**< Raw page data. */
|
||||||
|
YDB_PageSize size; /**< The amount of memory in a page. */
|
||||||
|
YDB_PageSize pos; /**< The current position of page data I/O stream. TODO: rewrite it */
|
||||||
|
YDB_PageSize row_count; /**< Row count in a page. */
|
||||||
|
YDB_Flags flags; /**< The flags of a page. */
|
||||||
|
};
|
||||||
|
|
||||||
|
YDB_TablePage* ydb_page_alloc(YDB_PageSize size) {
|
||||||
|
YDB_TablePage* new_page = malloc(sizeof(YDB_TablePage));
|
||||||
|
new_page->pos = 0;
|
||||||
|
new_page->flags = 0; // No flags set
|
||||||
|
new_page->size = size;
|
||||||
|
new_page->data = calloc(1, new_page->size);
|
||||||
|
return new_page;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ydb_page_free(YDB_TablePage* page) {
|
||||||
|
free(page->data);
|
||||||
|
free(page);
|
||||||
|
}
|
||||||
|
|
||||||
|
YDB_Error ydb_page_data_seek(YDB_TablePage *page, YDB_PageSize pos) {
|
||||||
|
THROW_IF_NULL(page, YDB_ERR_PAGE_NOT_INITIALIZED);
|
||||||
|
|
||||||
|
if ((pos < 0) || (pos >= page->size)) {
|
||||||
|
return YDB_ERR_PAGE_INDEX_OUT_OF_RANGE;
|
||||||
|
}
|
||||||
|
page->pos = pos;
|
||||||
|
|
||||||
|
return YDB_ERR_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
YDB_PageSize ydb_page_data_tell(YDB_TablePage *page) {
|
||||||
|
THROW_IF_NULL(page, YDB_ERR_PAGE_NOT_INITIALIZED);
|
||||||
|
return page->pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
YDB_Error ydb_page_data_read(YDB_TablePage *src, void *dst, YDB_PageSize n) {
|
||||||
|
THROW_IF_NULL(src, YDB_ERR_PAGE_NOT_INITIALIZED);
|
||||||
|
THROW_IF_NULL(dst, YDB_ERR_WRITE_TO_NULLPTR);
|
||||||
|
THROW_IF_NULL(n, YDB_ERR_ZERO_SIZE_RW);
|
||||||
|
|
||||||
|
if (src->pos == src->size) {
|
||||||
|
return YDB_ERR_PAGE_INDEX_OUT_OF_RANGE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trait as int64_t to prevent overflow.
|
||||||
|
int64_t new_pos = src->pos + n;
|
||||||
|
if (new_pos > (int64_t) src->size) {
|
||||||
|
return YDB_ERR_PAGE_INDEX_OUT_OF_RANGE;
|
||||||
|
}
|
||||||
|
|
||||||
|
void* data_start = src->data + src->pos;
|
||||||
|
memcpy(dst, data_start, n);
|
||||||
|
src->pos += n;
|
||||||
|
|
||||||
|
return YDB_ERR_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
YDB_Error ydb_page_data_write(YDB_TablePage *dst, void *src, YDB_PageSize n) {
|
||||||
|
THROW_IF_NULL(src, YDB_ERR_WRITE_TO_NULLPTR);
|
||||||
|
THROW_IF_NULL(dst, YDB_ERR_PAGE_NOT_INITIALIZED);
|
||||||
|
THROW_IF_NULL(n, YDB_ERR_ZERO_SIZE_RW);
|
||||||
|
|
||||||
|
if (dst->pos == dst->size) {
|
||||||
|
return YDB_ERR_PAGE_NO_MORE_MEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trait as int64_t to prevent overflow.
|
||||||
|
int64_t new_pos = dst->pos + n;
|
||||||
|
if (new_pos > (int64_t) dst->size) {
|
||||||
|
return YDB_ERR_PAGE_INDEX_OUT_OF_RANGE;
|
||||||
|
}
|
||||||
|
|
||||||
|
void* data_start = dst->data + dst->pos;
|
||||||
|
memcpy(data_start, src, n);
|
||||||
|
dst->pos += n;
|
||||||
|
|
||||||
|
return YDB_ERR_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
YDB_Flags ydb_page_flags_get(YDB_TablePage *page) {
|
||||||
|
THROW_IF_NULL(page, 0);
|
||||||
|
return page->flags;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ydb_page_flags_set(YDB_TablePage *page, YDB_Flags flags) {
|
||||||
|
if (!page) return;
|
||||||
|
page->flags = flags;
|
||||||
|
}
|
||||||
|
|
||||||
|
YDB_PageSize ydb_page_row_count_get(YDB_TablePage *page) {
|
||||||
|
THROW_IF_NULL(page, 0);
|
||||||
|
return page->row_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ydb_page_row_count_set(YDB_TablePage *page, YDB_PageSize row_count) {
|
||||||
|
if (!page) return;
|
||||||
|
page->row_count = row_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,373 @@
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <YeltsinDB/error_code.h>
|
||||||
|
#include <YeltsinDB/constants.h>
|
||||||
|
#include <YeltsinDB/macro.h>
|
||||||
|
#include <YeltsinDB/table_page.h>
|
||||||
|
#include <YeltsinDB/ydb.h>
|
||||||
|
|
||||||
|
/** @todo These things:
|
||||||
|
* - Page data (current, maybe cache next and prev too)
|
||||||
|
* - MAYBE query caching
|
||||||
|
*/
|
||||||
|
struct __YDB_Engine {
|
||||||
|
uint8_t ver_major; /**< A major version of loaded table. */
|
||||||
|
uint8_t ver_minor; /**< A minor version of loaded table. */
|
||||||
|
YDB_Offset first_page_offset; /**< A location of the first page in file. */
|
||||||
|
YDB_Offset last_page_offset; /**< A location of the last page in file. */
|
||||||
|
YDB_Offset last_free_page_offset; /**< A location of last free page in file. */
|
||||||
|
|
||||||
|
YDB_Offset prev_page_offset; /**< A location of previous page in file. */
|
||||||
|
YDB_Offset curr_page_offset; /**< A location of current page in file. */
|
||||||
|
YDB_Offset next_page_offset; /**< A location of next page in file. */
|
||||||
|
|
||||||
|
int64_t current_page_index; /**< Current page index in range [0, page_count). */
|
||||||
|
|
||||||
|
YDB_TablePage *curr_page; /**< A pointer to the current page. */
|
||||||
|
|
||||||
|
uint8_t in_use; /**< "In use" flag. */
|
||||||
|
char *filename; /**< Current table data file name. */
|
||||||
|
FILE *fd; /**< Current table data file descriptor. */
|
||||||
|
};
|
||||||
|
|
||||||
|
YDB_Engine *ydb_init_instance() {
|
||||||
|
YDB_Engine *new_instance = calloc(1, sizeof(YDB_Engine));
|
||||||
|
return new_instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ydb_terminate_instance(YDB_Engine *instance) {
|
||||||
|
ydb_unload_table(instance);
|
||||||
|
|
||||||
|
// And after all that, the instance could be freed
|
||||||
|
free(instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Internal usage only!
|
||||||
|
// Its only purpose to read page data and set next_page offset.
|
||||||
|
// You should write prev_page offset on your own.
|
||||||
|
inline YDB_Error __ydb_read_page(YDB_Engine *inst) {
|
||||||
|
THROW_IF_NULL(inst, YDB_ERR_INSTANCE_NOT_INITIALIZED);
|
||||||
|
|
||||||
|
fseek(inst->fd, inst->curr_page_offset, SEEK_SET);
|
||||||
|
|
||||||
|
char p_data[YDB_TABLE_PAGE_SIZE];
|
||||||
|
size_t bytes_read = fread(p_data, 1, YDB_TABLE_PAGE_SIZE, inst->fd);
|
||||||
|
if (bytes_read != YDB_TABLE_PAGE_SIZE) {
|
||||||
|
return YDB_ERR_TABLE_DATA_CORRUPTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
YDB_Flags page_flags = p_data[0];
|
||||||
|
YDB_Offset next;
|
||||||
|
YDB_PageSize row_count;
|
||||||
|
memcpy(&next, p_data + sizeof(page_flags), sizeof(next));
|
||||||
|
memcpy(&row_count, p_data + sizeof(page_flags) + sizeof(next), sizeof(row_count));
|
||||||
|
|
||||||
|
//TODO move it to consts. Also page meta size could differ in different major versions.
|
||||||
|
const YDB_PageSize meta_size = sizeof(page_flags) + sizeof(next) + sizeof(row_count);
|
||||||
|
const YDB_PageSize data_size = YDB_TABLE_PAGE_SIZE - meta_size;
|
||||||
|
|
||||||
|
YDB_TablePage *p = ydb_page_alloc(data_size);
|
||||||
|
|
||||||
|
ydb_page_flags_set(p, page_flags);
|
||||||
|
ydb_page_row_count_set(p, row_count);
|
||||||
|
|
||||||
|
YDB_Error write_status = ydb_page_data_write(p, p_data + meta_size, data_size);
|
||||||
|
if (write_status) return write_status;
|
||||||
|
|
||||||
|
inst->curr_page = p;
|
||||||
|
inst->next_page_offset = next;
|
||||||
|
|
||||||
|
return YDB_ERR_SUCCESS;
|
||||||
|
}
|
||||||
|
// Moves file position to allocated block.
|
||||||
|
// Also changes last_free_page_offset.
|
||||||
|
inline YDB_Offset __ydb_allocate_page_and_seek(YDB_Engine *inst) {
|
||||||
|
YDB_Offset result = 0;
|
||||||
|
// If no free pages in the table, then...
|
||||||
|
if (inst->last_free_page_offset == 0) {
|
||||||
|
// Return the end of the file where a new page will be allocated
|
||||||
|
fseek(inst->fd, 0, SEEK_END);
|
||||||
|
result = ftell(inst->fd);
|
||||||
|
|
||||||
|
// Write next page offset in the previous page
|
||||||
|
fseek(inst->fd, inst->last_page_offset + 1, SEEK_SET); // Skip page flag
|
||||||
|
fwrite(&result, sizeof(result), 1, inst->fd);
|
||||||
|
|
||||||
|
// Allocate an empty chunk
|
||||||
|
fseek(inst->fd, 0, SEEK_END);
|
||||||
|
char* new_page_data = calloc(1, YDB_TABLE_PAGE_SIZE);
|
||||||
|
fwrite(new_page_data, YDB_TABLE_PAGE_SIZE, 1, inst->fd);
|
||||||
|
free(new_page_data);
|
||||||
|
|
||||||
|
// Write last page offset
|
||||||
|
inst->last_page_offset = result;
|
||||||
|
fseek(inst->fd, 14, SEEK_SET); // TODO move magic offsets somewhere
|
||||||
|
fwrite(&inst->last_page_offset, sizeof(YDB_Offset), 1, inst->fd);
|
||||||
|
fflush(inst->fd);
|
||||||
|
} else {
|
||||||
|
// Return last free page offset
|
||||||
|
result = inst->last_free_page_offset;
|
||||||
|
|
||||||
|
// Read last free page offset after allocation
|
||||||
|
fseek(inst->fd, result + 1, SEEK_SET); // Skip flag
|
||||||
|
fread(&(inst->last_free_page_offset), sizeof(YDB_Offset), 1, inst->fd);
|
||||||
|
fseek(inst->fd, -(long)(sizeof(YDB_Offset) + 1), SEEK_CUR);
|
||||||
|
|
||||||
|
// Rewrite page header
|
||||||
|
char page_header[] = "\x00" // No flags
|
||||||
|
"\x00\x00\x00\x00\x00\x00\x00\x00" // No next page
|
||||||
|
"\x00\x00"; // No rows
|
||||||
|
fwrite(page_header, sizeof(page_header)-1, 1, inst->fd);
|
||||||
|
|
||||||
|
// Write last free page offset after allocation in file
|
||||||
|
fseek(inst->fd, 22, SEEK_SET); // Skip unneeded data TODO beautify it
|
||||||
|
fwrite(&(inst->last_free_page_offset), sizeof(YDB_Offset), 1, inst->fd);
|
||||||
|
}
|
||||||
|
fflush(inst->fd);
|
||||||
|
fseek(inst->fd, result, SEEK_SET);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
YDB_Error ydb_load_table(YDB_Engine *instance, const char *path) {
|
||||||
|
THROW_IF_NULL(instance, YDB_ERR_INSTANCE_NOT_INITIALIZED);
|
||||||
|
THROW_IF_NULL(!instance->in_use, YDB_ERR_INSTANCE_IN_USE);
|
||||||
|
|
||||||
|
instance->in_use = -1; // unsigned value overflow to fill all the bits
|
||||||
|
instance->filename = strdup(path);
|
||||||
|
|
||||||
|
if (access(instance->filename, F_OK) == -1) {
|
||||||
|
// TODO: Windows does not check W_OK correctly, use other methods.
|
||||||
|
// TODO: if can't read/write, throw other error
|
||||||
|
return YDB_ERR_TABLE_NOT_EXIST;
|
||||||
|
}
|
||||||
|
instance->fd = fopen(instance->filename, "rb+");
|
||||||
|
THROW_IF_NULL(instance->fd, YDB_ERR_UNKNOWN); // TODO file open error
|
||||||
|
|
||||||
|
char signature[4];
|
||||||
|
fread(signature, 1, 4, instance->fd);
|
||||||
|
int signature_match = memcmp(signature, YDB_TABLE_FILE_SIGN, 4) == 0;
|
||||||
|
if (!signature_match) {
|
||||||
|
return YDB_ERR_TABLE_DATA_CORRUPTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
fread(&(instance->ver_major), sizeof(instance->ver_major), 1, instance->fd);
|
||||||
|
fread(&(instance->ver_minor), sizeof(instance->ver_minor), 1, instance->fd);
|
||||||
|
|
||||||
|
if (instance->ver_major != 0) { // TODO proper version check
|
||||||
|
return YDB_ERR_TABLE_DATA_VERSION_MISMATCH;
|
||||||
|
}
|
||||||
|
|
||||||
|
fread(&(instance->first_page_offset), sizeof(YDB_Offset), 1, instance->fd);
|
||||||
|
fread(&(instance->last_page_offset), sizeof(YDB_Offset), 1, instance->fd);
|
||||||
|
fread(&(instance->last_free_page_offset), sizeof(YDB_Offset), 1, instance->fd);
|
||||||
|
// TODO check offsets
|
||||||
|
|
||||||
|
instance->curr_page_offset = instance->first_page_offset;
|
||||||
|
return __ydb_read_page(instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
YDB_Error ydb_unload_table(YDB_Engine *instance) {
|
||||||
|
THROW_IF_NULL(instance, YDB_ERR_INSTANCE_NOT_INITIALIZED);
|
||||||
|
THROW_IF_NULL(instance->in_use, YDB_ERR_INSTANCE_NOT_IN_USE);
|
||||||
|
THROW_IF_NULL(instance->curr_page, YDB_ERR_PAGE_NOT_INITIALIZED);
|
||||||
|
|
||||||
|
YDB_Engine *i = instance;
|
||||||
|
|
||||||
|
i->ver_major = 0;
|
||||||
|
i->ver_minor = 0;
|
||||||
|
i->first_page_offset = 0;
|
||||||
|
i->last_page_offset = 0;
|
||||||
|
i->last_free_page_offset = 0;
|
||||||
|
i->prev_page_offset = 0;
|
||||||
|
i->curr_page_offset = 0;
|
||||||
|
i->next_page_offset = 0;
|
||||||
|
i->current_page_index = 0;
|
||||||
|
ydb_page_free(i->curr_page);
|
||||||
|
i->curr_page = NULL;
|
||||||
|
free(i->filename);
|
||||||
|
fclose(i->fd);
|
||||||
|
|
||||||
|
// Unset "in use" flag
|
||||||
|
instance->in_use = 0;
|
||||||
|
|
||||||
|
return YDB_ERR_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
YDB_Error ydb_create_table(YDB_Engine *instance, const char *path) {
|
||||||
|
THROW_IF_NULL(instance, YDB_ERR_INSTANCE_NOT_INITIALIZED);
|
||||||
|
THROW_IF_NULL(!instance->in_use, YDB_ERR_INSTANCE_IN_USE);
|
||||||
|
|
||||||
|
if (access(instance->filename, F_OK) != -1) {
|
||||||
|
// TODO: Windows does not check W_OK correctly, use other methods.
|
||||||
|
// TODO: if can't read/write, throw other error
|
||||||
|
return YDB_ERR_TABLE_EXIST;
|
||||||
|
}
|
||||||
|
|
||||||
|
FILE *f = fopen(path, "wb");
|
||||||
|
|
||||||
|
char tpl[] = YDB_TABLE_FILE_SIGN
|
||||||
|
"\x00\x01"
|
||||||
|
"\x00\x00\x00\x00\x00\x00\x00\x1E"
|
||||||
|
"\x00\x00\x00\x00\x00\x00\x00\x1E"
|
||||||
|
"\x00\x00\x00\x00\x00\x00\x00\x00";
|
||||||
|
fwrite(tpl, sizeof(tpl)-1, 1, f);
|
||||||
|
|
||||||
|
char *first_page = calloc(1, YDB_TABLE_PAGE_SIZE);
|
||||||
|
fwrite(first_page, YDB_TABLE_PAGE_SIZE, 1, f);
|
||||||
|
free(first_page);
|
||||||
|
|
||||||
|
fclose(f);
|
||||||
|
|
||||||
|
return ydb_load_table(instance, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t ydb_tell_page(YDB_Engine *instance) {
|
||||||
|
THROW_IF_NULL(instance, YDB_ERR_INSTANCE_NOT_INITIALIZED);
|
||||||
|
THROW_IF_NULL(instance->in_use, YDB_ERR_INSTANCE_NOT_IN_USE);
|
||||||
|
return instance->current_page_index;
|
||||||
|
}
|
||||||
|
|
||||||
|
YDB_Error ydb_next_page(YDB_Engine *instance) {
|
||||||
|
THROW_IF_NULL(instance, YDB_ERR_INSTANCE_NOT_INITIALIZED);
|
||||||
|
THROW_IF_NULL(instance->in_use, YDB_ERR_INSTANCE_NOT_IN_USE);
|
||||||
|
THROW_IF_NULL(instance->next_page_offset, YDB_ERR_NO_MORE_PAGES);
|
||||||
|
|
||||||
|
instance->prev_page_offset = instance->curr_page_offset;
|
||||||
|
instance->curr_page_offset = instance->next_page_offset;
|
||||||
|
++(instance->current_page_index);
|
||||||
|
return __ydb_read_page(instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
YDB_Error ydb_seek_page(YDB_Engine *instance, int64_t index) {
|
||||||
|
THROW_IF_NULL(instance, YDB_ERR_INSTANCE_NOT_INITIALIZED);
|
||||||
|
THROW_IF_NULL(instance->in_use, YDB_ERR_INSTANCE_NOT_IN_USE);
|
||||||
|
if (index < 0) {
|
||||||
|
return YDB_ERR_PAGE_INDEX_OUT_OF_RANGE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save current state
|
||||||
|
YDB_Offset prev_page_offset = instance->prev_page_offset;
|
||||||
|
YDB_Offset curr_page_offset = instance->curr_page_offset;
|
||||||
|
YDB_Offset next_page_offset = instance->next_page_offset;
|
||||||
|
int64_t curr_page_index = instance->current_page_index;
|
||||||
|
|
||||||
|
if (index < instance->current_page_index) {
|
||||||
|
instance->prev_page_offset = 0;
|
||||||
|
instance->curr_page_offset = instance->first_page_offset;
|
||||||
|
instance->current_page_index = 0;
|
||||||
|
__ydb_read_page(instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (instance->current_page_index != index) {
|
||||||
|
YDB_Error status = ydb_next_page(instance);
|
||||||
|
|
||||||
|
if (status != YDB_ERR_SUCCESS) {
|
||||||
|
instance->prev_page_offset = prev_page_offset;
|
||||||
|
instance->curr_page_offset = curr_page_offset;
|
||||||
|
instance->next_page_offset = next_page_offset;
|
||||||
|
instance->current_page_index = curr_page_index;
|
||||||
|
__ydb_read_page(instance);
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return YDB_ERR_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
YDB_TablePage* ydb_get_current_page(YDB_Engine *instance) {
|
||||||
|
THROW_IF_NULL(instance, NULL);
|
||||||
|
THROW_IF_NULL(instance->in_use, NULL);
|
||||||
|
|
||||||
|
return instance->curr_page;
|
||||||
|
}
|
||||||
|
|
||||||
|
YDB_Error ydb_append_page(YDB_Engine* instance, YDB_TablePage* page) {
|
||||||
|
THROW_IF_NULL(instance, YDB_ERR_INSTANCE_NOT_INITIALIZED);
|
||||||
|
THROW_IF_NULL(instance->in_use, YDB_ERR_INSTANCE_NOT_IN_USE);
|
||||||
|
|
||||||
|
YDB_Offset new_page_offset = __ydb_allocate_page_and_seek(instance);
|
||||||
|
|
||||||
|
YDB_PageSize rc = ydb_page_row_count_get(page);
|
||||||
|
YDB_Flags f = ydb_page_flags_get(page);
|
||||||
|
YDB_Offset next = 0;
|
||||||
|
char d[YDB_TABLE_PAGE_SIZE - 11]; // TODO move magic numbers somewhere
|
||||||
|
if (ydb_page_data_read(page, d, sizeof(d))) {
|
||||||
|
return YDB_ERR_UNKNOWN; // FIXME
|
||||||
|
}
|
||||||
|
|
||||||
|
fwrite(&f, sizeof(f), 1, instance->fd);
|
||||||
|
fwrite(&next, sizeof(next), 1, instance->fd);
|
||||||
|
fwrite(&rc, sizeof(rc), 1, instance->fd);
|
||||||
|
fwrite(d, sizeof(d), 1, instance->fd);
|
||||||
|
|
||||||
|
fflush(instance->fd);
|
||||||
|
|
||||||
|
return YDB_ERR_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
YDB_Error ydb_replace_page(YDB_Engine *instance, YDB_TablePage *page) {
|
||||||
|
THROW_IF_NULL(instance, YDB_ERR_INSTANCE_NOT_INITIALIZED);
|
||||||
|
THROW_IF_NULL(instance->in_use, YDB_ERR_INSTANCE_NOT_IN_USE);
|
||||||
|
THROW_IF_NULL(page, YDB_ERR_PAGE_NOT_INITIALIZED);
|
||||||
|
|
||||||
|
// Seek to current page in file
|
||||||
|
fseek(instance->fd, instance->curr_page_offset, SEEK_SET);
|
||||||
|
|
||||||
|
// Write flags
|
||||||
|
YDB_Flags page_flags = ydb_page_flags_get(page);
|
||||||
|
fwrite(&page_flags, sizeof(YDB_Flags), 1, instance->fd);
|
||||||
|
|
||||||
|
// Skip next page offset
|
||||||
|
fseek(instance->fd, sizeof(YDB_Offset), SEEK_CUR);
|
||||||
|
|
||||||
|
// Write row count
|
||||||
|
YDB_PageSize row_cnt = ydb_page_row_count_get(page);
|
||||||
|
fwrite(&row_cnt, sizeof(row_cnt), 1, instance->fd);
|
||||||
|
|
||||||
|
// Write data
|
||||||
|
char page_data[YDB_TABLE_PAGE_SIZE - 11]; // TODO move magic numbers somewhere
|
||||||
|
if (ydb_page_data_read(page, page_data, sizeof(page_data))) {
|
||||||
|
return YDB_ERR_UNKNOWN; // FIXME
|
||||||
|
}
|
||||||
|
fwrite(page_data, sizeof(page_data), 1, instance->fd);
|
||||||
|
|
||||||
|
// Flush buffer
|
||||||
|
fflush(instance->fd);
|
||||||
|
|
||||||
|
return YDB_ERR_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
YDB_Error ydb_delete_current_page(YDB_Engine *instance) {
|
||||||
|
THROW_IF_NULL(instance, YDB_ERR_INSTANCE_NOT_INITIALIZED);
|
||||||
|
THROW_IF_NULL(instance->in_use, YDB_ERR_INSTANCE_NOT_IN_USE);
|
||||||
|
|
||||||
|
// Mark page as deleted
|
||||||
|
fseek(instance->fd, instance->curr_page_offset, SEEK_SET);
|
||||||
|
fputc(YDB_TABLE_PAGE_FLAG_DELETED, instance->fd);
|
||||||
|
|
||||||
|
// Write last_free_page_offset as the next page for current one
|
||||||
|
fwrite(&(instance->last_free_page_offset), sizeof(YDB_Offset), 1, instance->fd);
|
||||||
|
|
||||||
|
|
||||||
|
// Link the previous page with next one (could be null ptr)
|
||||||
|
fseek(instance->fd, instance->prev_page_offset + 1, SEEK_SET); // Skip flags
|
||||||
|
fwrite(&(instance->next_page_offset), sizeof(YDB_Offset), 1, instance->fd);
|
||||||
|
|
||||||
|
// If it was the last page, rewrite last_page_offset with prev_page_offset
|
||||||
|
if (instance->next_page_offset == 0) {
|
||||||
|
fseek(instance->fd, 14, SEEK_SET);
|
||||||
|
fwrite(&(instance->prev_page_offset), sizeof(YDB_Offset), 1, instance->fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rewrite last_free_page_offset with current offset
|
||||||
|
fseek(instance->fd, 22, SEEK_SET);
|
||||||
|
fwrite(&(instance->curr_page_offset), sizeof(YDB_Offset), 1, instance->fd);
|
||||||
|
|
||||||
|
// Flush buffer
|
||||||
|
fflush(instance->fd);
|
||||||
|
|
||||||
|
return YDB_ERR_SUCCESS;
|
||||||
|
}
|
|
@ -0,0 +1,58 @@
|
||||||
|
# Table file structure
|
||||||
|
|
||||||
|
## Table file version changelog
|
||||||
|
|
||||||
|
### v0.1
|
||||||
|
Initial version.
|
||||||
|
|
||||||
|
## Overall specification
|
||||||
|
|
||||||
|
1. `TBL!` file signature (4 bytes)
|
||||||
|
2. Table file version (2 bytes)
|
||||||
|
3. The offset to the first page in a table. (8 bytes)
|
||||||
|
4. The offset to the last page in a table. (8 bytes)
|
||||||
|
5. The offset to the last available *free* page (8 bytes)
|
||||||
|
6. Pages (64 KiB each)
|
||||||
|
1. Page flags (1 byte)
|
||||||
|
2. Next page offset (8 bytes) **could be 0 if last page**
|
||||||
|
3. Row count (2 bytes)
|
||||||
|
4. Rows
|
||||||
|
1. Row flags (1 byte)
|
||||||
|
2. Row data
|
||||||
|
|
||||||
|
## Table file version specification
|
||||||
|
|
||||||
|
In the further time, it's planned to rewrite the file structure, so it's useful to have a version marker.
|
||||||
|
|
||||||
|
| 7-4 | 3-0 |
|
||||||
|
|---------------|---------------|
|
||||||
|
| Major version | Minor version |
|
||||||
|
|
||||||
|
That means that the first byte defines major version, and the second one defines minor version.
|
||||||
|
|
||||||
|
If tables with different versions are binary-compatible with each other, their major version must be the same.
|
||||||
|
|
||||||
|
## Page flags specification
|
||||||
|
|
||||||
|
| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
|
||||||
|
|-----|-----|-----|-----|-----|-----|-----|-----|
|
||||||
|
| RSV | RSV | RSV | RSV | RSV | RSV | RSV | DEL |
|
||||||
|
|
||||||
|
- **RSV** -- reserved for further usage.
|
||||||
|
- **DEL** -- free page flag.
|
||||||
|
|
||||||
|
## Row flags specification
|
||||||
|
|
||||||
|
See "Page flags specification" above.
|
||||||
|
|
||||||
|
## Free pages
|
||||||
|
|
||||||
|
A page can be called *free* iff all its rows are deleted.
|
||||||
|
If there is a free page, there actions are being done:
|
||||||
|
|
||||||
|
1. Set `DEL` page flag ((6.3) |= FLAG_DEL)
|
||||||
|
2. If a page is **not** the last one, replace next page offset in the previous page with a value in current page
|
||||||
|
((previous 6.1) = (6.1))
|
||||||
|
3. Put last available free page as the next page ((6.1) = 5)
|
||||||
|
4. Set current page offset as the offset to the last available free page ((5) = current_page_offset)
|
||||||
|
|
Loading…
Reference in New Issue