MaxScale/server/core/resultset.c
2017-02-14 21:42:28 +02:00

521 lines
12 KiB
C

/*
* Copyright (c) 2016 MariaDB Corporation Ab
*
* Use of this software is governed by the Business Source License included
* in the LICENSE.TXT file and at www.mariadb.com/bsl11.
*
* Change Date: 2019-07-01
*
* On the date above, in accordance with the Business Source License, use
* of this software will be governed by version 2 or later of the General
* Public License.
*/
/**
* @file resultset.c - Implementation of a generic result set mechanism
*
* @verbatim
* Revision History
*
* Date Who Description
* 17/02/15 Mark Riddoch Initial implementation
*
* @endverbatim
*/
#include <string.h>
#include <ctype.h>
#include <maxscale/alloc.h>
#include <maxscale/resultset.h>
#include <maxscale/buffer.h>
#include <maxscale/dcb.h>
static int mysql_send_fieldcount(DCB *, int);
static int mysql_send_columndef(DCB *, const char *, int, int, uint8_t);
static int mysql_send_eof(DCB *, int);
static int mysql_send_row(DCB *, RESULT_ROW *, int);
/**
* Create a generic result set
*
* @param func Function to call for each row
* @param data Data to pass to the row retrieval function
* @return An empty resultset or NULL on error
*/
RESULTSET *
resultset_create(RESULT_ROW_CB func, void *data)
{
RESULTSET *rval = (RESULTSET *)MXS_MALLOC(sizeof(RESULTSET));
if (rval)
{
rval->n_cols = 0;
rval->column = NULL;
rval->userdata = data;
rval->fetchrow = func;
}
return rval;
}
/**
* Free a previously allocated resultset
*
* @param resultset The result set to free
*/
void
resultset_free(RESULTSET *resultset)
{
RESULT_COLUMN *col;
if (resultset != NULL)
{
col = resultset->column;
while (col)
{
RESULT_COLUMN *next;
next = col->next;
resultset_column_free(col);
col = next;
}
MXS_FREE(resultset);
}
}
/**
* Add a new column to a result set. Columns are added to the right
* of the result set, i.e. the existing order is maintained.
*
* @param set The result set
* @param name The column name
* @param len The column length
* @param type The column type
* @return The numebr of columns added to the result set
*/
int
resultset_add_column(RESULTSET *set, const char *cname, int len, RESULT_COL_TYPE type)
{
char *name = MXS_STRDUP(cname);
RESULT_COLUMN *newcol = (RESULT_COLUMN *)MXS_MALLOC(sizeof(RESULT_COLUMN));
if (!name || !newcol)
{
MXS_FREE(name);
MXS_FREE(newcol);
return 0;
}
newcol->name = name;
newcol->type = type;
newcol->len = len;
newcol->next = NULL;
if (set->column == NULL)
{
set->column = newcol;
}
else
{
RESULT_COLUMN *ptr = set->column;
while (ptr->next)
{
ptr = ptr->next;
}
ptr->next = newcol;
}
set->n_cols++;
return 1;
}
/**
* Free a result set column
*
* @param col Column to free
*/
void
resultset_column_free(RESULT_COLUMN *col)
{
MXS_FREE(col->name);
MXS_FREE(col);
}
/**
* Create a blank row, a row with all values NULL, for a result
* set.
*
* @param set The result set the row will be part of
* @return The NULL result set row
*/
RESULT_ROW *
resultset_make_row(RESULTSET *set)
{
RESULT_ROW *row;
int i;
if ((row = (RESULT_ROW *)MXS_MALLOC(sizeof(RESULT_ROW))) == NULL)
{
return NULL;
}
row->n_cols = set->n_cols;
if ((row->cols = (char **)MXS_MALLOC(row->n_cols * sizeof(char *))) == NULL)
{
MXS_FREE(row);
return NULL;
}
for (i = 0; i < set->n_cols; i++)
{
row->cols[i] = NULL;
}
return row;
}
/**
* Free a result set row. If a column in the row has a non-null values
* then the data is assumed to be a malloc'd pointer and will be free'd.
* If any value is not a malloc'd pointer it should be removed before
* making this call.
*
* @param row The row to free
*/
void
resultset_free_row(RESULT_ROW *row)
{
int i;
for (i = 0; i < row->n_cols; i++)
{
if (row->cols[i])
{
MXS_FREE(row->cols[i]);
}
}
MXS_FREE(row->cols);
MXS_FREE(row);
}
/**
* Add a value in a particular column of the row . The value is
* a NULL terminated string and will be copied into malloc'd
* storage by this routine.
*
* @param row The row ro add the column into
* @param col The column number (0 to n_cols - 1)
* @param value The column value, may be NULL
* @return The number of columns inserted
*/
int
resultset_row_set(RESULT_ROW *row, int col, const char *value)
{
if (col < 0 || col >= row->n_cols)
{
return 0;
}
if (value)
{
if ((row->cols[col] = MXS_STRDUP(value)) == NULL)
{
return 0;
}
return 1;
}
else if (row->cols[col])
{
MXS_FREE(row->cols[col]);
}
row->cols[col] = NULL;
return 1;
}
/**
* Stream a result set using the MySQL protocol for encodign the result
* set. Each row is retrieved by calling the function passed in the
* argument list.
*
* @param set The result set to stream
* @param dcb The connection to stream the result set to
*/
void
resultset_stream_mysql(RESULTSET *set, DCB *dcb)
{
RESULT_COLUMN *col;
RESULT_ROW *row;
uint8_t seqno = 2;
mysql_send_fieldcount(dcb, set->n_cols);
col = set->column;
while (col)
{
mysql_send_columndef(dcb, col->name, col->type, col->len, seqno++);
col = col->next;
}
mysql_send_eof(dcb, seqno++);
while ((row = (*set->fetchrow)(set, set->userdata)) != NULL)
{
mysql_send_row(dcb, row, seqno++);
resultset_free_row(row);
}
mysql_send_eof(dcb, seqno);
}
/**
* Send the field count packet in a response packet sequence.
*
* @param dcb DCB of connection to send result set to
* @param count Number of columns in the result set
* @return Non-zero on success
*/
static int
mysql_send_fieldcount(DCB *dcb, int count)
{
GWBUF *pkt;
uint8_t *ptr;
if ((pkt = gwbuf_alloc(5)) == NULL)
{
return 0;
}
ptr = GWBUF_DATA(pkt);
*ptr++ = 0x01; // Payload length
*ptr++ = 0x00;
*ptr++ = 0x00;
*ptr++ = 0x01; // Sequence number in response
*ptr++ = count; // Length of result string
return dcb->func.write(dcb, pkt);
}
/**
* Send the column definition packet in a response packet sequence.
*
* @param dcb The DCB of the connection
* @param name Name of the column
* @param type Column type
* @param len Column length
* @param seqno Packet sequence number
* @return Non-zero on success
*/
static int
mysql_send_columndef(DCB *dcb, const char *name, int type, int len, uint8_t seqno)
{
GWBUF *pkt;
uint8_t *ptr;
int plen;
if ((pkt = gwbuf_alloc(26 + strlen(name))) == NULL)
{
return 0;
}
ptr = GWBUF_DATA(pkt);
plen = 22 + strlen(name);
*ptr++ = plen & 0xff;
*ptr++ = (plen >> 8) & 0xff;
*ptr++ = (plen >> 16) & 0xff;
*ptr++ = seqno; // Sequence number in response
*ptr++ = 3; // Catalog is always def
*ptr++ = 'd';
*ptr++ = 'e';
*ptr++ = 'f';
*ptr++ = 0; // Schema name length
*ptr++ = 0; // virtual table name length
*ptr++ = 0; // Table name length
*ptr++ = strlen(name); // Column name length;
while (*name)
{
*ptr++ = *name++; // Copy the column name
}
*ptr++ = 0; // Orginal column name
*ptr++ = 0x0c; // Length of next fields always 12
*ptr++ = 0x3f; // Character set
*ptr++ = 0;
*ptr++ = len & 0xff; // Length of column
*ptr++ = (len >> 8) & 0xff;
*ptr++ = (len >> 16) & 0xff;
*ptr++ = (len >> 24) & 0xff;
*ptr++ = type;
*ptr++ = 0x81; // Two bytes of flags
if (type == 0xfd)
{
*ptr++ = 0x1f;
}
else
{
*ptr++ = 0x00;
}
*ptr++ = 0;
*ptr++ = 0;
*ptr++ = 0;
return dcb->func.write(dcb, pkt);
}
/**
* Send an EOF packet in a response packet sequence.
*
* @param dcb The client connection
* @param seqno The sequence number of the EOF packet
* @return Non-zero on success
*/
static int
mysql_send_eof(DCB *dcb, int seqno)
{
GWBUF *pkt;
uint8_t *ptr;
if ((pkt = gwbuf_alloc(9)) == NULL)
{
return 0;
}
ptr = GWBUF_DATA(pkt);
*ptr++ = 0x05;
*ptr++ = 0x00;
*ptr++ = 0x00;
*ptr++ = seqno; // Sequence number in response
*ptr++ = 0xfe; // Length of result string
*ptr++ = 0x00; // No Errors
*ptr++ = 0x00;
*ptr++ = 0x02; // Autocommit enabled
*ptr++ = 0x00;
return dcb->func.write(dcb, pkt);
}
/**
* Send a row packet in a response packet sequence.
*
* @param dcb The client connection
* @param row The row to send
* @param seqno The sequence number of the EOF packet
* @return Non-zero on success
*/
static int
mysql_send_row(DCB *dcb, RESULT_ROW *row, int seqno)
{
GWBUF *pkt;
int i, len = 4;
uint8_t *ptr;
for (i = 0; i < row->n_cols; i++)
{
if (row->cols[i])
{
len += strlen(row->cols[i]);
}
len++;
}
if ((pkt = gwbuf_alloc(len)) == NULL)
{
return 0;
}
ptr = GWBUF_DATA(pkt);
len -= 4;
*ptr++ = len & 0xff;
*ptr++ = (len >> 8) & 0xff;
*ptr++ = (len >> 16) & 0xff;
*ptr++ = seqno;
for (i = 0; i < row->n_cols; i++)
{
if (row->cols[i])
{
len = strlen(row->cols[i]);
*ptr++ = len;
memcpy(ptr, row->cols[i], len);
ptr += len;
}
else
{
*ptr++ = 0; // NULL column
}
}
return dcb->func.write(dcb, pkt);
}
/**
* Return true if the string only contains numerics
*
* @param value String to test
* @return Non-zero if the string is made of of numeric values
*/
static int
value_is_numeric(const char *value)
{
int rval = 0;
if (*value)
{
rval = 1;
while (*value)
{
if (!isdigit(*value))
{
return 0;
}
value++;
}
}
return rval;
}
/**
* Stream a result set encoding it as a JSON object
* Each row is retrieved by calling the function passed in the
* argument list.
*
* @param set The result set to stream
* @param dcb The connection to stream the result set to
*/
void
resultset_stream_json(RESULTSET *set, DCB *dcb)
{
RESULT_COLUMN *col;
RESULT_ROW *row;
int rowno = 0;
dcb_printf(dcb, "[ ");
while ((row = (*set->fetchrow)(set, set->userdata)) != NULL)
{
int i = 0;
if (rowno++ > 0)
{
dcb_printf(dcb, ",\n");
}
dcb_printf(dcb, "{ ");
col = set->column;
while (col)
{
dcb_printf(dcb, "\"%s\" : ", col->name);
if (row->cols[i])
{
if (value_is_numeric(row->cols[i]))
{
dcb_printf(dcb, "%s", row->cols[i]);
}
else
{
dcb_printf(dcb, "\"%s\"", row->cols[i]);
}
}
else
{
dcb_printf(dcb, "null");
}
i++;
col = col->next;
if (col)
{
dcb_printf(dcb, ", ");
}
}
resultset_free_row(row);
dcb_printf(dcb, "}");
}
dcb_printf(dcb, "]\n");
}