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");
 | |
| }
 | 
