WIP: journal + transactions
I'm not even sure this code works, but I thought it would be lame to lose even *that* amount of code.add-journal
parent
c3401c2317
commit
64be6a4466
|
@ -12,4 +12,7 @@ add_library(YeltsinDB STATIC
|
||||||
inc/YeltsinDB/constants.h
|
inc/YeltsinDB/constants.h
|
||||||
inc/YeltsinDB/types.h
|
inc/YeltsinDB/types.h
|
||||||
inc/YeltsinDB/macro.h
|
inc/YeltsinDB/macro.h
|
||||||
)
|
inc/YeltsinDB/journal.h src/journal.c
|
||||||
|
inc/YeltsinDB/journal/transaction.h src/journal/transaction.c
|
||||||
|
inc/YeltsinDB/journal/transaction_op.h
|
||||||
|
inc/YDB_ext/vec.h inc/YDB_ext/vec.c)
|
|
@ -0,0 +1,20 @@
|
||||||
|
Copyright (c) 2014 rxi
|
||||||
|
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
this software and associated documentation files (the "Software"), to deal in
|
||||||
|
the Software without restriction, including without limitation the rights to
|
||||||
|
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||||
|
of the Software, and to permit persons to whom the Software is furnished to do
|
||||||
|
so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
|
@ -0,0 +1,120 @@
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2014 rxi
|
||||||
|
*
|
||||||
|
* This library is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the MIT license. See LICENSE for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "vec.h"
|
||||||
|
|
||||||
|
|
||||||
|
int vec_expand_(char **data, int *length, int *capacity, int memsz) {
|
||||||
|
if (*length + 1 > *capacity) {
|
||||||
|
void *ptr;
|
||||||
|
int n = (*capacity == 0) ? 1 : *capacity << 1;
|
||||||
|
ptr = realloc(*data, n * memsz);
|
||||||
|
if (ptr == NULL) return -1;
|
||||||
|
*data = ptr;
|
||||||
|
*capacity = n;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int vec_reserve_(char **data, int *length, int *capacity, int memsz, int n) {
|
||||||
|
(void) length;
|
||||||
|
if (n > *capacity) {
|
||||||
|
void *ptr = realloc(*data, n * memsz);
|
||||||
|
if (ptr == NULL) return -1;
|
||||||
|
*data = ptr;
|
||||||
|
*capacity = n;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int vec_reserve_po2_(
|
||||||
|
char **data, int *length, int *capacity, int memsz, int n
|
||||||
|
) {
|
||||||
|
int n2 = 1;
|
||||||
|
if (n == 0) return 0;
|
||||||
|
while (n2 < n) n2 <<= 1;
|
||||||
|
return vec_reserve_(data, length, capacity, memsz, n2);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int vec_compact_(char **data, int *length, int *capacity, int memsz) {
|
||||||
|
if (*length == 0) {
|
||||||
|
free(*data);
|
||||||
|
*data = NULL;
|
||||||
|
*capacity = 0;
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
void *ptr;
|
||||||
|
int n = *length;
|
||||||
|
ptr = realloc(*data, n * memsz);
|
||||||
|
if (ptr == NULL) return -1;
|
||||||
|
*capacity = n;
|
||||||
|
*data = ptr;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int vec_insert_(char **data, int *length, int *capacity, int memsz,
|
||||||
|
int idx
|
||||||
|
) {
|
||||||
|
int err = vec_expand_(data, length, capacity, memsz);
|
||||||
|
if (err) return err;
|
||||||
|
memmove(*data + (idx + 1) * memsz,
|
||||||
|
*data + idx * memsz,
|
||||||
|
(*length - idx) * memsz);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void vec_splice_(char **data, int *length, int *capacity, int memsz,
|
||||||
|
int start, int count
|
||||||
|
) {
|
||||||
|
(void) capacity;
|
||||||
|
memmove(*data + start * memsz,
|
||||||
|
*data + (start + count) * memsz,
|
||||||
|
(*length - start - count) * memsz);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void vec_swapsplice_(char **data, int *length, int *capacity, int memsz,
|
||||||
|
int start, int count
|
||||||
|
) {
|
||||||
|
(void) capacity;
|
||||||
|
memmove(*data + start * memsz,
|
||||||
|
*data + (*length - count) * memsz,
|
||||||
|
count * memsz);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void vec_swap_(char **data, int *length, int *capacity, int memsz,
|
||||||
|
int idx1, int idx2
|
||||||
|
) {
|
||||||
|
unsigned char *a, *b, tmp;
|
||||||
|
int count;
|
||||||
|
(void) length;
|
||||||
|
(void) capacity;
|
||||||
|
if (idx1 == idx2) return;
|
||||||
|
a = (unsigned char*) *data + idx1 * memsz;
|
||||||
|
b = (unsigned char*) *data + idx2 * memsz;
|
||||||
|
count = memsz;
|
||||||
|
while (count--) {
|
||||||
|
tmp = *a;
|
||||||
|
*a = *b;
|
||||||
|
*b = tmp;
|
||||||
|
a++, b++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
|
@ -0,0 +1,189 @@
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2014 rxi
|
||||||
|
*
|
||||||
|
* This library is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the MIT license. See LICENSE for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef VEC_H
|
||||||
|
#define VEC_H
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#define VEC_VERSION "0.2.1"
|
||||||
|
|
||||||
|
|
||||||
|
#define vec_unpack_(v)\
|
||||||
|
(char**)&(v)->data, &(v)->length, &(v)->capacity, sizeof(*(v)->data)
|
||||||
|
|
||||||
|
|
||||||
|
#define vec_t(T)\
|
||||||
|
struct { T *data; int length, capacity; }
|
||||||
|
|
||||||
|
|
||||||
|
#define vec_init(v)\
|
||||||
|
memset((v), 0, sizeof(*(v)))
|
||||||
|
|
||||||
|
|
||||||
|
#define vec_deinit(v)\
|
||||||
|
( free((v)->data),\
|
||||||
|
vec_init(v) )
|
||||||
|
|
||||||
|
|
||||||
|
#define vec_push(v, val)\
|
||||||
|
( vec_expand_(vec_unpack_(v)) ? -1 :\
|
||||||
|
((v)->data[(v)->length++] = (val), 0), 0 )
|
||||||
|
|
||||||
|
|
||||||
|
#define vec_pop(v)\
|
||||||
|
(v)->data[--(v)->length]
|
||||||
|
|
||||||
|
|
||||||
|
#define vec_splice(v, start, count)\
|
||||||
|
( vec_splice_(vec_unpack_(v), start, count),\
|
||||||
|
(v)->length -= (count) )
|
||||||
|
|
||||||
|
|
||||||
|
#define vec_swapsplice(v, start, count)\
|
||||||
|
( vec_swapsplice_(vec_unpack_(v), start, count),\
|
||||||
|
(v)->length -= (count) )
|
||||||
|
|
||||||
|
|
||||||
|
#define vec_insert(v, idx, val)\
|
||||||
|
( vec_insert_(vec_unpack_(v), idx) ? -1 :\
|
||||||
|
((v)->data[idx] = (val), 0), (v)->length++, 0 )
|
||||||
|
|
||||||
|
|
||||||
|
#define vec_sort(v, fn)\
|
||||||
|
qsort((v)->data, (v)->length, sizeof(*(v)->data), fn)
|
||||||
|
|
||||||
|
|
||||||
|
#define vec_swap(v, idx1, idx2)\
|
||||||
|
vec_swap_(vec_unpack_(v), idx1, idx2)
|
||||||
|
|
||||||
|
|
||||||
|
#define vec_truncate(v, len)\
|
||||||
|
((v)->length = (len) < (v)->length ? (len) : (v)->length)
|
||||||
|
|
||||||
|
|
||||||
|
#define vec_clear(v)\
|
||||||
|
((v)->length = 0)
|
||||||
|
|
||||||
|
|
||||||
|
#define vec_first(v)\
|
||||||
|
(v)->data[0]
|
||||||
|
|
||||||
|
|
||||||
|
#define vec_last(v)\
|
||||||
|
(v)->data[(v)->length - 1]
|
||||||
|
|
||||||
|
|
||||||
|
#define vec_reserve(v, n)\
|
||||||
|
vec_reserve_(vec_unpack_(v), n)
|
||||||
|
|
||||||
|
|
||||||
|
#define vec_compact(v)\
|
||||||
|
vec_compact_(vec_unpack_(v))
|
||||||
|
|
||||||
|
|
||||||
|
#define vec_pusharr(v, arr, count)\
|
||||||
|
do {\
|
||||||
|
int i__, n__ = (count);\
|
||||||
|
if (vec_reserve_po2_(vec_unpack_(v), (v)->length + n__) != 0) break;\
|
||||||
|
for (i__ = 0; i__ < n__; i__++) {\
|
||||||
|
(v)->data[(v)->length++] = (arr)[i__];\
|
||||||
|
}\
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
|
||||||
|
#define vec_extend(v, v2)\
|
||||||
|
vec_pusharr((v), (v2)->data, (v2)->length)
|
||||||
|
|
||||||
|
|
||||||
|
#define vec_find(v, val, idx)\
|
||||||
|
do {\
|
||||||
|
for ((idx) = 0; (idx) < (v)->length; (idx)++) {\
|
||||||
|
if ((v)->data[(idx)] == (val)) break;\
|
||||||
|
}\
|
||||||
|
if ((idx) == (v)->length) (idx) = -1;\
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
|
||||||
|
#define vec_remove(v, val)\
|
||||||
|
do {\
|
||||||
|
int idx__;\
|
||||||
|
vec_find(v, val, idx__);\
|
||||||
|
if (idx__ != -1) vec_splice(v, idx__, 1);\
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
|
||||||
|
#define vec_reverse(v)\
|
||||||
|
do {\
|
||||||
|
int i__ = (v)->length / 2;\
|
||||||
|
while (i__--) {\
|
||||||
|
vec_swap((v), i__, (v)->length - (i__ + 1));\
|
||||||
|
}\
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
|
||||||
|
#define vec_foreach(v, var, iter)\
|
||||||
|
if ( (v)->length > 0 )\
|
||||||
|
for ( (iter) = 0;\
|
||||||
|
(iter) < (v)->length && (((var) = (v)->data[(iter)]), 1);\
|
||||||
|
++(iter))
|
||||||
|
|
||||||
|
|
||||||
|
#define vec_foreach_rev(v, var, iter)\
|
||||||
|
if ( (v)->length > 0 )\
|
||||||
|
for ( (iter) = (v)->length - 1;\
|
||||||
|
(iter) >= 0 && (((var) = (v)->data[(iter)]), 1);\
|
||||||
|
--(iter))
|
||||||
|
|
||||||
|
|
||||||
|
#define vec_foreach_ptr(v, var, iter)\
|
||||||
|
if ( (v)->length > 0 )\
|
||||||
|
for ( (iter) = 0;\
|
||||||
|
(iter) < (v)->length && (((var) = &(v)->data[(iter)]), 1);\
|
||||||
|
++(iter))
|
||||||
|
|
||||||
|
|
||||||
|
#define vec_foreach_ptr_rev(v, var, iter)\
|
||||||
|
if ( (v)->length > 0 )\
|
||||||
|
for ( (iter) = (v)->length - 1;\
|
||||||
|
(iter) >= 0 && (((var) = &(v)->data[(iter)]), 1);\
|
||||||
|
--(iter))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
int vec_expand_(char **data, int *length, int *capacity, int memsz);
|
||||||
|
int vec_reserve_(char **data, int *length, int *capacity, int memsz, int n);
|
||||||
|
int vec_reserve_po2_(char **data, int *length, int *capacity, int memsz,
|
||||||
|
int n);
|
||||||
|
int vec_compact_(char **data, int *length, int *capacity, int memsz);
|
||||||
|
int vec_insert_(char **data, int *length, int *capacity, int memsz,
|
||||||
|
int idx);
|
||||||
|
void vec_splice_(char **data, int *length, int *capacity, int memsz,
|
||||||
|
int start, int count);
|
||||||
|
void vec_swapsplice_(char **data, int *length, int *capacity, int memsz,
|
||||||
|
int start, int count);
|
||||||
|
void vec_swap_(char **data, int *length, int *capacity, int memsz,
|
||||||
|
int idx1, int idx2);
|
||||||
|
|
||||||
|
|
||||||
|
typedef vec_t(void*) vec_void_t;
|
||||||
|
typedef vec_t(char*) vec_str_t;
|
||||||
|
typedef vec_t(int) vec_int_t;
|
||||||
|
typedef vec_t(char) vec_char_t;
|
||||||
|
typedef vec_t(float) vec_float_t;
|
||||||
|
typedef vec_t(double) vec_double_t;
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
|
@ -45,3 +45,45 @@ enum YDB_v1_page_offsets {
|
||||||
YDB_v1_page_row_count_offset = YDB_v1_page_prev_offset + YDB_v1_page_prev_size,
|
YDB_v1_page_row_count_offset = YDB_v1_page_prev_offset + YDB_v1_page_prev_size,
|
||||||
YDB_v1_page_data_offset = YDB_v1_page_row_count_offset + YDB_v1_page_row_count_size,
|
YDB_v1_page_data_offset = YDB_v1_page_row_count_offset + YDB_v1_page_row_count_size,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#define YDB_JRNL_SIGN "JRNL"
|
||||||
|
#define YDB_JRNL_SIGN_SIZE (sizeof(YDB_JRNL_SIGN)-1)
|
||||||
|
|
||||||
|
enum YDB_jrnl_sizes {
|
||||||
|
YDB_jrnl_first_transaction_size = 8,
|
||||||
|
YDB_jrnl_last_transaction_size = 8,
|
||||||
|
YDB_jrnl_transaction_prev_size = 8,
|
||||||
|
YDB_jrnl_transaction_next_size = 8,
|
||||||
|
YDB_jrnl_transaction_timestamp_size = 8,
|
||||||
|
YDB_jrnl_transaction_flags_size = 1,
|
||||||
|
YDB_jrnl_transaction_op_type_size = 1,
|
||||||
|
YDB_jrnl_transaction_op_datasz_size = 4,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum YDB_jrnl_offsets {
|
||||||
|
YDB_jrnl_first_transaction_offset = YDB_JRNL_SIGN_SIZE,
|
||||||
|
YDB_jrnl_last_transaction_offset = YDB_jrnl_first_transaction_offset + YDB_jrnl_first_transaction_size,
|
||||||
|
YDB_jrnl_data_offset = YDB_jrnl_last_transaction_offset + YDB_jrnl_last_transaction_size,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum YDB_jrnl_transaction_offsets {
|
||||||
|
YDB_jrnl_transaction_prev_offset = 0,
|
||||||
|
YDB_jrnl_transaction_next_offset = YDB_jrnl_transaction_prev_offset + YDB_jrnl_transaction_prev_size,
|
||||||
|
YDB_jrnl_transaction_timestamp_offset = YDB_jrnl_transaction_next_offset + YDB_jrnl_transaction_next_size,
|
||||||
|
YDB_jrnl_transaction_flags_offset = YDB_jrnl_transaction_timestamp_offset + YDB_jrnl_transaction_timestamp_size,
|
||||||
|
YDB_jrnl_transaction_data_offset = YDB_jrnl_transaction_flags_offset + YDB_jrnl_transaction_flags_size,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum YDB_jrnl_transaction_op_offsets {
|
||||||
|
YDB_jrnl_transaction_op_type_offset = 0,
|
||||||
|
YDB_jrnl_transaction_op_datasz_offset = YDB_jrnl_transaction_op_type_offset + YDB_jrnl_transaction_op_type_size,
|
||||||
|
YDB_jrnl_transaction_op_data_offset = YDB_jrnl_transaction_op_datasz_offset + YDB_jrnl_transaction_op_datasz_size,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum YDB_jrnl_transaction_ops {
|
||||||
|
YDB_jrnl_op_page_alloc = 0x01,
|
||||||
|
YDB_jrnl_op_page_modify = 0x02,
|
||||||
|
YDB_jrnl_op_page_remove = 0x03,
|
||||||
|
YDB_jrnl_op_rollback = 0xFE,
|
||||||
|
YDB_jrnl_op_complete = 0xFF,
|
||||||
|
};
|
|
@ -70,6 +70,54 @@
|
||||||
* @brief The addresses of pages are the same.
|
* @brief The addresses of pages are the same.
|
||||||
*/
|
*/
|
||||||
#define YDB_ERR_SAME_PAGE_ADDRESS (-14)
|
#define YDB_ERR_SAME_PAGE_ADDRESS (-14)
|
||||||
|
/**
|
||||||
|
* @brief The transaction is not initialized.
|
||||||
|
*/
|
||||||
|
#define YDB_ERR_TRANSACTION_NOT_INITIALIZED (-15)
|
||||||
|
/**
|
||||||
|
* @brief Transaction operation push failed.
|
||||||
|
*/
|
||||||
|
#define YDB_ERR_TRANSACTION_OP_PUSH_FAILED (-16)
|
||||||
|
/**
|
||||||
|
* @brief Index of transaction operation is out of range.
|
||||||
|
*/
|
||||||
|
#define YDB_ERR_TRANSACTION_OP_OUT_OF_RANGE (-17)
|
||||||
|
/**
|
||||||
|
* @brief The journal is not initialized.
|
||||||
|
*/
|
||||||
|
#define YDB_ERR_JOURNAL_NOT_INITIALIZED (-18)
|
||||||
|
/**
|
||||||
|
* @brief The journal is in use.
|
||||||
|
*/
|
||||||
|
#define YDB_ERR_JOURNAL_IN_USE (-19)
|
||||||
|
/**
|
||||||
|
* @brief The journal is not in use.
|
||||||
|
*/
|
||||||
|
#define YDB_ERR_JOURNAL_NOT_IN_USE (-20)
|
||||||
|
/**
|
||||||
|
* @brief The journal file does not exist.
|
||||||
|
*/
|
||||||
|
#define YDB_ERR_JOURNAL_NOT_EXIST (-21)
|
||||||
|
/**
|
||||||
|
* @brief The journal file does already exist.
|
||||||
|
*/
|
||||||
|
#define YDB_ERR_JOURNAL_EXIST (-22)
|
||||||
|
/**
|
||||||
|
* @brief The journal file is corrupted.
|
||||||
|
*/
|
||||||
|
#define YDB_ERR_JOURNAL_FILE_CORRUPTED (-23)
|
||||||
|
/**
|
||||||
|
* @brief The journal is not consistent.
|
||||||
|
*/
|
||||||
|
#define YDB_ERR_JOURNAL_NOT_CONSISTENT (-24)
|
||||||
|
/**
|
||||||
|
* @brief No more transactions left in the journal.
|
||||||
|
*/
|
||||||
|
#define YDB_ERR_NO_MORE_TRANSACTIONS (-25)
|
||||||
|
/**
|
||||||
|
* @brief The journal is empty.
|
||||||
|
*/
|
||||||
|
#define YDB_ERR_JOURNAL_EMPTY (-26)
|
||||||
/**
|
/**
|
||||||
* @brief An unknown error has occurred.
|
* @brief An unknown error has occurred.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <YeltsinDB/types.h>
|
||||||
|
#include <YeltsinDB/journal/transaction.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file journal.h
|
||||||
|
* @todo Doxygen.
|
||||||
|
*/
|
||||||
|
|
||||||
|
struct __YDB_Journal;
|
||||||
|
typedef struct __YDB_Journal YDB_Journal;
|
||||||
|
|
||||||
|
YDB_Journal* ydb_journal_init();
|
||||||
|
|
||||||
|
void ydb_journal_destroy(YDB_Journal* jrnl);
|
||||||
|
|
||||||
|
YDB_Error ydb_journal_file_create(YDB_Journal* jrnl, char* path);
|
||||||
|
|
||||||
|
YDB_Error ydb_journal_file_open(YDB_Journal* jrnl, char* path);
|
||||||
|
|
||||||
|
YDB_Error ydb_journal_file_close(YDB_Journal* jrnl);
|
||||||
|
|
||||||
|
YDB_Error ydb_journal_file_check_consistency(YDB_Journal* jrnl);
|
||||||
|
|
||||||
|
YDB_Error ydb_journal_seek_to_begin(YDB_Journal* jrnl);
|
||||||
|
|
||||||
|
YDB_Error ydb_journal_seek_to_end(YDB_Journal* jrnl);
|
||||||
|
|
||||||
|
YDB_Error ydb_journal_prev_transaction(YDB_Journal* jrnl);
|
||||||
|
|
||||||
|
YDB_Error ydb_journal_next_transaction(YDB_Journal* jrnl);
|
||||||
|
|
||||||
|
YDB_Error ydb_journal_append_transaction(YDB_Journal* jrnl, YDB_Transaction* transaction);
|
||||||
|
|
||||||
|
YDB_Transaction* ydb_journal_get_current_transaction(YDB_Journal* jrnl);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
|
@ -0,0 +1,49 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file journal/transaction.h
|
||||||
|
* @todo Doxygen.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <YeltsinDB/journal/transaction_op.h>
|
||||||
|
|
||||||
|
struct __YDB_Transaction;
|
||||||
|
typedef struct __YDB_Transaction YDB_Transaction;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Create a new transaction.
|
||||||
|
* @return A transaction instance.
|
||||||
|
*/
|
||||||
|
YDB_Transaction* ydb_transaction_create();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Destroy a transaction object.
|
||||||
|
* @param t Transaction object.
|
||||||
|
*/
|
||||||
|
void ydb_transaction_destroy(YDB_Transaction* t);
|
||||||
|
|
||||||
|
YDB_Timestamp ydb_transaction_timestamp_get(YDB_Transaction* t);
|
||||||
|
|
||||||
|
YDB_Error ydb_transaction_timestamp_set(YDB_Transaction* t, YDB_Timestamp timestamp);
|
||||||
|
|
||||||
|
YDB_Flags ydb_transaction_flags_get(YDB_Transaction* t);
|
||||||
|
|
||||||
|
YDB_Error ydb_transaction_flags_set(YDB_Transaction* t, YDB_Flags flags);
|
||||||
|
|
||||||
|
uint32_t ydb_transaction_ops_size_get(YDB_Transaction* t);
|
||||||
|
|
||||||
|
YDB_Error ydb_transaction_push_op(YDB_Transaction* t, YDB_TransactionOp* op);
|
||||||
|
|
||||||
|
YDB_Error ydb_transaction_pop_op(YDB_Transaction* t);
|
||||||
|
|
||||||
|
YDB_TransactionOp* ydb_transaction_op_at(YDB_Transaction* t, uint32_t index);
|
||||||
|
|
||||||
|
YDB_Transaction* ydb_transaction_clone(YDB_Transaction* t);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
|
@ -0,0 +1,38 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <YeltsinDB/macro.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file journal/transaction_op.h
|
||||||
|
* @todo Doxygen.
|
||||||
|
*/
|
||||||
|
|
||||||
|
struct __YDB_TransactionOp {
|
||||||
|
YDB_OpCode opcode;
|
||||||
|
YDB_OpDataSize size;
|
||||||
|
void* data;
|
||||||
|
};
|
||||||
|
typedef struct __YDB_TransactionOp YDB_TransactionOp;
|
||||||
|
|
||||||
|
void ydb_transaction_op_destroy(YDB_TransactionOp* op) {
|
||||||
|
free(op->data);
|
||||||
|
free(op);
|
||||||
|
}
|
||||||
|
|
||||||
|
YDB_TransactionOp* ydb_transaction_op_clone(YDB_TransactionOp* op) {
|
||||||
|
THROW_IF_NULL(op, NULL);
|
||||||
|
YDB_TransactionOp* result = malloc(sizeof(YDB_TransactionOp));
|
||||||
|
result->opcode = op->opcode;
|
||||||
|
result->size = op->size;
|
||||||
|
result->data = malloc(op->size);
|
||||||
|
memcpy(result->data, op->data, op->size);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
|
@ -11,6 +11,13 @@
|
||||||
typedef int16_t YDB_Error;
|
typedef int16_t YDB_Error;
|
||||||
/** @brief YeltsinDB data file offset type. */
|
/** @brief YeltsinDB data file offset type. */
|
||||||
typedef uint64_t YDB_Offset;
|
typedef uint64_t YDB_Offset;
|
||||||
/** */
|
/** @brief YeltsinDB page size type. */
|
||||||
typedef uint16_t YDB_PageSize;
|
typedef uint16_t YDB_PageSize;
|
||||||
|
/** @brief YeltsinDB flags container type. */
|
||||||
typedef uint8_t YDB_Flags;
|
typedef uint8_t YDB_Flags;
|
||||||
|
/** @brief YeltsinDB journal operation code type. */
|
||||||
|
typedef uint8_t YDB_OpCode;
|
||||||
|
/** @brief YeltsinDB Unix timestamp type. */
|
||||||
|
typedef int64_t YDB_Timestamp;
|
||||||
|
/** @brief YeltsinDB operation data size type. */
|
||||||
|
typedef uint32_t YDB_OpDataSize;
|
|
@ -135,6 +135,8 @@ YDB_Error ydb_seek_to_begin(YDB_Engine* instance);
|
||||||
*/
|
*/
|
||||||
YDB_Error ydb_seek_to_end(YDB_Engine* instance);
|
YDB_Error ydb_seek_to_end(YDB_Engine* instance);
|
||||||
|
|
||||||
|
YDB_Error ydb_commit_changes(YDB_Engine* instance);
|
||||||
|
|
||||||
// TODO: rebuild page offsets, etc.
|
// TODO: rebuild page offsets, etc.
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -3,11 +3,11 @@
|
||||||
## Specification
|
## Specification
|
||||||
|
|
||||||
1. `JRNL` file signature (4 bytes)
|
1. `JRNL` file signature (4 bytes)
|
||||||
2. The offset to the first transaction (8 bytes)
|
2. The offset to the first transaction (8 bytes) **(could be 0 if no transactions)**
|
||||||
3. The offset to the last transaction (8 bytes)
|
3. The offset to the last transaction (8 bytes) **(could be 0 if no transactions)**
|
||||||
4. Transactions
|
4. Transactions
|
||||||
1. Previous transaction offset (8 bytes)
|
1. Previous transaction offset (8 bytes) **(could be 0 if the first one)**
|
||||||
2. Next transaction offset (8 bytes)
|
2. Next transaction offset (8 bytes) **(could be 0 if the last one)**
|
||||||
3. Creation date in Unix timestamp format (8 bytes)
|
3. Creation date in Unix timestamp format (8 bytes)
|
||||||
4. Transaction flags (1 byte)
|
4. Transaction flags (1 byte)
|
||||||
5. Operations list
|
5. Operations list
|
||||||
|
@ -20,12 +20,16 @@
|
||||||
### Flags
|
### Flags
|
||||||
|
|
||||||
**TODO**. Now there is the only option -- `JRNL_COMPLETE` on bit 0.
|
**TODO**. Now there is the only option -- `JRNL_COMPLETE` on bit 0.
|
||||||
|
It means that the transaction was completely written to the storage.
|
||||||
|
|
||||||
### Consistency
|
### Consistency
|
||||||
|
|
||||||
If the last transaction was not written completely, journal rollback is started.
|
If the last transaction was not written completely, journal rollback is started.
|
||||||
That means that it will be cleaned since it has not even been written to storage.
|
That means that it will be cleaned since it has not even been written to storage.
|
||||||
|
|
||||||
|
If the incomplete transaction is the only one in journal, the journal file is truncated
|
||||||
|
to 20 bytes and first and last transaction offsets are being nulled.
|
||||||
|
|
||||||
If the last transaction is complete in journal but not in storage (flag `JRNL_COMPLETE` is
|
If the last transaction is complete in journal but not in storage (flag `JRNL_COMPLETE` is
|
||||||
not set), redo is being done. That means the transaction starts again since the journal is
|
not set), redo is being done. That means the transaction starts again since the journal is
|
||||||
consistent.
|
consistent.
|
||||||
|
|
|
@ -0,0 +1,409 @@
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <YeltsinDB/types.h>
|
||||||
|
#include <YeltsinDB/error_code.h>
|
||||||
|
#include <YeltsinDB/constants.h>
|
||||||
|
#include <YeltsinDB/macro.h>
|
||||||
|
#include <YeltsinDB/journal/transaction.h>
|
||||||
|
#include <YeltsinDB/journal/transaction_op.h>
|
||||||
|
#include <YeltsinDB/journal.h>
|
||||||
|
|
||||||
|
struct __YDB_Journal {
|
||||||
|
FILE *fd;
|
||||||
|
YDB_Offset first_transaction_offset;
|
||||||
|
YDB_Offset last_transaction_offset;
|
||||||
|
|
||||||
|
YDB_Offset prev_transaction_offset;
|
||||||
|
YDB_Offset curr_transaction_offset;
|
||||||
|
YDB_Offset next_transaction_offset;
|
||||||
|
|
||||||
|
YDB_Transaction* curr_transaction;
|
||||||
|
|
||||||
|
uint8_t is_consistent;
|
||||||
|
uint8_t in_use;
|
||||||
|
};
|
||||||
|
|
||||||
|
inline static YDB_Error __ydb_jrnl_truncate_incomplete(YDB_Journal* j, YDB_Offset last_complete_offset) {
|
||||||
|
THROW_IF_NULL(j, YDB_ERR_JOURNAL_NOT_INITIALIZED);
|
||||||
|
|
||||||
|
// The only transaction is incomplete
|
||||||
|
if (last_complete_offset == 0) {
|
||||||
|
j->prev_transaction_offset = 0;
|
||||||
|
j->next_transaction_offset = 0;
|
||||||
|
j->curr_transaction_offset = 0;
|
||||||
|
j->first_transaction_offset = 0;
|
||||||
|
j->last_transaction_offset = 0;
|
||||||
|
|
||||||
|
fseeko(j->fd, YDB_jrnl_first_transaction_offset, SEEK_SET);
|
||||||
|
fwrite(&j->first_transaction_offset, sizeof(YDB_Offset), 1, j->fd);
|
||||||
|
fwrite(&j->last_transaction_offset, sizeof(YDB_Offset), 1, j->fd);
|
||||||
|
|
||||||
|
ftruncate(fileno(j->fd), YDB_jrnl_data_offset);
|
||||||
|
fsync(fileno(j->fd));
|
||||||
|
return YDB_ERR_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
fseeko(j->fd, last_complete_offset + YDB_jrnl_transaction_prev_offset, SEEK_SET);
|
||||||
|
|
||||||
|
YDB_Offset prev_offset;
|
||||||
|
fread(&prev_offset, sizeof(YDB_Offset), 1, j->fd);
|
||||||
|
REASSIGN_FROM_LE(prev_offset);
|
||||||
|
|
||||||
|
YDB_Offset next_offset;
|
||||||
|
fread(&next_offset, sizeof(YDB_Offset), 1, j->fd);
|
||||||
|
REASSIGN_FROM_LE(next_offset);
|
||||||
|
|
||||||
|
fseeko(j->fd, last_complete_offset + YDB_jrnl_transaction_next_offset, SEEK_SET);
|
||||||
|
YDB_Offset new_next_offset = 0;
|
||||||
|
fwrite(&new_next_offset, sizeof(YDB_Offset), 1, j->fd);
|
||||||
|
|
||||||
|
j->last_transaction_offset = last_complete_offset;
|
||||||
|
fseeko(j->fd, YDB_jrnl_last_transaction_offset, SEEK_SET);
|
||||||
|
YDB_Offset last_complete_offset_le = TO_LE(last_complete_offset);
|
||||||
|
fwrite(&last_complete_offset_le, sizeof(YDB_Offset), 1, j->fd);
|
||||||
|
|
||||||
|
ftruncate(fileno(j->fd), next_offset);
|
||||||
|
fsync(fileno(j->fd));
|
||||||
|
|
||||||
|
return YDB_ERR_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline static YDB_TransactionOp* __ydb_read_op(YDB_Journal* j) {
|
||||||
|
THROW_IF_NULL(j, NULL);
|
||||||
|
YDB_TransactionOp* r = malloc(sizeof(YDB_TransactionOp));
|
||||||
|
fread(&r->opcode, sizeof(YDB_OpCode), 1, j->fd);
|
||||||
|
fread(&r->size, sizeof(YDB_OpDataSize), 1, j->fd);
|
||||||
|
r->data = malloc(r->size);
|
||||||
|
fread(r->data, r->size, 1, j->fd);
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline static YDB_Error __ydb_jrnl_read(YDB_Journal* j, YDB_Offset off) {
|
||||||
|
THROW_IF_NULL(j, YDB_ERR_JOURNAL_NOT_INITIALIZED);
|
||||||
|
|
||||||
|
j->curr_transaction_offset = off;
|
||||||
|
fseeko(j->fd, off, SEEK_SET);
|
||||||
|
|
||||||
|
YDB_Offset prev_offset, next_offset;
|
||||||
|
YDB_Timestamp timestamp;
|
||||||
|
YDB_Flags flags;
|
||||||
|
|
||||||
|
fread(&prev_offset, sizeof(YDB_Offset), 1, j->fd);
|
||||||
|
fread(&next_offset, sizeof(YDB_Offset), 1, j->fd);
|
||||||
|
fread(×tamp, sizeof(YDB_Timestamp), 1, j->fd);
|
||||||
|
fread(&flags, sizeof(YDB_Flags), 1, j->fd);
|
||||||
|
|
||||||
|
j->prev_transaction_offset = prev_offset;
|
||||||
|
j->next_transaction_offset = next_offset;
|
||||||
|
|
||||||
|
YDB_Transaction* t = ydb_transaction_create();
|
||||||
|
ydb_transaction_timestamp_set(t, timestamp);
|
||||||
|
ydb_transaction_flags_set(t, flags);
|
||||||
|
|
||||||
|
YDB_TransactionOp* op;
|
||||||
|
do {
|
||||||
|
op = __ydb_read_op(j);
|
||||||
|
if (op->opcode != YDB_jrnl_op_complete) {
|
||||||
|
ydb_transaction_push_op(t, op);
|
||||||
|
}
|
||||||
|
} while (op->opcode != YDB_jrnl_op_complete);
|
||||||
|
|
||||||
|
return YDB_ERR_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
YDB_Journal *ydb_journal_init() {
|
||||||
|
YDB_Journal* result = calloc(1, sizeof(YDB_Journal));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ydb_journal_destroy(YDB_Journal *jrnl) {
|
||||||
|
ydb_journal_file_close(jrnl);
|
||||||
|
free(jrnl);
|
||||||
|
}
|
||||||
|
|
||||||
|
YDB_Error ydb_journal_file_create(YDB_Journal *jrnl, char *path) {
|
||||||
|
THROW_IF_NULL(jrnl, YDB_ERR_JOURNAL_NOT_INITIALIZED);
|
||||||
|
THROW_IF_NULL(!jrnl->in_use, YDB_ERR_JOURNAL_NOT_IN_USE);
|
||||||
|
|
||||||
|
if (access(path, 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_JOURNAL_EXIST;
|
||||||
|
}
|
||||||
|
|
||||||
|
FILE *f = fopen(path, "wb");
|
||||||
|
|
||||||
|
char tpl[] = YDB_JRNL_SIGN // File signature
|
||||||
|
"\x00\x00\x00\x00\x00\x00\x00\x00" // First transaction offset
|
||||||
|
"\x00\x00\x00\x00\x00\x00\x00\x00"; // Last transaction offset
|
||||||
|
fwrite(tpl, sizeof(tpl)-1, 1, f);
|
||||||
|
|
||||||
|
fclose(f);
|
||||||
|
|
||||||
|
return ydb_journal_file_open(jrnl, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
YDB_Error ydb_journal_file_open(YDB_Journal *jrnl, char *path) {
|
||||||
|
THROW_IF_NULL(jrnl, YDB_ERR_INSTANCE_NOT_INITIALIZED);
|
||||||
|
THROW_IF_NULL(!jrnl->in_use, YDB_ERR_INSTANCE_IN_USE);
|
||||||
|
|
||||||
|
if (access(path, 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_JOURNAL_NOT_EXIST;
|
||||||
|
}
|
||||||
|
|
||||||
|
jrnl->fd = fopen(path, "rb+");
|
||||||
|
THROW_IF_NULL(jrnl->fd, YDB_ERR_UNKNOWN); // TODO file open error
|
||||||
|
|
||||||
|
char signature[YDB_JRNL_SIGN_SIZE];
|
||||||
|
fread(signature, 1, YDB_JRNL_SIGN_SIZE, jrnl->fd);
|
||||||
|
int sig_match = memcmp(signature, YDB_JRNL_SIGN, YDB_JRNL_SIGN_SIZE) == 0;
|
||||||
|
if (!sig_match) {
|
||||||
|
return YDB_ERR_JOURNAL_FILE_CORRUPTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO check for read error
|
||||||
|
fread(&jrnl->first_transaction_offset, sizeof(YDB_Offset), 1, jrnl->fd);
|
||||||
|
REASSIGN_FROM_LE(jrnl->first_transaction_offset);
|
||||||
|
fread(&jrnl->last_transaction_offset, sizeof(YDB_Offset), 1, jrnl->fd);
|
||||||
|
REASSIGN_FROM_LE(jrnl->last_transaction_offset);
|
||||||
|
|
||||||
|
jrnl->in_use = -1;
|
||||||
|
jrnl->curr_transaction_offset = jrnl->first_transaction_offset;
|
||||||
|
|
||||||
|
return ydb_journal_file_check_consistency(jrnl);
|
||||||
|
}
|
||||||
|
|
||||||
|
YDB_Error ydb_journal_file_close(YDB_Journal *jrnl) {
|
||||||
|
THROW_IF_NULL(jrnl, YDB_ERR_JOURNAL_NOT_INITIALIZED);
|
||||||
|
THROW_IF_NULL(jrnl->in_use, YDB_ERR_JOURNAL_NOT_IN_USE);
|
||||||
|
|
||||||
|
jrnl->first_transaction_offset = 0;
|
||||||
|
jrnl->last_transaction_offset = 0;
|
||||||
|
jrnl->prev_transaction_offset = 0;
|
||||||
|
jrnl->curr_transaction_offset = 0;
|
||||||
|
jrnl->next_transaction_offset = 0;
|
||||||
|
ydb_transaction_destroy(jrnl->curr_transaction);
|
||||||
|
jrnl->curr_transaction = NULL;
|
||||||
|
fclose(jrnl->fd);
|
||||||
|
jrnl->in_use = 0;
|
||||||
|
|
||||||
|
return YDB_ERR_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
YDB_Error ydb_journal_file_check_consistency(YDB_Journal *jrnl) {
|
||||||
|
THROW_IF_NULL(jrnl, YDB_ERR_JOURNAL_NOT_INITIALIZED);
|
||||||
|
THROW_IF_NULL(jrnl->in_use, YDB_ERR_JOURNAL_NOT_IN_USE);
|
||||||
|
|
||||||
|
// Empty journal is always consistent lol
|
||||||
|
if (jrnl->last_transaction_offset == 0) {
|
||||||
|
jrnl->is_consistent = -1;
|
||||||
|
return YDB_ERR_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
YDB_Offset last_complete_offset = 0;
|
||||||
|
YDB_Offset prev_offset = 0;
|
||||||
|
YDB_Offset next_offset = 0;
|
||||||
|
|
||||||
|
fseeko(jrnl->fd, jrnl->last_transaction_offset, SEEK_SET);
|
||||||
|
if (fread(&prev_offset, sizeof(YDB_Offset), 1, jrnl->fd) != sizeof(YDB_Offset)) {
|
||||||
|
if (feof(jrnl->fd)) {
|
||||||
|
clearerr(jrnl->fd);
|
||||||
|
perror("Mozhno nenado...\n");
|
||||||
|
abort();
|
||||||
|
// TODO try to find last transaction from begin
|
||||||
|
} else {
|
||||||
|
return YDB_ERR_UNKNOWN; // FIXME
|
||||||
|
}
|
||||||
|
}
|
||||||
|
REASSIGN_FROM_LE(prev_offset);
|
||||||
|
|
||||||
|
last_complete_offset = prev_offset;
|
||||||
|
|
||||||
|
if (fread(&next_offset, sizeof(YDB_Offset), 1, jrnl->fd) != sizeof(YDB_Offset)) {
|
||||||
|
if (feof(jrnl->fd)) {
|
||||||
|
clearerr(jrnl->fd);
|
||||||
|
__ydb_jrnl_truncate_incomplete(jrnl, last_complete_offset);
|
||||||
|
jrnl->is_consistent = -1;
|
||||||
|
return YDB_ERR_SUCCESS;
|
||||||
|
} else {
|
||||||
|
return YDB_ERR_UNKNOWN; // FIXME
|
||||||
|
}
|
||||||
|
}
|
||||||
|
REASSIGN_FROM_LE(next_offset);
|
||||||
|
|
||||||
|
if (next_offset != 0) {
|
||||||
|
perror("Kavo? Ne ponyal shyas...\n");
|
||||||
|
abort(); // Аборт -- это грех, кста
|
||||||
|
}
|
||||||
|
|
||||||
|
fseeko(jrnl->fd, jrnl->last_transaction_offset + YDB_jrnl_transaction_flags_offset, SEEK_SET);
|
||||||
|
if (feof(jrnl->fd)) {
|
||||||
|
__ydb_jrnl_truncate_incomplete(jrnl, last_complete_offset);
|
||||||
|
jrnl->is_consistent = -1;
|
||||||
|
return YDB_ERR_SUCCESS;
|
||||||
|
} else if (ferror(jrnl->fd)) {
|
||||||
|
return YDB_ERR_UNKNOWN; //FIXME
|
||||||
|
}
|
||||||
|
|
||||||
|
YDB_Flags flags = 0;
|
||||||
|
fread(&flags, sizeof(YDB_Flags), 1, jrnl->fd);
|
||||||
|
|
||||||
|
if (flags & 1) { //FIXME magic
|
||||||
|
// A transaction has been written to the storage.
|
||||||
|
return YDB_ERR_SUCCESS;
|
||||||
|
} else {
|
||||||
|
YDB_OpCode op = 0;
|
||||||
|
|
||||||
|
while (op != YDB_jrnl_op_complete) {
|
||||||
|
YDB_OpDataSize size;
|
||||||
|
fread(&op, sizeof(YDB_TransactionOp), 1, jrnl->fd);
|
||||||
|
fread(&size, sizeof(YDB_OpDataSize), 1, jrnl->fd);
|
||||||
|
REASSIGN_FROM_LE(size);
|
||||||
|
|
||||||
|
if (feof(jrnl->fd) || ferror(jrnl->fd)) break;
|
||||||
|
fseeko(jrnl->fd, size, SEEK_CUR);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (op == YDB_jrnl_op_complete) {
|
||||||
|
//fseeko(jrnl->fd, jrnl->last_transaction_offset + YDB_jrnl_transaction_flags_offset, SEEK_SET);
|
||||||
|
//fwrite(flags | 1, sizeof(YDB_Flags), 1, jrnl->fd); //FIXME magic // Also flag should be written when the data in storage is consistent
|
||||||
|
//fsync(fileno(jrnl->fd));
|
||||||
|
jrnl->is_consistent = -1;
|
||||||
|
return YDB_ERR_SUCCESS;
|
||||||
|
}
|
||||||
|
__ydb_jrnl_truncate_incomplete(jrnl, last_complete_offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
jrnl->is_consistent = -1;
|
||||||
|
YDB_Error e = ydb_journal_seek_to_begin(jrnl);
|
||||||
|
if (e == YDB_ERR_SUCCESS || e == YDB_ERR_JOURNAL_EMPTY) {
|
||||||
|
return YDB_ERR_SUCCESS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
YDB_Error ydb_journal_seek_to_begin(YDB_Journal *jrnl) {
|
||||||
|
THROW_IF_NULL(jrnl, YDB_ERR_JOURNAL_NOT_INITIALIZED);
|
||||||
|
THROW_IF_NULL(jrnl->in_use, YDB_ERR_JOURNAL_NOT_IN_USE);
|
||||||
|
THROW_IF_NULL(jrnl->is_consistent, YDB_ERR_JOURNAL_NOT_CONSISTENT);
|
||||||
|
THROW_IF_NULL(jrnl->first_transaction_offset, YDB_ERR_JOURNAL_EMPTY);
|
||||||
|
|
||||||
|
return __ydb_jrnl_read(jrnl, jrnl->first_transaction_offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
YDB_Error ydb_journal_seek_to_end(YDB_Journal *jrnl) {
|
||||||
|
THROW_IF_NULL(jrnl, YDB_ERR_JOURNAL_NOT_INITIALIZED);
|
||||||
|
THROW_IF_NULL(jrnl->in_use, YDB_ERR_JOURNAL_NOT_IN_USE);
|
||||||
|
THROW_IF_NULL(jrnl->is_consistent, YDB_ERR_JOURNAL_NOT_CONSISTENT);
|
||||||
|
THROW_IF_NULL(jrnl->last_transaction_offset, YDB_ERR_JOURNAL_EMPTY);
|
||||||
|
|
||||||
|
return __ydb_jrnl_read(jrnl, jrnl->last_transaction_offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
YDB_Error ydb_journal_prev_transaction(YDB_Journal *jrnl) {
|
||||||
|
THROW_IF_NULL(jrnl, YDB_ERR_JOURNAL_NOT_INITIALIZED);
|
||||||
|
THROW_IF_NULL(jrnl->in_use, YDB_ERR_JOURNAL_NOT_IN_USE);
|
||||||
|
THROW_IF_NULL(jrnl->is_consistent, YDB_ERR_JOURNAL_NOT_CONSISTENT);
|
||||||
|
|
||||||
|
if (jrnl->prev_transaction_offset != 0) {
|
||||||
|
__ydb_jrnl_read(jrnl, jrnl->prev_transaction_offset);
|
||||||
|
return YDB_ERR_SUCCESS;
|
||||||
|
}
|
||||||
|
return YDB_ERR_NO_MORE_TRANSACTIONS;
|
||||||
|
}
|
||||||
|
|
||||||
|
YDB_Error ydb_journal_next_transaction(YDB_Journal *jrnl) {
|
||||||
|
THROW_IF_NULL(jrnl, YDB_ERR_JOURNAL_NOT_INITIALIZED);
|
||||||
|
THROW_IF_NULL(jrnl->in_use, YDB_ERR_JOURNAL_NOT_IN_USE);
|
||||||
|
THROW_IF_NULL(jrnl->is_consistent, YDB_ERR_JOURNAL_NOT_CONSISTENT);
|
||||||
|
|
||||||
|
if (jrnl->next_transaction_offset != 0) {
|
||||||
|
__ydb_jrnl_read(jrnl, jrnl->prev_transaction_offset);
|
||||||
|
return YDB_ERR_SUCCESS;
|
||||||
|
}
|
||||||
|
return YDB_ERR_NO_MORE_TRANSACTIONS;
|
||||||
|
}
|
||||||
|
|
||||||
|
YDB_Error ydb_journal_append_transaction(YDB_Journal *jrnl, YDB_Transaction *transaction) {
|
||||||
|
THROW_IF_NULL(jrnl, YDB_ERR_JOURNAL_NOT_INITIALIZED);
|
||||||
|
THROW_IF_NULL(jrnl->in_use, YDB_ERR_JOURNAL_NOT_IN_USE);
|
||||||
|
THROW_IF_NULL(jrnl->is_consistent, YDB_ERR_JOURNAL_NOT_CONSISTENT);
|
||||||
|
|
||||||
|
YDB_Transaction* t = ydb_transaction_clone(transaction);
|
||||||
|
|
||||||
|
fseeko(jrnl->fd, 0, SEEK_END);
|
||||||
|
YDB_Offset file_end = ftello(jrnl->fd);
|
||||||
|
YDB_Offset file_end_le = TO_LE(file_end);
|
||||||
|
|
||||||
|
YDB_Offset prev = jrnl->last_transaction_offset;
|
||||||
|
YDB_Offset prev_le = TO_LE(prev);
|
||||||
|
YDB_Offset next_le = 0;
|
||||||
|
YDB_Timestamp ts_le = TO_LE(ydb_transaction_timestamp_get(t));
|
||||||
|
YDB_Flags flags = ydb_transaction_flags_get(t);
|
||||||
|
|
||||||
|
fseeko(jrnl->fd, file_end, SEEK_SET);
|
||||||
|
fwrite(&prev_le, sizeof(YDB_Offset), 1, jrnl->fd);
|
||||||
|
fwrite(&next_le, sizeof(YDB_Offset), 1, jrnl->fd);
|
||||||
|
fwrite(&ts_le, sizeof(YDB_Timestamp), 1, jrnl->fd);
|
||||||
|
fwrite(&flags, sizeof(YDB_Flags), 1, jrnl->fd);
|
||||||
|
|
||||||
|
jrnl->last_transaction_offset = file_end;
|
||||||
|
fseeko(jrnl->fd, YDB_jrnl_last_transaction_offset, SEEK_SET);
|
||||||
|
fwrite(&file_end_le, sizeof(YDB_Offset), 1, jrnl->fd);
|
||||||
|
|
||||||
|
fsync(fileno(jrnl->fd)); // Checkpoint
|
||||||
|
|
||||||
|
if (prev != 0) {
|
||||||
|
fseeko(jrnl->fd, prev + YDB_jrnl_transaction_next_offset, SEEK_SET);
|
||||||
|
} else {
|
||||||
|
fseeko(jrnl->fd, YDB_jrnl_first_transaction_offset, SEEK_SET);
|
||||||
|
}
|
||||||
|
fwrite(&file_end_le, sizeof(YDB_Offset), 1, jrnl->fd);
|
||||||
|
|
||||||
|
fsync(fileno(jrnl->fd)); // Checkpoint
|
||||||
|
|
||||||
|
fseeko(jrnl->fd, file_end, SEEK_SET);
|
||||||
|
uint32_t op_cnt = ydb_transaction_ops_size_get(t);
|
||||||
|
for (uint32_t i = 0; i < op_cnt; ++i) {
|
||||||
|
YDB_TransactionOp* op = ydb_transaction_op_at(t, i);
|
||||||
|
fwrite(&op->opcode, sizeof(YDB_OpCode), 1, jrnl->fd);
|
||||||
|
fwrite(&op->size, sizeof(YDB_OpDataSize), 1, jrnl->fd);
|
||||||
|
fwrite(&op->data, op->size, 1, jrnl->fd);
|
||||||
|
fsync(fileno(jrnl->fd));
|
||||||
|
}
|
||||||
|
// Write completion op
|
||||||
|
fputc(YDB_jrnl_op_complete, jrnl->fd);
|
||||||
|
YDB_OpDataSize sz = 0;
|
||||||
|
#ifndef NO_EASTER_EGGS
|
||||||
|
char easter[] = "Andrew is a pi-door-ass";
|
||||||
|
sz = sizeof(easter) - 1;
|
||||||
|
fwrite(&sz, sizeof(YDB_OpDataSize), 1, jrnl->fd);
|
||||||
|
fwrite(easter, sz, 1, jrnl->fd);
|
||||||
|
#else
|
||||||
|
fwrite(&sz, sizeof(YDB_OpDataSize), 1, jrnl->fd);
|
||||||
|
#endif
|
||||||
|
fsync(fileno(jrnl->fd));
|
||||||
|
|
||||||
|
return __ydb_jrnl_read(jrnl, file_end);
|
||||||
|
}
|
||||||
|
|
||||||
|
YDB_Transaction *ydb_journal_get_current_transaction(YDB_Journal *jrnl) {
|
||||||
|
THROW_IF_NULL(jrnl, NULL);
|
||||||
|
THROW_IF_NULL(jrnl->in_use, NULL);
|
||||||
|
THROW_IF_NULL(jrnl->is_consistent, NULL);
|
||||||
|
THROW_IF_NULL(jrnl->first_transaction_offset, NULL);
|
||||||
|
|
||||||
|
return jrnl->curr_transaction;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
|
@ -0,0 +1,101 @@
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <YeltsinDB/types.h>
|
||||||
|
#include <YDB_ext/vec.h>
|
||||||
|
#include <YeltsinDB/journal/transaction_op.h>
|
||||||
|
#include <YeltsinDB/macro.h>
|
||||||
|
#include <YeltsinDB/error_code.h>
|
||||||
|
#include <YeltsinDB/journal/transaction.h>
|
||||||
|
|
||||||
|
typedef vec_t(YDB_TransactionOp*) vec_op_t;
|
||||||
|
|
||||||
|
struct __YDB_Transaction {
|
||||||
|
YDB_Timestamp timestamp;
|
||||||
|
YDB_Flags flags;
|
||||||
|
vec_op_t ops;
|
||||||
|
};
|
||||||
|
|
||||||
|
YDB_Transaction *ydb_transaction_create() {
|
||||||
|
YDB_Transaction* result = calloc(1, sizeof(YDB_Transaction));
|
||||||
|
vec_init(&result->ops);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ydb_transaction_destroy(YDB_Transaction *t) {
|
||||||
|
THROW_IF_NULL(t, (void)0);
|
||||||
|
int i; YDB_TransactionOp* op;
|
||||||
|
vec_foreach(&t->ops, op, i) {
|
||||||
|
ydb_transaction_op_destroy(t->ops.data[i]);
|
||||||
|
}
|
||||||
|
vec_deinit(&t->ops);
|
||||||
|
free(t);
|
||||||
|
}
|
||||||
|
YDB_Timestamp ydb_transaction_timestamp_get(YDB_Transaction *t) {
|
||||||
|
THROW_IF_NULL(t, 1 << sizeof(YDB_Timestamp)); // NOLINT minimal signed int value
|
||||||
|
return t->timestamp;
|
||||||
|
}
|
||||||
|
YDB_Error ydb_transaction_timestamp_set(YDB_Transaction *t, YDB_Timestamp timestamp) {
|
||||||
|
THROW_IF_NULL(t, YDB_ERR_TRANSACTION_NOT_INITIALIZED);
|
||||||
|
t->timestamp = timestamp;
|
||||||
|
return YDB_ERR_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
YDB_Flags ydb_transaction_flags_get(YDB_Transaction *t) {
|
||||||
|
THROW_IF_NULL(t, 0); // NOLINT minimal signed int value
|
||||||
|
return t->flags;
|
||||||
|
}
|
||||||
|
|
||||||
|
YDB_Error ydb_transaction_flags_set(YDB_Transaction *t, YDB_Flags flags) {
|
||||||
|
THROW_IF_NULL(t, YDB_ERR_TRANSACTION_NOT_INITIALIZED);
|
||||||
|
t->flags = flags;
|
||||||
|
return YDB_ERR_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t ydb_transaction_ops_size_get(YDB_Transaction *t) {
|
||||||
|
THROW_IF_NULL(t, 0);
|
||||||
|
return t->ops.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
YDB_Error ydb_transaction_push_op(YDB_Transaction* t, YDB_TransactionOp *op) {
|
||||||
|
THROW_IF_NULL(t, YDB_ERR_TRANSACTION_NOT_INITIALIZED);
|
||||||
|
YDB_TransactionOp* op_copy = ydb_transaction_op_clone(op);
|
||||||
|
if(vec_push(&t->ops, op_copy)) {
|
||||||
|
return YDB_ERR_TRANSACTION_OP_PUSH_FAILED;
|
||||||
|
}
|
||||||
|
return YDB_ERR_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
YDB_Error ydb_transaction_pop_op(YDB_Transaction *t) {
|
||||||
|
THROW_IF_NULL(t, YDB_ERR_TRANSACTION_NOT_INITIALIZED);
|
||||||
|
uint32_t vec_back = t->ops.length - 1;
|
||||||
|
ydb_transaction_op_destroy(t->ops.data[vec_back]);
|
||||||
|
vec_pop(&t->ops);
|
||||||
|
return YDB_ERR_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
YDB_TransactionOp *ydb_transaction_op_at(YDB_Transaction *t, uint32_t index) {
|
||||||
|
THROW_IF_NULL(t, NULL);
|
||||||
|
if (index > ydb_transaction_ops_size_get(t)) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
return t->ops.data[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
YDB_Transaction *ydb_transaction_clone(YDB_Transaction *t) {
|
||||||
|
THROW_IF_NULL(t, NULL);
|
||||||
|
YDB_Transaction* r = ydb_transaction_create();
|
||||||
|
r->timestamp = t->timestamp;
|
||||||
|
r->flags = t->flags;
|
||||||
|
int i; YDB_TransactionOp* op;
|
||||||
|
vec_foreach(&t->ops, op, i) {
|
||||||
|
vec_push(&r->ops, ydb_transaction_op_clone(op));
|
||||||
|
}
|
||||||
|
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
38
src/ydb.c
38
src/ydb.c
|
@ -11,6 +11,11 @@ extern "C" {
|
||||||
#include <YeltsinDB/constants.h>
|
#include <YeltsinDB/constants.h>
|
||||||
#include <YeltsinDB/macro.h>
|
#include <YeltsinDB/macro.h>
|
||||||
#include <YeltsinDB/table_page.h>
|
#include <YeltsinDB/table_page.h>
|
||||||
|
#ifndef NO_JOURNAL
|
||||||
|
#include <YeltsinDB/journal.h>
|
||||||
|
#include <YeltsinDB/journal/transaction.h>
|
||||||
|
#include <YeltsinDB/journal/transaction_op.h>
|
||||||
|
#endif
|
||||||
#include <YeltsinDB/ydb.h>
|
#include <YeltsinDB/ydb.h>
|
||||||
|
|
||||||
struct __YDB_Engine {
|
struct __YDB_Engine {
|
||||||
|
@ -29,16 +34,27 @@ struct __YDB_Engine {
|
||||||
uint8_t in_use; /**< "In use" flag. */
|
uint8_t in_use; /**< "In use" flag. */
|
||||||
char *filename; /**< Current table data file name. */
|
char *filename; /**< Current table data file name. */
|
||||||
FILE *fd; /**< Current table data file descriptor. */
|
FILE *fd; /**< Current table data file descriptor. */
|
||||||
|
|
||||||
|
#ifndef NO_JOURNAL
|
||||||
|
YDB_Journal* jrnl;
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
YDB_Engine *ydb_init_instance() {
|
YDB_Engine *ydb_init_instance() {
|
||||||
YDB_Engine *new_instance = calloc(1, sizeof(YDB_Engine));
|
YDB_Engine *new_instance = calloc(1, sizeof(YDB_Engine));
|
||||||
|
#ifndef NO_JOURNAL
|
||||||
|
new_instance->jrnl = ydb_journal_init();
|
||||||
|
#endif
|
||||||
return new_instance;
|
return new_instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ydb_terminate_instance(YDB_Engine *instance) {
|
void ydb_terminate_instance(YDB_Engine *instance) {
|
||||||
ydb_unload_table(instance);
|
ydb_unload_table(instance);
|
||||||
|
|
||||||
|
#ifndef NO_JOURNAL
|
||||||
|
ydb_journal_destroy(instance->jrnl);
|
||||||
|
#endif
|
||||||
|
|
||||||
// And after all that, the instance could be freed
|
// And after all that, the instance could be freed
|
||||||
free(instance);
|
free(instance);
|
||||||
}
|
}
|
||||||
|
@ -91,6 +107,14 @@ static YDB_Error __ydb_read_page(YDB_Engine *inst) {
|
||||||
// Moves file position to allocated block.
|
// Moves file position to allocated block.
|
||||||
// Also changes last_free_page_offset.
|
// Also changes last_free_page_offset.
|
||||||
static YDB_Offset __ydb_allocate_page_and_seek(YDB_Engine *inst) {
|
static YDB_Offset __ydb_allocate_page_and_seek(YDB_Engine *inst) {
|
||||||
|
// TODO journal
|
||||||
|
#ifndef NO_JOURNAL
|
||||||
|
YDB_Transaction* t = ydb_transaction_create();
|
||||||
|
YDB_TransactionOp* op_alloc = malloc(sizeof(YDB_TransactionOp));
|
||||||
|
op_alloc->opcode = YDB_jrnl_op_page_alloc;
|
||||||
|
op_alloc->size = 24;
|
||||||
|
op_alloc->data = malloc(op_alloc->size);
|
||||||
|
#endif
|
||||||
// TODO change signature of the function. It should return an error code, I think.
|
// TODO change signature of the function. It should return an error code, I think.
|
||||||
YDB_Offset result = 0;
|
YDB_Offset result = 0;
|
||||||
// If no free pages in the table, then...
|
// If no free pages in the table, then...
|
||||||
|
@ -118,7 +142,7 @@ static YDB_Offset __ydb_allocate_page_and_seek(YDB_Engine *inst) {
|
||||||
inst->last_page_offset = result;
|
inst->last_page_offset = result;
|
||||||
fseek(inst->fd, YDB_v1_last_page_offset, SEEK_SET);
|
fseek(inst->fd, YDB_v1_last_page_offset, SEEK_SET);
|
||||||
fwrite(&allocated_page_offset_le, sizeof(YDB_Offset), 1, inst->fd);
|
fwrite(&allocated_page_offset_le, sizeof(YDB_Offset), 1, inst->fd);
|
||||||
fflush(inst->fd);
|
fsync(fileno(inst->fd));
|
||||||
} else {
|
} else {
|
||||||
// Return last free page offset
|
// Return last free page offset
|
||||||
result = inst->last_free_page_offset;
|
result = inst->last_free_page_offset;
|
||||||
|
@ -143,7 +167,7 @@ static YDB_Offset __ydb_allocate_page_and_seek(YDB_Engine *inst) {
|
||||||
YDB_Offset lfp_offset_le = TO_LE(inst->last_free_page_offset);
|
YDB_Offset lfp_offset_le = TO_LE(inst->last_free_page_offset);
|
||||||
fwrite(&lfp_offset_le, sizeof(YDB_Offset), 1, inst->fd);
|
fwrite(&lfp_offset_le, sizeof(YDB_Offset), 1, inst->fd);
|
||||||
}
|
}
|
||||||
fflush(inst->fd);
|
fsync(fileno(inst->fd));
|
||||||
fseek(inst->fd, result, SEEK_SET);
|
fseek(inst->fd, result, SEEK_SET);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -303,7 +327,7 @@ YDB_Error ydb_append_page(YDB_Engine* instance, YDB_TablePage* page) {
|
||||||
fwrite(&rc_le, sizeof(rc), 1, instance->fd);
|
fwrite(&rc_le, sizeof(rc), 1, instance->fd);
|
||||||
fwrite(d, sizeof(d), 1, instance->fd);
|
fwrite(d, sizeof(d), 1, instance->fd);
|
||||||
|
|
||||||
fflush(instance->fd);
|
fsync(fileno(instance->fd));
|
||||||
|
|
||||||
__ydb_read_page(instance);
|
__ydb_read_page(instance);
|
||||||
|
|
||||||
|
@ -343,7 +367,7 @@ YDB_Error ydb_replace_current_page(YDB_Engine *instance, YDB_TablePage *page) {
|
||||||
fwrite(page_data, sizeof(page_data), 1, instance->fd);
|
fwrite(page_data, sizeof(page_data), 1, instance->fd);
|
||||||
|
|
||||||
// Flush buffer
|
// Flush buffer
|
||||||
fflush(instance->fd);
|
fsync(fileno(instance->fd));
|
||||||
|
|
||||||
ydb_page_free(instance->curr_page);
|
ydb_page_free(instance->curr_page);
|
||||||
instance->curr_page = page;
|
instance->curr_page = page;
|
||||||
|
@ -398,7 +422,7 @@ YDB_Error ydb_delete_current_page(YDB_Engine *instance) {
|
||||||
fwrite(&cp_le, sizeof(YDB_Offset), 1, instance->fd);
|
fwrite(&cp_le, sizeof(YDB_Offset), 1, instance->fd);
|
||||||
|
|
||||||
// Flush buffer
|
// Flush buffer
|
||||||
fflush(instance->fd);
|
fsync(fileno(instance->fd));
|
||||||
|
|
||||||
// Seek to the next page if it's not the last, else seek to the previous one
|
// Seek to the next page if it's not the last, else seek to the previous one
|
||||||
if (instance->next_page_offset != 0) {
|
if (instance->next_page_offset != 0) {
|
||||||
|
@ -431,6 +455,10 @@ YDB_Error ydb_seek_to_end(YDB_Engine *instance) {
|
||||||
return __ydb_read_page(instance);
|
return __ydb_read_page(instance);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
YDB_Error ydb_commit_changes(YDB_Engine *instance) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
Loading…
Reference in New Issue