Converting a MySQL result into a JSON string in C - mysql

How do I convert a result from a MySQL query to a JSON string in C?
Of course I know how to do it, I just wondered if there's already a solution to copy-paste, realizing I don't want to write boiler-plate code.
And how do I Google for this question? Google just disregard the c and shows results for PHP etc.

The C file I made to do this is four times faster than the corresponding PHP file, using ab to measure performance:
ab -k -c 300 -n 10000 localhost/tiny.php
Time per request: 393.072 [ms] (mean)
With C:
ab -k -c 300 -n 10000 localhost/cgi/tiny.fcgi
Time per request: 98.237 [ms] (mean)
This is assuming Apache spawns 10 processes of tiny.fcgi, and PHP does not use FastCGI.
FastCgiServer /var/www/cgi/tiny.fcgi -processes 10
This is the PHP code, which connects to MySQL, fetch a query result and echo the JSON representation:
<?php
$mysqli = mysqli_connect("localhost", "user", "password", "db");
mysqli_set_charset($mysqli, "utf8");
if (mysqli_connect_errno()) {
printf("Connect failed: %s\n", mysqli_connect_error());
exit();
}
$result = mysqli_query($mysqli, "SELECT * FROM table");
$rows = array();
while ($row = mysqli_fetch_assoc($result)) {
$rows[] = $row;
}
echo json_encode($rows);
Let's step through the C code. I will use the PHP internal struct smart_str to handle strings (this is what PHP use in the json_encode function). Using the naive char* for strings will be catastrophical, because the number of malloc and free will be huge due to string append usage.
We want to use FastCGI:
#include "fcgi_stdio.h"
int main(void)
{
while(FCGI_Accept() >= 0)
{
}
FCGI_Finish();
return 0;
}
Analysing this with valgrind gives 768 bytes still reachable at end, which we will ignore (bug in FastCGI probably).
Next comes MySQL connection and query:
MYSQL* connection = NULL;
MYSQL_RES* result = NULL;
connection = mysql_init(NULL);
if (connection == NULL)
{
(void) FCGI_fprintf(FCGI_stderr, "Could not connect to MySQL: %s\n", mysql_error(connection));
continue;
}
// Connect to database
if (mysql_real_connect(connection, "localhost", "user", "password", "db", 0, NULL, 0) == NULL)
{
close_mysql_with_error(connection);
continue;
}
// Select from pages
if (mysql_query(connection, "SELECT * FROM table") != 0)
{
close_mysql_with_error(connection);
continue;
}
// Get result
result = mysql_store_result(connection);
// Abort if no result
if (result == NULL)
{
close_mysql_with_error(connection);
continue;
}
(I use continue instead of exit or return, because this code is within the while loop seen above.)
Nothing strange here, right?
The next part will create our smart_str JSON variable, pass it to function result_to_json and then echo the result.
smart_str json = {0, 0, 0};
result_to_json(result, &json);
if (json.c != NULL)
(void) FCGI_printf("json = %s\n", json.c);
smart_str_free(&json);
The result_to_json is just a loop over the rows in the MySQL result:
static void result_to_json(MYSQL_RES *result, smart_str* json)
{
MYSQL_ROW row;
int i;
int num_fields = (int) mysql_num_fields(result);
smart_str** fields = get_field_names(result, num_fields);
if (fields == NULL)
{
return;
}
smart_str_appendc(json, '[');
while ((row = mysql_fetch_row(result, num_fields)))
{
smart_str_appendl(json, "{", 1);
for (i = 0; i < num_fields; i++)
{
// key
smart_str_appendl(json, "\"", 1);
smart_str_appendl(json, fields[i]->c, fields[i]->len);
smart_str_appendl(json, "\": ", 3);
if (row[i] == NULL)
{
smart_str_appendl(json, "null", 4);
smart_str_appendl(json, ", ", 2);
}
else
{
smart_str_appendl(json, "\"", 1);
smart_str_appendl(json, row[i], strlen(row[i]));
smart_str_appendl(json, "\", ", 3);
}
}
if (json == NULL) {
free_field_names(fields, num_fields);
return;
}
// Strip last ','
json->len--;
json->len--;
smart_str_appendl(json, "}, ", 3);
}
if (json == NULL)
{
free_field_names(fields, num_fields);
return;
}
// Strip last ','
json->len--;
json->len--;
smart_str_appendl(json, "]", 1);
smart_str_0(json);
free_field_names(fields, num_fields);
return;
}
Finally, get_field_names and free_field_names:
static smart_str** get_field_names(MYSQL_RES *my_result, int num_fields)
{
smart_str** fields; // Array of pointers
MYSQL_FIELD *field = NULL;
int i;
// Allocate size of array
fields = malloc(num_fields * sizeof(smart_str*));
if (fields == NULL)
{
return NULL;
}
for (i = 0; i < num_fields; i++)
{
field = mysql_fetch_field(my_result);
if (field == NULL) {
// TODO: Free fields[]
free(fields);
return NULL;
}
fields[i] = malloc(sizeof(smart_str));
if (fields[i] == NULL) {
// TODO: Free fields[]
free(fields);
return NULL;
}
else
{
fields[i]->c = NULL;
smart_str_appendl(fields[i], field->name, strlen(field->name));
}
return fields;
}
static void free_field_names(smart_str** strings, int size)
{
int i;
for (i = 0; i < size; i++)
{
smart_str_free(strings[i]);
free(strings[i]);
}
free(strings);
}
There you go! What's left to do is to measure against FastCGI enabled PHP, to see how much the PHP performance improves.

Related

CentOS6.9 (MySql v5.7.22) use mysql C API mysql_real_query cause the memory always growing

I test the mysql_real_query API, I just loop to execute Sql syntax ,like 'UPDATE ** SET **' there is a leak memory bug occur. when I use 'top' to check the bug, I find the system 'used memory' option will always growing until the system or process crush. but 'mysqld' and 'testsql' processes's %MEM option has not increase, System free memory look like disappear. I try to force kill the 'testsql' process but the memory still be used and can not be release. Why? Please help me.
int ThreadExeSQL(MYSQL* lpSQLConn, char * sql, int iLen)
{
if (mysql_real_query(lpSQLConn, sql, iLen))
{
MYSQL_RES* lpGetSQLRes = mysql_store_result(lpSQLConn);
mysql_free_result(lpGetSQLRes);
return -1;
}
//mysql_errno(lpSQLConn);
//mysql_error(lpSQLConn);
MYSQL_RES* lpGetSQLRes = mysql_store_result(lpSQLConn);
mysql_free_result(lpGetSQLRes); // release sql memory
return 0; // success
}
void* ThreadSQL_HexWrite(void* lpGet)
{
LPThreadParam getParam = (LPThreadParam)lpGet;
MYSQL* lpSQLConn = (MYSQL*)&getParam->lpSQLConn;
int iThreadIdx = getParam->iThreadIdx;
printf("ID:%d\n", iThreadIdx);
mysql_thread_init();
lpSQLConn = mysql_init(NULL);
if (!mysql_real_connect(lpSQLConn, g_host_name, g_user_name, g_password, g_db_name, g_db_port, NULL, 0))
{
ThreadSQLError(lpSQLConn, NULL);
return;
}
else
{
printf("mysql_real_connect OK!\n");
}
for (int i = 0; i < 1000000; i++)
{
char lpCmdStr[8192] = "\0";
sprintf(lpCmdStr, "update %s set %s=0x%d where id=%d\0", "tb_Data", "Info", i, 1);
if (ThreadExeSQL(lpSQLConn, (char*)lpCmdStr, strlen(lpCmdStr)))
{
MySQLError getError = ThreadSQLError(lpSQLConn, NULL);
HandleMySqlError(getError);
continue; //error
}
else
{
printf("ok. ");
}
usleep(1000 * 10);
}
mysql_close(lpSQLConn);
mysql_thread_end();
printf("ThreadSQL_HexWrite OK!\n");
}
MYSQL* g_MySQLConnList[100];
void main()
{
if (mysql_library_init(0, NULL, NULL))
{
printf("could not initialize MySQL client library\n");
exit(1);
}
int thread_num = 1;
//while (true)
{
pthread_t *pTh = new pthread_t[thread_num];
for (int i = 0; i < thread_num; i++)
{
LPThreadParam lpSetParam = new ThreadParam;
lpSetParam->lpSQLConn = (MYSQL*)&g_MySQLConnList[i];
lpSetParam->iThreadIdx = i;
printf("---create thread idx:%d\n", i);
if (0 != pthread_create(&pTh[i], NULL, ThreadSQL_HexWrite, lpSetParam))
{
printf("pthread_create failed\n");
continue;
}
}
for (int i = 0; i < thread_num; i++)
{
pthread_join(pTh[i], NULL);
}
delete[] pTh;
}
mysql_library_end();
printf("All Done!\n");
}
Your connection is not closing when ThreadExeSQL() fails.

C- HTTP web server: how to cache

I know that by using wget INSIDE my c program for the server, this is possible. For example, we have to wget facebook.com. Then a file called index.html will be downloaded to my laptop. After that, we have to call in /facebook.com/index.html to the web server through the web browser once more. Any idea on how to do this? I tried to do this but this is wrong.
int connection(int fd, int portnum) {
char request[500], resource[500], answer, *ptr;
int fd1, length,c1,c2;
if (recv_new(fd, request) == 0) {
printf("Receive Failed\n");
}
printf("%s\n", request);
// Check for a valid browser request
ptr = strstr(request, " HTTP/");
if (ptr == NULL) {
printf("NOT HTTP !\n");
} else {
*ptr = 0;
ptr = NULL;
if (strncmp(request, "GET ", 4) == 0) {
ptr = request + 4;
}
if (ptr == NULL) {
printf("Unknown Request ! \n");
} else {
if (ptr[strlen(ptr) - 1] == '/') {
strcat(ptr, "test.html");
}
char s1[100] = "facebook.com";
char s2[100] = "twitter.com";
c1 = strncmp(s1,ptr,100);
c2 = strncmp(s2,ptr,100);
if((c1==0)||(c2==0))
{
printf("ACCESS DENIED\n");
}
//how to compare strings
strcpy(resource, webroot());
strcat(resource, ptr);
char* s = strchr(ptr, '.');
int i;
for (i = 0; extensions[i].ext != NULL; i++) {
if (strcmp(s + 1, extensions[i].ext) == 0) {
fd1 = open(resource, O_RDONLY, 0);
printf("Opening \"%s\"\n", resource);
system("wget -E -H -k -K -p localhost:8888"); //lets assume that this is the default port number
printf("Do you want to reload your recent website?\n");
scanf(" %c", &answer);
if(answer=='Y'||answer=='y')
{
fd1 = open(resource, O_RDONLY, 0);
printf("Opening \"%s\"\n", resource);
}
Have a look at the curl library. It allows you to do what you need and much more..
https://curl.haxx.se/

How to get all html file with a HTTP GET request using c++?

I need to create a c++ console application that query a site in turn to get the html page.
The site is static because when I queried it in the url I see the html file, so I use this code:
send(Socket, "GET /it/ricette/q-torte_forno_statico.html HTTP/1.1\r\nHost: worldrecipes.expo2015.org/\r\nConnection: close\r\n\r\n", strlen("GET /it/ricette/q-torte_forno_statico.html HTTP/1.1\r\nHost: worldrecipes.expo2015.org\r\nConnection: close\r\n\r\n"), 0);
char buffer[1000000];
int nDataLength;
while ((nDataLength = recv(Socket, buffer, 1000000, 0)) > 0) {
int i = 0;
while (buffer[i] >= 32 || buffer[i] == '\n' || buffer[i] == '\r') {
cout << buffer[i];
i += 1;
}
}
It doesn't give me any errors but don't show me the whole html page and every time that I call send the request I get different answers ... why?
This code below seems to only work on the index page worldrecipes.expo2015.org and not the sub pages. You might want to look at more advanced webbrowser controls for visual studio for parsing and processing HTML.
Like here : http://www.codeproject.com/Articles/3365/Embed-an-HTML-control-in-your-own-window-using-pla
here : https://msdn.microsoft.com/en-us/library/aa752046(v=vs.85).aspx
and here : http://www.codeproject.com/Articles/3919/Using-the-WebBrowser-control-simplified
Example code:
#include <windows.h>
#include <string>
#include <stdio.h>
using std::string;
#pragma comment(lib,"ws2_32.lib")
HINSTANCE hInst;
WSADATA wsaData;
void mParseUrl(char *mUrl, string &serverName, string &filepath, string &filename);
SOCKET connectToServer(char *szServerName, WORD portNum);
int getHeaderLength(char *content);
char *readUrl2(char *szUrl, long &bytesReturnedOut, char **headerOut);
int main()
{
const int bufLen = 1024;
char *szUrl = "http://worldrecipes.expo2015.org/it/ricette/q-torte_forno_statico.html";
long fileSize;
char *memBuffer, *headerBuffer;
FILE *fp;
memBuffer = headerBuffer = NULL;
if ( WSAStartup(0x101, &wsaData) != 0)
return -1;
memBuffer = readUrl2(szUrl, fileSize, &headerBuffer);
printf("returned from readUrl\n");
printf("data returned:\n%s", memBuffer);
if (fileSize != 0)
{
printf("Got some data\n");
fp = fopen("downloaded.file", "wb");
fwrite(memBuffer, 1, fileSize, fp);
fclose(fp);
// SetDlgItemText(hwndDlg, IDC_EDIT4, headerBuffer);
// SetDlgItemText(hwndDlg, IDC_EDIT5, memBuffer);
delete(memBuffer);
delete(headerBuffer);
}
WSACleanup();
return 0;
}
void mParseUrl(char *mUrl, string &serverName, string &filepath, string &filename)
{
string::size_type n;
string url = mUrl;
if (url.substr(0,7) == "http://")
url.erase(0,7);
if (url.substr(0,8) == "https://")
url.erase(0,8);
n = url.find('/');
if (n != string::npos)
{
serverName = url.substr(0,n);
filepath = url.substr(n);
n = filepath.rfind('/');
filename = filepath.substr(n+1);
}
else
{
serverName = url;
filepath = "/";
filename = "";
}
}
SOCKET connectToServer(char *szServerName, WORD portNum)
{
struct hostent *hp;
unsigned int addr;
struct sockaddr_in server;
SOCKET conn;
conn = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (conn == INVALID_SOCKET)
return NULL;
if(inet_addr(szServerName)==INADDR_NONE)
{
hp=gethostbyname(szServerName);
}
else
{
addr=inet_addr(szServerName);
hp=gethostbyaddr((char*)&addr,sizeof(addr),AF_INET);
}
if(hp==NULL)
{
closesocket(conn);
return NULL;
}
server.sin_addr.s_addr=*((unsigned long*)hp->h_addr);
server.sin_family=AF_INET;
server.sin_port=htons(portNum);
if(connect(conn,(struct sockaddr*)&server,sizeof(server)))
{
closesocket(conn);
return NULL;
}
return conn;
}
int getHeaderLength(char *content)
{
const char *srchStr1 = "\r\n\r\n", *srchStr2 = "\n\r\n\r";
char *findPos;
int ofset = -1;
findPos = strstr(content, srchStr1);
if (findPos != NULL)
{
ofset = findPos - content;
ofset += strlen(srchStr1);
}
else
{
findPos = strstr(content, srchStr2);
if (findPos != NULL)
{
ofset = findPos - content;
ofset += strlen(srchStr2);
}
}
return ofset;
}
char *readUrl2(char *szUrl, long &bytesReturnedOut, char **headerOut)
{
const int bufSize = 512;
char readBuffer[bufSize], sendBuffer[bufSize], tmpBuffer[bufSize];
char *tmpResult=NULL, *result;
SOCKET conn;
string server, filepath, filename;
long totalBytesRead, thisReadSize, headerLen;
mParseUrl(szUrl, server, filepath, filename);
///////////// step 1, connect //////////////////////
conn = connectToServer((char*)server.c_str(), 80);
///////////// step 2, send GET request /////////////
sprintf(tmpBuffer, "GET %s HTTP/1.0", filepath.c_str());
strcpy(sendBuffer, tmpBuffer);
strcat(sendBuffer, "\r\n");
sprintf(tmpBuffer, "Host: %s", server.c_str());
strcat(sendBuffer, tmpBuffer);
strcat(sendBuffer, "\r\n");
strcat(sendBuffer, "\r\n");
send(conn, sendBuffer, strlen(sendBuffer), 0);
// SetWindowText(edit3Hwnd, sendBuffer);
printf("Buffer being sent:\n%s", sendBuffer);
///////////// step 3 - get received bytes ////////////////
// Receive until the peer closes the connection
totalBytesRead = 0;
while(1)
{
memset(readBuffer, 0, bufSize);
thisReadSize = recv (conn, readBuffer, bufSize, 0);
if ( thisReadSize <= 0 )
break;
tmpResult = (char*)realloc(tmpResult, thisReadSize+totalBytesRead);
memcpy(tmpResult+totalBytesRead, readBuffer, thisReadSize);
totalBytesRead += thisReadSize;
}
headerLen = getHeaderLength(tmpResult);
long contenLen = totalBytesRead-headerLen;
result = new char[contenLen+1];
memcpy(result, tmpResult+headerLen, contenLen);
result[contenLen] = 0x0;
char *myTmp;
myTmp = new char[headerLen+1];
strncpy(myTmp, tmpResult, headerLen);
myTmp[headerLen] = NULL;
delete(tmpResult);
*headerOut = myTmp;
bytesReturnedOut = contenLen;
closesocket(conn);
return(result);
}

Segfault submitting query

I'm testing a wrapper library for the MySQL C API, and I'm trying to insert a row.
I've tested it in GDB, and the line (in my code) that faults appears as follows:
Breakpoint 1, cq_query (con=0x7fffffffe1c0,
query=0x6014a0 "INSERT INTO TaskType(state,parentID,displayName) VALUES(1,19,'boop')") at cqstatic.c:32
32 return mysql_query(con->con, query);
This query string is formatted correctly (I can paste it into the mysql command line, and it runs fine), so it would appear that something is wrong in the connection (con->con is of type void * cast to MYSQL *).
My other functions which do SELECT and UPDATE work fine. Only insert appears to be broken.
Here is my Test Code
#include <stdio.h>
#include <string.h>
#include <cquel.h>
int main(void)
{
struct dbconn con = cq_new_connection("myurl.tld",
"myuser", "mypasswd", "mydb");
cq_init(1024, 128);
char *fields[] = {
"state",
"parentID",
"displayName"
};
char *vals[] = {
"1",
"19",
"boop"
};
struct drow *row = cq_new_drow(3);
cq_drow_set(row, vals);
struct dlist *list = cq_new_dlist(3, fields, "");
cq_dlist_add(list, row);
cq_insert(con, "TaskType", list);
cq_free_dlist(list);
return 0;
}
Insert Function
int cq_insert(struct dbconn con, const char *table, const struct dlist *list)
{
int rc;
char *query, *columns, *values;
const char *fmt = "INSERT INTO %s(%s) VALUES(%s)";
if (table == NULL)
return 1;
if (list == NULL)
return 2;
query = calloc(CQ_QLEN, sizeof(char));
if (query == NULL)
return -1;
columns = calloc(CQ_QLEN/2, sizeof(char));
if (columns == NULL) {
free(query);
return -2;
}
values = calloc(CQ_QLEN/2, sizeof(char));
if (values == NULL) {
free(query);
free(columns);
return -3;
}
rc = cq_dlist_fields_to_utf8(&con, columns, CQ_QLEN/2, *list);
if (rc) {
free(query);
free(columns);
free(values);
return 100;
}
rc = cq_connect(&con);
if (rc) {
free(query);
free(columns);
free(values);
return 200;
}
for (struct drow *r = list->first; r != NULL; r = r->next) {
rc = cq_drow_to_utf8(&con, values, CQ_QLEN/2, *r);
if (rc)
break;
rc = snprintf(query, CQ_QLEN, fmt, table, columns, values);
if (CQ_QLEN <= (size_t) rc) {
rc = -4;
break;
}
rc = cq_query(&con, query);
if (rc) {
rc = 201;
break;
}
}
cq_close_connection(&con);
free(query);
free(columns);
free(values);
return rc;
}
And one of the important helper functions
int cq_fields_to_utf8(struct dbconn *con, char *buf, size_t buflen,
size_t fieldc, char * const *fieldnames, bool usequotes)
{
int rc = 0;
size_t num_left = fieldc, written = 0;
if (num_left == 0)
return 1;
char *temp = calloc(CQ_FMAXLEN+3, sizeof(char));
if (NULL == temp)
return -1;
char *field = calloc((CQ_FMAXLEN+3)*2+1, sizeof(char));
if (NULL == field) {
free(temp);
return -2;
}
/* prevent appending to buffer */
buf[0] = '\0';
cq_connect(con);
for (size_t i = 0; i < fieldc; ++i) {
bool escaped = fieldnames[i][0] == '\\';
const char *orig = escaped ? &fieldnames[i][1] : fieldnames[i];
const char *value;
bool isstr = false;
if (!escaped) {
mysql_real_escape_string(con->con, field, orig, strlen(orig));
value = field;
if (usequotes)
for (size_t j = 0; j < strlen(value); ++j) {
if (!isdigit(value[j])) {
isstr = true;
break;
}
}
} else {
value = orig;
}
const char *a = isstr ? "'" : "";
const char *c = --num_left > 0 ? "," : "";
written += snprintf(temp, CQ_FMAXLEN+3, "%s%s%s%s", a, value, a, c);
if (written >= buflen) {
rc = 2;
break;
}
strcat(buf, temp);
}
cq_close_connection(con);
free(field);
free(temp);
return rc;
}
used when setting up the query string.
Found my own issue. Blame the 10 minute rule.
I didn't look closely enough at cq_insert(), and it is making a double connection, closing the second one, leaving the first one lost, and the pointer pointing to freed memory, so segfault.

Printing records alternative way

Is there a way to directly display the content of a query in Mysql using C?
What I mean is:
through mysql shell if I type : SELECT * FROM table_name; I get the query result in a neat and formatted way.
If I want to do the same thing using Api C I have to write several lines of codes and the final result is far from being nice (at least this is my personal experience )
For example :
void display_Table1(MYSQL *conn)
{
int jj,ii;
char query[512];
sprintf(query, "SELECT * FROM Table1 ;");
if (mysql_query (conn, query)) {
printf("\nErrore query:\n");
printf("%s", mysql_error(conn),"\n");
result = mysql_store_result(conn);
if (result) {
num_rows = mysql_num_rows(result);
num_fields =mysql_num_fields(result);
//printf("Number of rows=%u Number of fields=%d \n", num_rows,num_fields);
//printf(" ");
}
else
{
printf("Result set is empty");
}
// Print column headers
fields = mysql_fetch_fields(result);
for(jj=0; jj < num_fields; jj++)
{
printf("\n%s\t\t",fields[jj].name);
}
printf("\n\t ");
// print query results
while(row = mysql_fetch_row(result)) // row pointer in the result set
{
for(ii=0; ii < num_fields; ii++)
{
printf("%s\t", row[ii] ? row[ii] : "NULL"); // Not NULL then print
}
printf("\n");
}
if(result)
{
mysql_free_result(result);
result = NULL;
}
}
}
That's a knotty problem to solve. I get headers one after the other in a vertical way.
I also get
Commands out of sync; you can't run this command now
Firstly, there is no direct way to print out a formatted display. What you can do, is use
MYSQL_FIELD *field = mysql_fetch_field (resultset);
col_len = field->max_length;
if(col_len < strlen(field->name))
col_len = strlen(field->name);
to find out the maximum width of a column, and the space the data accordingly.