1
0
mirror of https://github.com/fumiama/fumidb.git synced 2026-06-06 01:00:32 +08:00
Files
fumidb/src/table.c
2022-05-04 14:52:00 +08:00

645 lines
18 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include <stdarg.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include "../include/binary.h"
#include "../include/file.h"
#include "../include/page.h"
#include "../include/table.h"
#include "../include/types.h"
#include "../include/types/int8.h"
#include "../include/types/int16.h"
// 计算索引需要的内存
static int _calc_index_size(type_t t) {
switch(t&7) {
case TYPE_INT8:
return INT8_INDEX_SZ+10;
break;
case TYPE_INT16:
return INT16_INDEX_SZ+10 + INT16_BITMAP_SZ+8*2;
break;
case TYPE_INT32:
case TYPE_FLOAT:
case TYPE_INT64:
case TYPE_DOUBLE:
case TYPE_STRING:
return PAGESZ+8;
break;
case TYPE_BINARY: // 不能创建索引
default:
return EOF;
break;
}
}
// 计算本类型占用的空间
static int _calc_type_size(type_t t) {
switch(t&7) {
case TYPE_INT8:
return (int)(!(t&EXTYPE_NONNULL))+1+((t&EXTYPE_UNIQUE)?0:8);
break;
case TYPE_INT16:
return (int)(!(t&EXTYPE_NONNULL))+2+((t&EXTYPE_UNIQUE)?0:8);
break;
case TYPE_INT32:
case TYPE_FLOAT:
return (int)(!(t&EXTYPE_NONNULL))+4;
break;
case TYPE_INT64:
case TYPE_DOUBLE:
return (int)(!(t&EXTYPE_NONNULL))+8;
break;
case TYPE_STRING:
return (int)(!(t&EXTYPE_NONNULL))+8+2;
break;
case TYPE_BINARY:
return (int)(!(t&EXTYPE_NONNULL))+2;
break;
}
return 0;
}
// 为 t 类型创建索引,写入 index_ptr
// 返回:
// 1 失败,详见 errno
// 0 成功
static int _add_index_type(int fd, uint64_t* index_ptr, type_t t) {
int sz = _calc_index_size(t);
if(sz <= 0) {
errno = EINVAL;
return EINVAL;
}
void* buf = malloc(sz);
if(buf == NULL) return 1;
void* index = create_index(fd, t, buf);
if(index == NULL) {
free(buf);
return 2;
}
*index_ptr = le64(buf);
free(buf);
return 0;
}
// 移除 t 类型索引
// 返回:
// 1 失败,详见 errno
// 0 成功
static int _remove_index_type(int fd, type_t t, uint64_t ptr) {
int sz = _calc_index_size(t);
if(sz <= 0) {
errno = EINVAL;
return EINVAL;
}
void* buf = malloc(sz);
if(buf == NULL) return 1;
void* index = load_index(fd, t, ptr, buf);
if(index == NULL) {
free(buf);
return 2;
}
sz = remove_index(fd, t, index);
free(buf);
return sz;
}
// 创建表,可变参数 list 为本表的一行的 types详见 types.h
// list 以 type_t 为单元,遇到 uint64_t ptr 时偏移 +8
// 如果 types 为外键,需要紧跟一个 uint64_t ptr
// 指示外键链接到的表位置
// len(buf) >= 4096+8+2=4106
// 返回:
// NULL 失败,详见 errno
// table 指向表头的指针
void* create_table(int fd, char* buf, const char* name, int row_len, const void* list) {
if(row_len <= 0 || row_len > 128) {
errno = EINVAL;
return NULL;
}
int len = strlen(name);
if(len <= 0 || len >= UINT16_MAX) {
errno = EINVAL;
return NULL;
}
void* table = alloc_page(fd, buf+2);
if(table == NULL) return NULL;
memset(table, 0, PAGESZ);
putle16(table+8, len);
memcpy(table+8+2, name, len);
len += 8+2;
uint64_t ptr = get_first_table(fd);
while(ptr > 0) ptr = get_next_table(fd, ptr); // 结束时fd位于最后一个表头+8
if(ptr) return NULL; // 发生错误
lseek(fd, -8, SEEK_CUR);
if(write(fd, buf+2, 8) != 8) return NULL; // 写入新表位置
putle16(table+len, row_len);
len += 2;
int foreign_cnt = 0, ap = 0;
type_t t = ((type_t*)list)[ap++]; // 是主键,检查是否有 unique + nonnull 类型修饰符
if(!(t&EXTYPE_NONNULL) || !(t&EXTYPE_UNIQUE)) {
errno = EINVAL;
return NULL;
}
((type_t*)table)[len] = t; // 填充 type of row No.0
#ifdef DEBUG
printf("fill[%d]: %d\n", ap, t);
#endif
if(t & EXTYPE_FOREIGNKEY) { // 是外键,还有一个参数
ptr = le64(list+ap);
#ifdef DEBUG
printf("fill[%d]: %016llx\n", ap, ptr);
#endif
ap += 8;
putle64(table+len+(int)row_len*(8+1)+8*(foreign_cnt++), ptr);
}
// 为 pk 创建索引
if(_add_index_type(fd, table+len+(int)row_len, t)) return NULL;
for(int i = 1; i < (int)row_len; i++) {
t = ((type_t*)list)[ap++];
((type_t*)table)[len+i] = t; // 填充 type of row No.i
#ifdef DEBUG
printf("fill[%d]: %d\n", ap, t);
#endif
if(t & EXTYPE_FOREIGNKEY) { // 是外键,还有一个参数
ptr = le64(list+ap);
#ifdef DEBUG
printf("fill[%d]: %016llx\n", ap, ptr);
#endif
ap += 8;
putle64(table+len+(int)row_len*(8+1)+8*(foreign_cnt++), ptr);
}
}
len += (int)row_len*(8+1) + 8*foreign_cnt;
if(len > PAGESZ) {
errno = EFBIG;
return NULL;
}
// 写回文件
if(sync_page(fd, table)) return NULL;
// 回收剩余空间
ptr = le64(buf+2);
if(PAGESZ - len > 10) add_block(fd, PAGESZ - len, ptr+(uint64_t)len);
// 将 page 变为 block
putle64(buf, ptr);
putle16(buf+8, len);
#ifdef DEBUG
printf("create len: %d\n", len);
#endif
return buf+10;
}
// 加载 ptr 位置的表
// len(buf) >= 4096+8+2=4106
// 返回:
// NULL 失败,详见 errno
// table 指向表头的指针
void* load_table(int fd, char* buf, uint64_t ptr) {
uint64_t tmp;
int len = 8;
putle64(buf, ptr); // this blk ptr
if(lseek(fd, ptr+8, SEEK_SET) < 0) return NULL; // skip next table ptr
readle16(fd, tmp); // table name length
len += 2+(int)tmp;
if(lseek(fd, tmp, SEEK_CUR) < 0) return NULL;
readle16(fd, tmp); // table row length
len += 2+((int)tmp)*(8+1);
for(int i = 0; i < (int)tmp; i++) {
type_t t;
read(fd, &t, 1);
if(t&EXTYPE_FOREIGNKEY) { // 外键有额外 ptr 长度
len += 8;
}
}
putle16(buf+8, len); // this blk len
lseek(fd, ptr, SEEK_SET);
if(read(fd, buf+10, len) != len) return NULL;
#ifdef DEBUG
printf("load len: %d\n", len);
#endif
return buf+10;
}
// 获得表名长度包含结尾0
uint16_t get_table_name_length(void* table) {
return le16(table+8)+1;
}
// 获得表名写入buflen(buf) >= len(name)
// 返回buf
char* get_table_name(void* table, char* buf) {
uint16_t len = le16(table+8);
memcpy(buf, table+10, len);
buf[len] = 0;
return buf;
}
uint64_t get_index_ptr(void* table, uint16_t pos) {
int len = 8+2+le16(table+8);
uint16_t rlen = le16(table+len);
if(pos >= rlen) {
errno = EINVAL;
return 0;
}
len += 2;
type_t t = ((type_t*)table)[len+(int)pos];
len += (int)rlen+(int)pos*8;
return *(uint64_t*)(table+len);
}
// 为 pos 位置的列创建索引。不可用于 0 列,即 pk 列,因为 pk 必有索引
// 返回:
// 0 失败,详见 errno
// ptr 指向索引头的指针
uint64_t add_table_index(int fd, void* table, uint16_t pos) {
if(!pos) {
errno = EINVAL;
return 0;
}
int len = 8+2+le16(table+8);
uint16_t rlen = le16(table+len);
if(pos >= rlen) {
errno = EINVAL;
return 0;
}
len += 2;
type_t t = ((type_t*)table)[len+(int)pos];
len += (int)rlen+(int)pos*8;
if(*(uint64_t*)(table+len)) return *(uint64_t*)(table+len); // 已经有索引
if(_add_index_type(fd, table+len, t)) return 0;
if(sync_block(fd, table)) return 0;
return *(uint64_t*)(table+len);
}
#define DEBUG
int get_row_length(int fd, void* table, uint64_t ptr) {
if(lseek(fd, ptr, SEEK_SET) < 0) return EOF;
int len = 8+2+le16(table+8);
int rlen = le16(table+len);
int sz = 0; // 本行长度
len += 2;
for(int i = 0; i < rlen; i++) {
type_t t = ((type_t*)table)[len+i];
#ifdef DEBUG
printf("type: %d, ", (int)t);
#endif
int size = _calc_type_size(t);
sz += size;
#ifdef DEBUG
printf("skip: %d, size: %d\n", size, sz);
#endif
if(lseek(fd, size, SEEK_CUR) < 0) return EOF;
int blen;
switch(t&7) {
case TYPE_STRING: // 是 string/binary多读取一个长度
case TYPE_BINARY:
lseek(fd, -2, SEEK_CUR);
readle16(fd, blen);
#ifdef DEBUG
printf("blen: %d, ", blen);
#endif
if(blen > PAGESZ/2 || blen <= 0) { // 长度超标
errno = EFBIG;
return 0;
}
sz += blen;
if(lseek(fd, blen, SEEK_CUR) < 0) return EOF;
break;
default: break;
}
}
#ifdef DEBUG
printf("total size: %d\n", sz);
#endif
return sz;
}
// 删除 pos 位置的列的索引。不可用于 0 列,即 pk 列,因为 pk 必有索引
// 返回:
// 非 0 失败,详见 errno
// 0 成功
int remove_table_index(int fd, void* table, uint16_t pos) {
if(!pos) {
errno = EINVAL;
return 0;
}
int len = 8+2+le16(table+8);
uint16_t rlen = le16(table+len);
if(pos >= rlen) {
errno = EINVAL;
return 0;
}
len += 2;
type_t t = ((type_t*)table)[len+(int)pos];
len += (int)rlen+(int)pos*8;
uint64_t ptr = *(uint64_t*)(table+len);
if(ptr == 0) return 0; // 没有索引
*(uint64_t*)(table+len) = 0; // 清除
_remove_index_type(fd, t, ptr);
return sync_block(fd, table);
}
#define DEBUG
// 插入一行,如果 pk 有值则替换
// list 以 key_t 为单元
// 如果当前项有 nullable 属性,需要在此项之前
// 加一个 key_t isavailable标记本项是否有值
// 如果 isavailable==0后面不再跟有本项数据
// 如果 isavailable!=0则在后面附加数据
// 如果 val 不为 string/binary直接装填其值
// 否则,值是指向 string/binary 的指针 (const char*)
// 且需要在指针之前提供一个 key_t 参数指示其大小
// 返回:
// 0 失败,详见 errno
// ptr 本行插入的位置
uint64_t insert_row(int fd, void* table, const key_t* list) {
int len = 8+2+le16(table+8);
int rlen = le16(table+len);
int sz = 0; // 本行长度
int ap = 0;
len += 2;
uint64_t ptr = *(uint64_t*)(table+len+rlen);
int indexsz = _calc_index_size(((type_t*)table)[len]);
if(indexsz <= 0) return 0;
void* indexbuf = malloc(indexsz);
if(!indexbuf) return 0;
void* index = load_index(fd, ((type_t*)table)[len], ptr, indexbuf);
if(index == NULL) {
free(indexbuf);
return 0;
}
ptr = find_item_by_key(fd, ((type_t*)table)[len], index, list[0]);
for(int i = 0; i < rlen; i++) {
type_t t = ((type_t*)table)[len+i];
#ifdef DEBUG
printf("type: %d, ", (int)t);
#endif
int isnull = 0;
sz += _calc_type_size(t);
#ifdef DEBUG
printf("sz: %d, ", sz);
#endif
if(!(t&EXTYPE_NONNULL)) { // 可空,读取是否为空
isnull = !((int)(list[ap++]));
#ifdef DEBUG
printf("isnull: %s, ", isnull?"true":"false");
#endif
}
if((t&7) >= TYPE_STRING) { // 是 string/binary多读取一个长度
int blen = (int)(list[ap++]);
#ifdef DEBUG
printf("blen: %d, ", blen);
#endif
if(blen > PAGESZ/2 || blen <= 0) { // 长度超标
errno = EFBIG;
free(indexbuf);
return 0;
}
sz += blen;
}
if(!isnull) {
#ifdef DEBUG
printf("skip key: %lld, ", list[ap]);
#endif
ap++; // 跳过读取真实值
}
#ifdef DEBUG
printf("ap: %d\n", ap);
#endif
}
#ifdef DEBUG
printf("total size: %d\n", sz);
#endif
if(sz <= 0 || sz > PAGESZ) { // 总长超出一页,无法插入
errno = EFBIG;
free(indexbuf);
return 0;
}
void* buf = malloc(sz+10);
if(!buf) {
free(indexbuf);
return 0;
}
void* blk = ptr?get_block(fd, sz, ptr, buf):alloc_block(fd, sz, buf);
if(blk == NULL) {
free(indexbuf);
free(buf);
return 0;
}
ap = 0;
int p = 0;
for(int i = 0; i < rlen; i++) {
type_t t = ((type_t*)table)[len+i];
int blen;
if(!(t&EXTYPE_NONNULL)) { // 可空,读取是否为空
int isnull = !((int)(list[ap++]));
((uint8_t*)blk)[p++] = isnull;
if(isnull) {
p += _calc_type_size(t)-1; // 跳过本项
if((t&7) >= TYPE_STRING) { // 是 string/binary多读取一个长度
blen = (int)(list[ap++]);
if(blen > PAGESZ/2 || blen <= 0) { // 长度超标
errno = EFBIG;
free(indexbuf);
return 0;
}
p += blen;
}
#ifdef DEBUG
printf("skip to: %d\n", p);
#endif
continue;
}
}
switch(t&7) {
case TYPE_INT8:
if(!(t&EXTYPE_UNIQUE)) p += 8; // 跳过下一个哈希相同的数据项的指针
((uint8_t*)blk)[p++] = (uint8_t)(list[ap++]);
break;
case TYPE_INT16:
if(!(t&EXTYPE_UNIQUE)) p += 8; // 跳过下一个哈希相同的数据项的指针
putle16(blk+p, list[ap++]);
p += 2;
break;
case TYPE_INT32:
case TYPE_FLOAT:
putle32(blk+p, list[ap++]);
p += 4;
break;
case TYPE_INT64:
case TYPE_DOUBLE:
putle64(blk+p, list[ap++]);
p += 8;
break;
case TYPE_STRING: // 跳过哈希相同指针
p += 8;
case TYPE_BINARY: // 是 string/binary多读取一个长度
blen = (int)(list[ap++]);
#ifdef DEBUG
printf("blen: %d, ", blen);
#endif
if(blen > PAGESZ/2 || blen <= 0) { // 长度超标
errno = EFBIG;
free(indexbuf);
return 0;
}
putle16(blk+p, blen); // 写入长度
p += 2;
char* field = (char*)(list[ap++]); // 读取指针
#ifdef DEBUG
printf("p: %d, ", p);
#endif
memcpy(blk+p, field, blen); // 复制
#ifdef DEBUG
printf("copy field: ");
for(int i = 0; i < blen; i++) printf("%02x ", (int)((unsigned char)(field[i])));
putchar('\n');
#endif
p += blen;
break;
}
}
#ifdef DEBUG
printf("total size: %d\n", p);
#endif
if(sync_block(fd, blk)) { // 将行写入文件
free(indexbuf);
free(buf);
return 0;
}
ptr = le64(buf);
if(insert_item(fd, ((type_t*)table)[len], index, list[0], ptr)) { // 插入 pk 索引
free(indexbuf);
free(buf);
return 0;
}
// TODO: 同时插入其它已创建索引的列的索引
free(indexbuf);
free(buf);
return ptr;
}
// 根据主键的匹配值查找行
// 如果主键不为 stringk 直接装填其值
// 否则k 是指向 string 的指针 (const char*)
// 返回:
// 0 失败,详见 errno
// ptr 行所在位置
uint64_t find_row_by_pk(int fd, void* table, key_t k) {
int len = 8+2+le16(table+8);
int rlen = le16(table+len);
len += 2;
uint64_t ptr = *(uint64_t*)(table+len+rlen);
#ifdef DEBUG
printf("indexptr: %016llx, ", ptr);
#endif
int indexsz = _calc_index_size(((type_t*)table)[len]);
#ifdef DEBUG
printf("indexsz: %d, ", indexsz);
#endif
if(indexsz <= 0) return 0;
void* indexbuf = malloc(indexsz);
if(!indexbuf) return 0;
void* index = load_index(fd, ((type_t*)table)[len], ptr, indexbuf);
if(index == NULL) {
free(indexbuf);
return 0;
}
ptr = find_item_by_key(fd, ((type_t*)table)[len], index, k);
#ifdef DEBUG
printf("ptr: %016llx\n", ptr);
#endif
free(indexbuf);
return ptr;
}
// 根据任意匹配值遍历查找行
// 可变参数两两成对uint16_t pos + key_t val
// 如果 val 不为 string直接装填其值
// 否则,值是指向 string 的指针 (const char*)
// f 为遍历函数,入参为本行 ptr返回非 0 值中断遍历
// 返回:
// 非 0 失败,详见 errno
// 0 成功
int find_row_by(int fd, void* table, int (*f)(uint64_t), int row_len, const void* list) {
return 1;
}
// 根据主键的匹配值删除行
// 如果主键不为 stringk 直接装填其值
// 否则k 是指向 string 的指针 (const char*)
// 返回:
// 非 0 失败,详见 errno
// 0 成功
int remove_row_by_pk(int fd, void* table, key_t k) {
int len = 8+2+le16(table+8);
int rlen = le16(table+len);
len += 2;
uint64_t indexptr = *(uint64_t*)(table+len+rlen);
#ifdef DEBUG
printf("indexptr: %016llx, ", indexptr);
#endif
type_t t = ((type_t*)table)[len];
int indexsz = _calc_index_size(t);
#ifdef DEBUG
printf("indexsz: %d, ", indexsz);
#endif
uint64_t ptr = find_row_by_pk(fd, table, k);
if(ptr == 0) return 0;
len = get_row_length(fd, table, ptr);
if(len <= 0) return EOF;
void* buf = malloc(indexsz);
if(buf == NULL) return EOF;
if(add_block(fd, len, ptr)) {
free(buf);
return EOF;
}
void* index = load_index(fd, t, indexptr, buf);
if(index == NULL) {
free(buf);
return EOF;
}
if(remove_index(fd, t, index)) {
free(buf);
return EOF;
}
free(buf);
return 0;
}
// 根据任意匹配值删除行
// 可变参数两两成对uint16_t pos + key_t val
// 如果 val 不为 string直接装填其值
// 否则,值是指向 string 的指针 (const char*)
// 返回:
// 非 0 失败,详见 errno
// 0 成功
int remove_row_by(int fd, void* table, int row_len, const void* list) {
return 1;
}