521 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			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: 2020-01-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");
 | 
						|
}
 |