说明:本文供数据库爱好者和初级开发人员学习使用
标签:数据库管理系统、RDBMS、C语言小程序、C语言、C程序
系统:Windows 11 x86
CPU :Intel
IDE :CLion
语言:C语言
标准:C23
提示:如果您发现本文哪里写的有问题或者有更好的写法请留言或私信我进行修改优化
目前该C语言小程序实现了RDBMS上表的几个基础功能,如:
✔ DDL:create、drop、alter、truncate、flashback、purge
✔ DML:insert、delete、update、select
✔ 程序会在首次运行时在同级创建一个目录用于存放数据库信息
✔ 程序运行期间表数据存储在内存中,类似于Redis
✔ 程序运行结束表数据会刷入磁盘避免丢失
✔ 表在删除后清理前支持闪回功能,清理后空间可以复用
✔ 表记录在删除后只是标记为删除,方便后期复用并减少数据shrink的性能消化
https://download.csdn.net/download/zzt_2009/88792573
##########【欢迎使用 ZDB】##########
# 作者:zzt_2009 版本:V 5.0.0 #
# [H/h]查看帮助 [E/e]退出程序 #
首次使用,正在初始化数据字典……
字典落盘成功!
字典文件初始化完成。
首次使用,正在初始化数据文件……
数据落盘成功!
数据文件初始化完成。
首次使用,已为您展示帮助文档。
# L For > list table def #
# C For > Create table #
# D For > Drop table #
# A For > Alter table #
# R For > Rename table #
# T For > Truncate table #
# F For > Flashback table #
# P For > Purge recyclebin #
# i For > insert #
# d For > delete #
# u For > update #
# s For > select #
# c For > clear #
# H/h For > help #
# E/e For > exit #
SQL > H
# L For > list table def #
# C For > Create table #
# D For > Drop table #
# A For > Alter table #
# R For > Rename table #
# T For > Truncate table #
# F For > Flashback table #
# P For > Purge recyclebin #
# i For > insert #
# d For > delete #
# u For > update #
# s For > select #
# c For > clear #
# H/h For > help #
# E/e For > exit #
SQL > L
# 库中所有状态的表信息如下
内存ID 表名 表状态 表编号 行数 列数
TMID[0] T0 STA[2] OID[0] ROWS[4] COLS[2]
# STA列值:1>新/空表、2>有数据、3>已删除、4>可复用
SQL > C
请输入表名: T1
请输入列的[数量]:2
列的[数量]:2
请输入第[1]列的[名称]:ID
请输入第[2]列的[名称]:NAME
表TMID:1,表OID:2字典落盘成功!
数据落盘成功!
SQL > C
请输入表名: T2
请输入列的[数量]:2
列的[数量]:2
请输入第[1]列的[名称]:ID
请输入第[2]列的[名称]:NAME
表TMID:2,表OID:3字典落盘成功!
数据落盘成功!
SQL > D
请输入表名: T1
已删除
字典落盘成功!
数据落盘成功!
SQL > F
请输入表OID: 1
表已闪回
字典落盘成功!
数据落盘成功!
SQL > R
请输入表名: 1
请输入新名: T1
# 更新成功:
# 库中所有状态的表信息如下
内存ID 表名 表状态 表编号 行数 列数
TMID[0] T0 STA[2] OID[0] ROWS[4] COLS[2]
TMID[1] T1 STA[1] OID[1] ROWS[0] COLS[2]
TMID[2] T2 STA[1] OID[2] ROWS[0] COLS[2]
# STA列值:1>新/空表、2>有数据、3>已删除、4>可复用
字典落盘成功!
数据落盘成功!
SQL > A
请输入表名: T1
请输入CID(列号): 1
请输入字段新值: ID2
# 更新后表结构和数据如下:
R[00] RMID[00] : ID2 NAME
# [0]ROWS,[2]COLS
字典落盘成功!
数据落盘成功!
SQL > i
请输入表名: T1
请输入多行(列以tab分隔,新行";\n"结束)
Input > 1 a
Input > 2 b
Input > 3 c
Input > ;
插入结束!
字典落盘成功!
数据落盘成功!
# 表[T1]最新数据如下:
R[00] RMID[00] : ID2 NAME
R[01] RMID[01] : 1 a
R[02] RMID[02] : 2 b
R[03] RMID[03] : 3 c
# [3]ROWS,[2]COLS
表行列数不正确,修正完成.
数据落盘成功!
SQL > d
请输入表名: T1
请输入RMID: 2
# RID[2]已删除
# 删除后表数据如下:
R[00] RMID[00] : ID2 NAME
R[01] RMID[01] : 1 a
R[02] RMID[03] : 3 c
# [2]ROWS,[2]COLS
表行列数不正确,修正完成.
数据落盘成功!
字典落盘成功!
数据落盘成功!
SQL > u
请输入表名: T1
请输入RMID(内存行号): 3
请输入CID(列号): 2
请输入字段新值: ccc
# 更新后表数据如下:
R[00] RMID[00] : ID2 NAME
R[01] RMID[01] : 1 a
R[02] RMID[03] : 3 ccc
# [2]ROWS,[2]COLS
字典落盘成功!
数据落盘成功!
SQL > e
字典落盘成功!
数据落盘成功!
#######【感谢使用 ZDB 再见!】#######
进程已结束,退出代码0
//引用头文件
#include
#include
#include
#include
#include
//使用宏定义代替相关内容
#define R 99 //行
#define C 99 //列
#define N 32 //名称
#define L 99 //长度
#define S 32 //SIZE
#define DefaultPATH ".\\ZDATA\\"
//全局变量
int i, j, k = 0; //
int fr, fc = 0; //文件行数,文件列数
int rn, cn = 0; //行号、列号
int oid, noid = 0; //全局对象号、内存号
char g_temp[L]; //临时使用的字符串变量
char table_name[N]; //表名
FILE *fp;
char Path[L];
char dict_file[N] = "ZDB.dic"; //文件名
char data_file[N] = "ZDB.dat"; //文件名
typedef struct { //字典
int state; //状态:0>未分配、1>有效、2>删除
char name[N]; //名称
char value[L]; //值
char comment[L]; //描述
} S_Dict;
S_Dict g_dict[S];
typedef struct { //表
int state; //状态:0>未分配、1>新表、2>有数、3>删除、4>可复用
char name[N]; //名称
int oid; //编号
int rows; //行数
int cols; //列数
char record[R][C][L]; //数据&状态:"">未分配、H>字段名、A>可用、D>删除
} S_Table;
S_Table g_table[S];
//函数声明
int Head(); //界面输出
int Body(); //程序主体
int Bye(); //程序退出
int Help(); //
int Iint_DB(); //
int Iint_Dir(); //
int Data_Read(); //
int Data_Write(); //
int Dict_Read(); //
int Dict_Write(); //
int List_Table(); //
int Create(char table_name[N]); //
int Drop(char table_name[N]); //
int Alter(char table_name[N], int cn, char g_temp[L]); //
int Truncate(char table_name[N]); //
int Purge(); //
int Flashback(int oid); //
int Rename(char table_name[N], char g_temp[L]); //
int Insert(char table_name[N]); //
int Delete(char table_name[N], int rn); //
int Update(char table_name[N], int rn, int cn, char g_temp[L]); //
int Select(char table_name[N]); //
//主函数
int main() {
Head();
Dict_Read();
Data_Read();
Body();
return 0;
}
//函数定义
int Head() {
system("CLS");
printf("##########【欢迎使用 ZDB】##########\n");
printf("# 作者:zzt_2009 版本:V 5.0.0 #\n");
printf("# [H/h]查看帮助 [E/e]退出程序 #\n");
return 1;
}
int Body() {
//命令判断
while (1) {
printf("SQL > ");
int n;
n = getch();
printf("%c\n", (char) n); //输出按键
switch (n) {
case 76: // L -> list table defination
List_Table();
break;
case 67: // C -> create
printf("请输入表名: ");
scanf("%s", table_name);
Create(table_name);
break;
case 68: // D -> drop
printf("请输入表名: ");
scanf("%s", table_name);
Drop(table_name);
break;
case 65: // A -> alter
printf("请输入表名: ");
scanf("%s", table_name);
printf("请输入CID(列号): ");
scanf("%d", &cn);
printf("请输入字段新值: ");
scanf("%s", g_temp);
Alter(table_name, cn, g_temp);
break;
case 70: // F -> flashback
printf("请输入表OID: ");
scanf("%d", &oid);
Flashback(oid);
break;
case 80: // P -> purge
Purge();
break;
case 84: // T -> truncate
printf("请输入表名: ");
scanf("%s", table_name);
Truncate(table_name);
break;
case 82: // R -> rename
printf("请输入表名: ");
scanf("%s", table_name);
printf("请输入新名: ");
scanf("%s", g_temp);
Rename(table_name, g_temp);
break;
case 105: // i -> insert
printf("请输入表名: ");
scanf("%s", table_name);
Insert(table_name);
break;
case 100: // d -> delete
printf("请输入表名: ");
scanf("%s", table_name);
printf("请输入RMID(内存行号): ");
scanf("%d", &rn);
Delete(table_name, rn);
break;
case 117: // u -> update
printf("请输入表名: ");
scanf("%s", table_name);
printf("请输入RMID(内存行号): ");
scanf("%d", &rn);
printf("请输入CID(列号): ");
scanf("%d", &cn);
printf("请输入字段新值: ");
scanf("%s", g_temp);
Update(table_name, rn, cn, g_temp);
break;
case 115: // s -> select
printf("请输入表名: ");
scanf("%s", table_name);
Select(table_name);
break;
case 99: // c -> clear
Head();
break;
case 72: // H -> help
case 104: // h -> help
Help();
break;
case 69: // E -> exit
case 101: // e -> exit
Dict_Write();
Data_Write();
Bye();
exit(0);
default:
printf("命令不正确请重新输入,或按[H/h]查看帮助\n");
break;
}
}
}
int Bye() {
printf("#######【感谢使用 ZDB 再见!】#######\n");
return 0;
}
int Help() {
printf("# L For > list table def #\n");
printf("# C For > Create table #\n");
printf("# D For > Drop table #\n");
printf("# A For > Alter table #\n");
printf("# R For > Rename table #\n");
printf("# T For > Truncate table #\n");
printf("# F For > Flashback table #\n");
printf("# P For > Purge recyclebin #\n");
printf("# i For > insert #\n");
printf("# d For > delete #\n");
printf("# u For > update #\n");
printf("# s For > select #\n");
printf("# c For > clear #\n");
printf("# H/h For > help #\n");
printf("# E/e For > exit #\n");
}
int Dict_Read() {
//printf("字典加载中…………\n");
strcpy(Path, DefaultPATH);
strcat(Path, dict_file);
fp = fopen(Path, "rb");
if (fp == NULL) {
printf("首次使用,正在初始化数据字典……\n");
Iint_DB();
Iint_Dir();
Dict_Write();
printf("字典文件初始化完成。\n");
}
fread(&g_dict, sizeof(S_Dict), S, fp);
fclose(fp);
noid = atoi(g_dict[0].value);
//printf("字典文件已加载[noid:%d]\n", noid);
return 0;
}
int Data_Read() {
//printf("数据加载中…………\n");
strcpy(Path, DefaultPATH);
strcat(Path, data_file);
fp = fopen(Path, "rb");
if (fp == NULL) {
printf("首次使用,正在初始化数据文件……\n");
Iint_DB();
Iint_Dir();
Data_Write();
printf("数据文件初始化完成。\n");
//
printf("首次使用,已为您展示帮助文档。\n");
Help();
}
fread(&g_table, sizeof(S_Table), S, fp);
fclose(fp);
//printf("数据文件已加载[%s]\n", g_table[0].name);
return 0;
}
int Iint_DB() {
//数据字典
g_dict[0].state = 1;
strcpy(g_dict[0].name, "noid");
strcpy(g_dict[0].value, "1");
strcpy(g_dict[0].comment, "下一个对象的编号");
//案例表
strcpy(g_table[0].name, "T0");
g_table[0].state = 2;
g_table[0].oid = 0;
g_table[0].rows = 4;
g_table[0].cols = 2;
g_table[0].oid = 0;
strcpy(g_table[0].record[0][0], "H");
strcpy(g_table[0].record[0][1], "TEL");
strcpy(g_table[0].record[0][2], "NAME");
strcpy(g_table[0].record[1][0], "A");
strcpy(g_table[0].record[1][1], "110");
strcpy(g_table[0].record[1][2], "Police");
strcpy(g_table[0].record[2][0], "A");
strcpy(g_table[0].record[2][1], "120");
strcpy(g_table[0].record[2][2], "Ambulance");
strcpy(g_table[0].record[3][0], "A");
strcpy(g_table[0].record[3][1], "119");
strcpy(g_table[0].record[3][2], "Fire");
return 0;
}
int Iint_Dir() {
if (_mkdir(DefaultPATH) == 0) {
//printf("目录创建成功!\n");
} else {
//printf("目录已存在!\n");
}
return 0;
}
int Dict_Write() {
strcpy(Path, DefaultPATH);
strcat(Path, dict_file);
fp = fopen(Path, "wb");
if (fp == NULL) {
printf("无法打开文件\n");
exit(1);
}
fwrite(&g_dict, sizeof(S_Dict), S, fp);
fclose(fp);
printf("字典落盘成功!\n");
return 0;
}
int Data_Write() {
strcpy(Path, DefaultPATH);
strcat(Path, data_file);
fp = fopen(Path, "wb");
if (fp == NULL) {
printf("无法打开文件\n");
exit(1);
}
fwrite(&g_table, sizeof(S_Table), S, fp);
fclose(fp);
printf("数据落盘成功!\n");
return 0;
}
int List_Table() {
i = 0;
printf("# 库中所有状态的表信息如下\n");
printf("内存ID 表名 表状态 表编号 行数 列数\n");
while (g_table[i].state != 0) {
printf("TMID[%d] %4s STA[%d] OID[%d] ROWS[%d] COLS[%d]\n",
i, g_table[i].name, g_table[i].state, g_table[i].oid, g_table[i].rows, g_table[i].cols);
i++;
}
printf("# STA列值:1>新/空表、2>有数据、3>已删除、4>可复用\n");
}
int Create(char table_name[N]) {
//判断表是否存在
i = 0;
while (g_table[i].state != 0) {
if ((strcmp(g_table[i].name, table_name) == 0) && ((g_table[i].state == 1) || (g_table[i].state == 2))) {
printf("表已存在\n");
goto label_Create_end;
}
i++;
}
//寻找可写的区域+重置之前的表记录状态为D
i = 0;
while (i < S) {
if ((g_table[i].state == 0) || (g_table[i].state == 4)) {
g_table[i].state = 1; //设置表状态为:0>未分配、1>空表、2>有数、3>删除、4>清除
g_table[i].oid = noid;
g_table[i].rows = 0;
g_table[i].cols = 0;
strcpy(g_table[i].name, table_name);
//记录列定义
strcpy(g_table[i].record[0][0], "H"); //record状态:H>字段名、A>可用、D>删除
printf("请输入列的[数量]:");
scanf("%d", &g_table[i].cols);
printf("列的[数量]:%d\n", g_table[i].cols);
for (j = 0; j < g_table[i].cols; j++) {
printf("请输入第[%d]列的[名称]:", j + 1);
scanf("%s", &g_table[i].record[0][j + 1]);
}
noid++;
strcpy(g_dict[0].value, itoa(noid, g_temp, L));
//调试
printf("表TMID:%d,表OID:%d", i, noid);
//重置表旧数据状态
j = 1;
while (strlen(g_table[i].record[j][0]) != 0) {
strcpy(g_table[i].record[j][0], "");
j++;
}
//写盘
Dict_Write();
Data_Write();
return 1;
}
i++;
}
printf("表空间已满,请扩容!");
//goto标签
label_Create_end:
return 0;
}
int Drop(char table_name[N]) {
//判断表是否存在
i = 0;
while (g_table[i].state != 0) {
if ((strcmp(g_table[i].name, table_name) == 0) && ((g_table[i].state == 1) || (g_table[i].state == 2))) {
//标记表状态为删除状态
g_table[i].state = 3;
printf("已删除\n");
//写盘
Dict_Write();
Data_Write();
return 1;
}
i++;
}
printf("表不存在!\n");
}
int Alter(char table_name[N], int cn, char g_temp[L]) {
//判断表是否存在
i = 0;
while (g_table[i].state != 0) {
if ((strcmp(g_table[i].name, table_name) == 0) && ((g_table[i].state == 1) || (g_table[i].state == 2))) {
//表存在则更新字段
rn = 0;
if (cn != 0) {
strcpy(g_table[i].record[rn][cn], g_temp);
printf("# 更新后表结构和数据如下:\n");
Select(table_name);
//写盘
Dict_Write();
Data_Write();
return 1;
}
printf("非列名位置不容许DDL!\n");
return 1;
}
i++;
}
printf("表不存在!\n");
}
int Rename(char table_name[N], char g_temp[L]) {
//判断新名是否冲突
i = 0;
while (g_table[i].state != 0) {
if ((strcmp(g_table[i].name, g_temp) == 0) && ((g_table[i].state == 1) || (g_table[i].state == 2))) {
printf("新表名已存在!\n");
return 0;
}
i++;
}
//判断表是否存在
i = 0;
while (g_table[i].state != 0) {
if ((strcmp(g_table[i].name, table_name) == 0) && ((g_table[i].state == 1) || (g_table[i].state == 2))) {
//更新
strcpy(g_table[i].name, g_temp);
printf("# 更新成功:\n");
List_Table();
//写盘
Dict_Write();
Data_Write();
return 1;
}
i++;
}
printf("表不存在!\n");
}
int Truncate(char table_name[N]) {
//判断表是否存在
i = 0;
while (g_table[i].state != 0) {
if ((strcmp(g_table[i].name, table_name) == 0) && ((g_table[i].state == 1) || (g_table[i].state == 2))) {
//标记状态
g_table[i].state = 1;
//重置表旧数据状态
j = 1;
while (strlen(g_table[i].record[j][0]) != 0) {
strcpy(g_table[i].record[j][0], "");
j++;
}
printf("表已清空\n");
//写盘
Dict_Write();
Data_Write();
return 1;
}
i++;
}
printf("表不存在!\n");
}
int Purge() {
//标记所有删除状态的表为可复用状态
i = 0;
while (g_table[i].state != 0) {
if (g_table[i].state == 3) {
//标记状态
g_table[i].state = 4;
}
i++;
}
printf("回收站已清空!\n");
//写盘
Dict_Write();
Data_Write();
return 1;
}
int Flashback(int oid) {
//判断表是否存在
i = 0;
while (g_table[i].state != 0) {
if ((g_table[i].oid == oid) && (g_table[i].state == 3)) {
//标记状态
if (strlen(g_table[i].record[1][0]) == 0) {
g_table[i].state = 1;
} else {
g_table[i].state = 2;
}
strcpy(g_table[i].name, itoa(g_table[i].oid, g_temp, L));
printf("表已闪回\n");
//写盘
Dict_Write();
Data_Write();
return 1;
}
i++;
}
printf("表不存在或已被清理!\n");
}
int Insert(char table_name[N]) {
//判断表是否存在
i = 0;
while (g_table[i].state != 0) {
if ((strcmp(g_table[i].name, table_name) == 0) && ((g_table[i].state == 1) || (g_table[i].state == 2))) {
//插入数据
char *token;
j = 0; //j记录输入了多少行
printf("请输入多行(列以tab分隔,新行\";\\n\"结束)\n");
//循环读取每行输入直到达到最大行数或者遇到文件结束符EOF
while (j < R && fgets(g_temp, sizeof(g_temp), stdin)) {
if (strcmp(g_temp, ";\n") == 0) { // 如果输入为";\n",则结束输入
break;
}
//打印输入提示符
printf("Input > ");
// 使用strtok分割输入字符串并存入表字段中
token = strtok(g_temp, "\t\n");
rn = 1; //由于第一行存储了表列信息,因此从第二行开始
cn = 1; //将输入按token存入1开头的数组中
while (rn < R) { //遍历表record,找到可以存储的位置
if ((strcmp(g_table[i].record[rn][0], "D") == 0) || (strlen(g_table[i].record[rn][0]) == 0)) {
while (token != NULL && cn < C) {
strcpy(g_table[i].record[rn][0], "A"); //设置record插入的首列
strncpy(g_table[i].record[rn][cn], token, L);
g_table[i].record[rn][cn][L - 1] = '\0'; // 确保字符串以null字符结尾
token = strtok(NULL, "\t\n");
cn++;
}
break;
}
rn++;
}
j++;
}
printf("插入结束!\n");
//修改表状态
g_table[i].state = 2;
//写盘
Dict_Write();
Data_Write();
//查看插入结果
printf("# 表[%s]最新数据如下:\n", g_table[i].name);
Select(g_table[i].name);
return 1;
}
i++;
}
printf("表不存在!\n");
return 0;
}
int Delete(char table_name[N], int rn) {
//判断表是否存在
i = 0;
while (g_table[i].state != 0) {
if ((strcmp(g_table[i].name, table_name) == 0) && ((g_table[i].state == 2) || (g_table[i].state == 1))) {
//空表提示
if (g_table[i].state == 1) {
printf("# 空表,无法DML!\n");
return 0;
}
//标记行
if (rn != 0) {
strcpy(g_table[i].record[rn][0], "D");
printf("# RMID[%d]已删除\n", rn);
printf("# 删除后表数据如下:\n");
Select(table_name);
//写盘
Dict_Write();
Data_Write();
return 1;
}
printf("首行为列名不容许删除!\n");
return 1;
}
i++;
}
printf("表不存在!\n");
}
int Update(char table_name[N], int rn, int cn, char g_temp[L]) {
//判断表是否存在
i = 0;
while (g_table[i].state != 0) {
if ((strcmp(g_table[i].name, table_name) == 0) && ((g_table[i].state == 2) || (g_table[i].state == 1))) {
//空表提示
if (g_table[i].state == 1) {
printf("# 空表,无法DML!\n");
return 0;
}
//更新字段
if ((rn != 0) && (cn != 0) && (strcmp(g_table[i].record[rn][0], "A") == 0)) {
//printf("%d %d %s %d\n", rn, cn, g_table[i].record[rn][0], strcmp(g_table[i].record[rn][0], "A"));
strcpy(g_table[i].record[rn][cn], g_temp);
printf("# 更新后表数据如下:\n");
Select(table_name);
//写盘
Dict_Write();
Data_Write();
return 1;
}
printf("不容许DML的位置!\n");
return 1;
}
i++;
}
printf("表不存在!\n");
}
int Select(char table_name[N]) {
//判断表是否存在
i = 0;
while (g_table[i].state != 0) {
if ((strcmp(g_table[i].name, table_name) == 0) && ((g_table[i].state == 2) || (g_table[i].state == 1))) {
//输出内容
j = 0;
k = 0;
rn = 0;
while (strlen(g_table[i].record[rn][0]) != 0) { //遍历非空数组
if (strcmp(g_table[i].record[rn][0], "D") != 0) { //检索非delete状态的记录
cn = 0;
printf("R[%02d] RMID[%02d] : ", k, rn);
while (strlen(g_table[i].record[rn][cn]) != 0) {
printf("%s\t", g_table[i].record[rn][cn + 1]);
j++;
cn++;
}
printf("\n");
k++;
}
rn++;
}
rn = k - 1;
cn = j / (rn + 1) - 1;
printf("# [%d]ROWS,[%d]COLS\n", rn, cn);
//修正字典
if ((g_table[i].rows != rn) || (g_table[i].cols != cn)) {
g_table[i].rows = rn;
g_table[i].cols = cn;
printf("表行列数不正确,修正完成.\n", rn, cn);
Data_Write();
}
return 1;
}
i++;
}
printf("表不存在!\n");
}
//end
※ 如果您觉得文章写的还不错, 别忘了在文末给作者点个赞哦 ~