前段时间,在工作中需要使用Sqlite3嵌入式数据库,在生成sql语句的过程中,每次都要去预估sql大概的长度,有些时候插入一条记录的时候,sql语句可能会很长,如果是在栈上分配,觉得不是很妥,索性就写了一个可以自动扩容的string buffer,用于存储生成的sql。
1. 首先是声明string buffer:
typedef struct _string_buffer_t
{
uint32_t size;
uint32_t len;
char* str;
uint16_t free:1; // self free or not
uint16_t reverse:15;
}string_buffer_t;
2. 接下来是实现string buffer对象的初始化与销毁的方法:
int string_buffer_init(string_buffer_t *self, char *p, uint32_t size)
{
if(self == NULL){
ERR("null point");
return ENULL_POINT;
}
if(size == 0){
size = STRING_BUFFER_DEFAULT_SIZE;
}
if(p){
self->str = p;
self->free = 0;
}else{
self->str = (char *)MALLOC(size);
if(self->str == NULL){
ERR("MALLOC failed");
return EMALLOC;
}
self->free = 1;
}
self->size = size;
self->len = 0;
return RET_SUCCESS;
}
void string_buffer_reset(string_buffer_t *self)
{
if(self){
if(self->str && self->free){
free(self->str);
memset(self, 0, sizeof(*self));
}
}
}
在init方法中,允许用户使用外部分配的内存,如果不使用,则p=NULL.
3. 接着实现string expand方法:
static int _string_buffer_expand(string_buffer_t *self, size_t len)
{
if(self->free == 0){
DBG("can not expand");
return EINVAL_ARG;
}
size_t t_len = self->len + len + 1;
if(t_len <= self->size)
return RET_SUCCESS;
t_len = self->size + len + 1;
t_len = ((t_len + STRING_BUFFER_MASK) && ~STRING_BUFFER_MASK);
char *ptr = (char *)realloc(self->str, t_len);
if(ptr != NULL){
self->size = t_len;
if(ptr != self->str){
memcpy(ptr, self->str, self->len);
self->str = ptr;
}
return RET_SUCCESS;
}else{
ERR("realloc failed");
return EMALLOC;
}
}
4. 最后,为了方便,实现比较常用的几个方法:
int string_buffer_snprintf(string_buffer_t *self, const char *fmt, ...)
{
int ret;
va_list ap;
va_start(ap, fmt);
if(self->len >= self->size){
if((ret = _string_buffer_expand(self, self->len)) < 0){
return ret;
}
}
_RETRY:
ret = vsnprintf(self->str + self->len, self->size - self->len, fmt, ap);
if(ret <= 0){
DBG("vsnprintf failed");
return -1;
}
if((self->len + ret) >= self->size){
if(self->str[self->size-1] != '\0'){
if((ret = _string_buffer_expand(self, ret)) < 0){
return ret;
}
goto _RETRY;
}
}
self->len += ret;
self->str[self->len] = '\0';
va_end(ap);
return RET_SUCCESS;
}
int string_buffer_strcat(string_buffer_t *self, const char *str)
{
size_t len = strlen(str);
int ret = _string_buffer_expand(self, len);
if(ret != RET_SUCCESS)
return ret;
strcpy(self->str + self->len, str);
self->len += len;
self->str[self->len] = '\0';
return RET_SUCCESS;
}
int string_buffer_insert(string_buffer_t *self, int index, const char *str)
{
size_t len = strlen(str);
size_t start;
int ret;
if(index > self->len){
DBG("overlen: index(%d) > len(%u)", index, self->len);
return EINVAL_ARG;
}
if(index >= 0){
start = index;
}else{
if(index + self->len >= 0){
start = index + self->len;
}else{
DBG("overlen: index(%d) < -len(%u)", index, self->len);
return EINVAL_ARG;
}
}
size_t end = start + len;
if(end >= self->size){
if((ret = _string_buffer_expand(self, len+1)) < 0){
return ret;
}
}
strcpy(self->str+start, str);
self->len = end;
self->str[self->len] = '\0';
return RET_SUCCESS;
}
void string_buffer_debug(string_buffer_t *self)
{
if(self && self->str){
printf("str: %s\n", self->str);
}
}
const char *string_buffer_get_str(string_buffer_t *self)
{
if(self)
return self->str;
}
其中string_buffer_snprintf就像是snprintf,可以直接格式化字符串。string_buffer_strcat跟strcat一样效果,将字符串str连接到原先字符串的后面。string_buffer_insert方法,用于在指定的位置index处,插入字符串str,这里的index可以是负数,如-1表示插入到最后一个字符的位置处。string_buffer_debug方法是为了方便调试,将里面的字符串打印出来。
5. 最后这里简单的写了一个测试程序
int string_buffer_test(void)
{
string_buffer_t sql_buf;
int ret;
printf("** test sql_buffer:\n");
ret = sql_buffer_init(&sql_buf, NULL, 0);
ASSERT(ret == RET_SUCCESS);
ret = string_buffer_strcat(&sql_buf, \
"CREATE TABLE test_table(id INT PRIMARY KEY,name CHAR NOT NULL);");
ASSERT(ret == RET_SUCCESS);
string_buffer_debug(&sql_buf);
ret = string_buffer_snprintf(&sql_buf, "INSERT INTO %s(id, name) VALUES(%d,'%s')",
"test_table", 1, "xxxx");
ASSERT(ret == RET_SUCCESS);
string_buffer_debug(&sql_buf);
ret = string_buffer_snprintf(&sql_buf, "UPDATE %s SET name='%s' WHERE id=%d,",\
"test_table", "yyyy", 1);
ASSERT(ret == RET_SUCCESS);
ret = string_buffer_insert(&sql_buf, -1, "");
string_buffer_debug(&sql_buf);
sql_buffer_reset(&sql_buf);
printf("** test sql_buffer success\n");
return 0;
}
int main(int argc, char **argv)
{
return string_buffer_test();
}