Ad

Combine Rows Of A Sqlite_exec Response Into A Single String From A SQLite Callback In C

- 1 answer

I am trying to pretty print a sqlite table returned from a SELECT call, but I can't get the pointers to work right.

I have the following so far:

char *full_response = "";
sqlite3_exec(db, select_query, callback, (void *)full_response, &zErrMsg);
printf(full_response);  // This is where I'd like the entire table

And the following callback function:

static int callback(void *full_response, int argc, char **argv, char **azColName){
   int i;
   int response_header_length = 0;
   int response_body_length = 0;
   
   // This is to get the length of the header and body strings.
   for (i=0;i<argc;i++){
    response_header_length+=strlen(azColName[i])+1;
    response_body_length+=strlen(argv[i])+1;
   }
   //This is to turn the headers and field values of the returned table into a single string
   char response_header[response_header_length-1];strcpy(response_header,azColName[0]);
   char response_body[response_body_length-1];strcpy(response_body,argv[0]);
   for (i=1;i<argc;i++){
       strcat(response_header,"|"); strcat(response_header,azColName[i]);
       strcat(response_body,"|"); strcat(response_body,argv[i]);
   }


   if (strlen((char *)full_response)==0){ // full_response is empty so we need the header
    char temp_response[strlen(response_header) + strlen(response_body)+1];
    strcpy(temp_response, response_header);
    strcat(temp_response, "\n");
    strcat(temp_response, response_body);
    full_response = &temp_response;
   }
   else{ // full_response has stuff in it so we just need the field values for this row
   char temp_response[strlen((char *)full_response) + strlen(response_body)+1];
   strcpy(temp_response, (char *)full_response);
   strcat(temp_response, "\n");
   strcat(temp_response, response_body);
   full_response = &temp_response;
   }
   return 0;
}

(I know it isn't very elegant, but this is mostly for a proof of concept for something else).

I thought the line

full_response = &temp_response

in the callback function would reassign the full_response address to the address of temp_response, which should contain the table so far plus the current row being processed by the callback function, but it seems to not work like that.

--

Alternatively, if there is a pretty-print function or code snippet or git repo in C that gets me the entire returned table in a string in the main body (i.e. where sqlite_exec is called), that would be magnificent.

Ad

Answer

You'll need to allocate your string dynamically (DISCLAIMER -- untested):

struct Buffer {
    size_t n;
    // todo: may want to add capacity and over-allocate to avoid worst-case quadratic performance.
    char *p;
};
void bufcat(struct Buffer *buf, const char *s) {
    size_t m = strlen(s), n = buf->n+m;
    char *p = realloc(buf->p, n+1);
    /* note: realloc may fail */
    memcpy(p+buf->n, s, m+1);
    buf->p = p;
    buf->n = n;
}

static int callback(void *full_response, int argc, char **argv, char **azColName){
    struct Buffer *buf = full_response;
    if(buf->n == 0) {
        for(int i=0;i<argc;i++){
            if(i) bufcat(buf, "|");
            bufcat(buf, azColName[i]);
        }
        bufcat(buf, "\n");
    }
    for(int i=0;i<argc;i++){
        if(i) bufcat(buf, "|");
        bufcat(buf, argv[i]);
    }
    bufcat(buf, "\n");
    return 0;
}

Then you can use that as follows:

struct Buffer buf = {};
sqlite3_exec(db, select_query, callback, (void *)&buf, &zErrMsg);
puts(buf.p ? buf.p : "");
free(buf.p);

Alternatively you can use open_memstream on platforms where it's available:

static int callback(void *full_response, int argc, char **argv, char **azColName){
    FILE *file = full_response;
    if(ftell(file) == 0) {
        for(int i=0;i<argc;i++){
            if(i) fputc('|',file);
            fputs(azColName[i],file);
        }
        fputc('\n',file);
    }
    for(int i=0;i<argc;i++){
        if(i) fputc('|',file);
        fputs(argv[i],file);
    }
    fputc('\n',file);
    return 0;
}

Usage:

char *full_response = 0;
size_t n = 0;
FILE *file = open_memstream(&full_response, &n);
sqlite3_exec(db, select_query, callback, (void *)file, &zErrMsg);
fclose(file);
puts(full_response);
free(full_response);
Ad
source: stackoverflow.com
Ad