Addition of the MaxInfo filter and the generic MaxScale resultset
This commit is contained in:
@ -7,7 +7,7 @@ add_executable(maxscale atomic.c buffer.c spinlock.c gateway.c
|
||||
gw_utils.c utils.c dcb.c load_utils.c session.c service.c server.c
|
||||
poll.c config.c users.c hashtable.c dbusers.c thread.c gwbitmask.c
|
||||
monitor.c adminusers.c secrets.c filter.c modutil.c hint.c
|
||||
housekeeper.c memlog.c)
|
||||
housekeeper.c memlog.c resultset.c)
|
||||
target_link_libraries(maxscale ${EMBEDDED_LIB} log_manager utils ssl aio pthread crypt dl crypto inih z rt m stdc++)
|
||||
install(TARGETS maxscale DESTINATION bin)
|
||||
|
||||
|
@ -802,7 +802,8 @@ int dcb_read(
|
||||
|
||||
if (r <= 0 &&
|
||||
l_errno != EAGAIN &&
|
||||
l_errno != EWOULDBLOCK)
|
||||
l_errno != EWOULDBLOCK &&
|
||||
l_errno != 0)
|
||||
{
|
||||
n = -1;
|
||||
goto return_n;
|
||||
|
@ -79,6 +79,8 @@
|
||||
# define _GNU_SOURCE
|
||||
#endif
|
||||
|
||||
time_t MaxScaleStarted;
|
||||
|
||||
extern char *program_invocation_name;
|
||||
extern char *program_invocation_short_name;
|
||||
|
||||
@ -1796,6 +1798,8 @@ int main(int argc, char **argv)
|
||||
LOGIF(LM, (skygw_log_write(LOGFILE_MESSAGE,
|
||||
"MaxScale started with %d server threads.",
|
||||
config_threadcount())));
|
||||
|
||||
MaxScaleStarted = time(0);
|
||||
/*<
|
||||
* Serve clients.
|
||||
*/
|
||||
@ -1950,3 +1954,9 @@ static int write_pid_file(char *home_dir) {
|
||||
/* success */
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
MaxScaleUptime()
|
||||
{
|
||||
return time(0) - MaxScaleStarted;
|
||||
}
|
||||
|
403
server/core/resultset.c
Normal file
403
server/core/resultset.c
Normal file
@ -0,0 +1,403 @@
|
||||
/*
|
||||
* This file is distributed as part of the MariaDB Corporation MaxScale. It is free
|
||||
* software: you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation,
|
||||
* version 2.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program; if not, write to the Free Software Foundation, Inc., 51
|
||||
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Copyright MariaDB Corporation Ab 2013-2014
|
||||
*/
|
||||
|
||||
/**
|
||||
* @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 <resultset.h>
|
||||
#include <buffer.h>
|
||||
#include <dcb.h>
|
||||
|
||||
|
||||
static int mysql_send_fieldcount(DCB *, int);
|
||||
static int mysql_send_columndef(DCB *, 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;
|
||||
|
||||
if ((rval = (RESULTSET *)malloc(sizeof(RESULTSET))) != NULL)
|
||||
{
|
||||
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)
|
||||
return;
|
||||
col = resultset->column;
|
||||
while (col)
|
||||
{
|
||||
RESULT_COLUMN *next;
|
||||
|
||||
next = col->next;
|
||||
resultset_column_free(col);
|
||||
col = next;
|
||||
}
|
||||
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, char *name, int len, RESULT_COL_TYPE type)
|
||||
{
|
||||
RESULT_COLUMN *newcol, *ptr;
|
||||
|
||||
if ((newcol = (RESULT_COLUMN *)malloc(sizeof(RESULT_COLUMN))) == NULL)
|
||||
return 0;
|
||||
if ((newcol->name = strdup(name)) == NULL)
|
||||
{
|
||||
free(newcol);
|
||||
return 0;
|
||||
}
|
||||
newcol->type = type;
|
||||
newcol->len = len;
|
||||
newcol->next = NULL;
|
||||
|
||||
if (set->column == NULL)
|
||||
set->column = newcol;
|
||||
else
|
||||
{
|
||||
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)
|
||||
{
|
||||
free(col->name);
|
||||
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 *)malloc(sizeof(RESULT_ROW))) == NULL)
|
||||
return NULL;
|
||||
row->n_cols = set->n_cols;
|
||||
if ((row->cols = (char **)malloc(sizeof(char *))) == NULL)
|
||||
{
|
||||
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])
|
||||
free(row->cols[i]);
|
||||
free(row->cols);
|
||||
free(row);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a value in a partcular 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, char *value)
|
||||
{
|
||||
if (col < 0 || col >= row->n_cols)
|
||||
return 0;
|
||||
if (value)
|
||||
{
|
||||
if ((row->cols[col] = strdup(value)) == NULL)
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
else if (row->cols[col])
|
||||
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, 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;
|
||||
strncpy((char *)ptr, row->cols[i], len);
|
||||
ptr += len;
|
||||
}
|
||||
else
|
||||
{
|
||||
*ptr++ = 0; // NULL column
|
||||
}
|
||||
}
|
||||
|
||||
return dcb->func.write(dcb, pkt);
|
||||
}
|
@ -1511,3 +1511,25 @@ void service_shutdown()
|
||||
}
|
||||
spinlock_release(&service_spin);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the count of all sessions active for all services
|
||||
*
|
||||
* @return Count of all active sessions
|
||||
*/
|
||||
int
|
||||
serviceSessionCountAll()
|
||||
{
|
||||
SERVICE *ptr;
|
||||
int rval = 0;
|
||||
|
||||
spinlock_acquire(&service_spin);
|
||||
ptr = allServices;
|
||||
while (ptr)
|
||||
{
|
||||
rval += ptr->stats.n_current;
|
||||
ptr = ptr->next;
|
||||
}
|
||||
spinlock_release(&service_spin);
|
||||
return rval;
|
||||
}
|
||||
|
87
server/include/resultset.h
Normal file
87
server/include/resultset.h
Normal file
@ -0,0 +1,87 @@
|
||||
#ifndef _RESULTSET_H
|
||||
#define _RESULTSET_H
|
||||
/*
|
||||
* This file is distributed as part of the MariaDB Corporation MaxScale. It is free
|
||||
* software: you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation,
|
||||
* version 2.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program; if not, write to the Free Software Foundation, Inc., 51
|
||||
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Copyright MariaDB Corporation Ab 2013-2014
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file resultset.h The MaxScale generic result set mechanism
|
||||
*
|
||||
* @verbatim
|
||||
* Revision History
|
||||
*
|
||||
* Date Who Description
|
||||
* 17/02/15 Mark Riddoch Initial implementation
|
||||
*
|
||||
* @endverbatim
|
||||
*/
|
||||
#include <dcb.h>
|
||||
|
||||
|
||||
/**
|
||||
* Column types
|
||||
*/
|
||||
typedef enum {
|
||||
COL_TYPE_VARCHAR = 0x0f,
|
||||
COL_TYPE_VARSTRING = 0xfd
|
||||
} RESULT_COL_TYPE;
|
||||
|
||||
/**
|
||||
* The result set column definition. Each result set has an order linked
|
||||
* list of column definitions.
|
||||
*/
|
||||
typedef struct resultcolumn {
|
||||
char *name; /*< Column name */
|
||||
int len; /*< Column length */
|
||||
RESULT_COL_TYPE type; /*< Column type */
|
||||
struct resultcolumn *next; /*< next column */
|
||||
} RESULT_COLUMN;
|
||||
|
||||
/**
|
||||
* A representation of a row within a result set.
|
||||
*/
|
||||
typedef struct resultrow {
|
||||
int n_cols; /*< No. of columns in row */
|
||||
char **cols; /*< The columns themselves */
|
||||
} RESULT_ROW;
|
||||
|
||||
struct resultset;
|
||||
|
||||
/**
|
||||
* Type of callback function used to supply each row
|
||||
*/
|
||||
typedef RESULT_ROW * (*RESULT_ROW_CB)(struct resultset *, void *);
|
||||
|
||||
/**
|
||||
* The representation of the result set itself.
|
||||
*/
|
||||
typedef struct resultset {
|
||||
int n_cols; /*< No. of columns */
|
||||
RESULT_COLUMN *column; /*< Linked list of column definitions */
|
||||
RESULT_ROW_CB fetchrow; /*< Fetch a row for the result set */
|
||||
void *userdata; /*< User data for the fetch row call */
|
||||
} RESULTSET;
|
||||
|
||||
extern RESULTSET *resultset_create(RESULT_ROW_CB, void *);
|
||||
extern void resultset_free(RESULTSET *);
|
||||
extern int resultset_add_column(RESULTSET *, char *, int, RESULT_COL_TYPE);
|
||||
extern void resultset_column_free(RESULT_COLUMN *);
|
||||
extern RESULT_ROW *resultset_make_row(RESULTSET *);
|
||||
extern void resultset_free_row(RESULT_ROW *);
|
||||
extern int resultset_row_set(RESULT_ROW *, int, char *);
|
||||
extern void resultset_stream_mysql(RESULTSET *, DCB *);
|
||||
#endif
|
@ -193,4 +193,5 @@ extern void dListServices(DCB *);
|
||||
extern void dListListeners(DCB *);
|
||||
char* service_get_name(SERVICE* svc);
|
||||
void service_shutdown();
|
||||
extern int serviceSessionCountAll();
|
||||
#endif
|
||||
|
131
server/modules/include/maxinfo.h
Normal file
131
server/modules/include/maxinfo.h
Normal file
@ -0,0 +1,131 @@
|
||||
#ifndef _MAXINFO_H
|
||||
#define _MAXINFO_H
|
||||
/*
|
||||
* This file is distributed as part of the MariaDB Corporation MaxScale. It is free
|
||||
* software: you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation,
|
||||
* version 2.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program; if not, write to the Free Software Foundation, Inc., 51
|
||||
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Copyright MariaDB Corporation Ab 2013-2014
|
||||
*/
|
||||
#include <service.h>
|
||||
#include <session.h>
|
||||
#include <spinlock.h>
|
||||
|
||||
/**
|
||||
* @file maxinfo.h The MaxScale information schema provider
|
||||
*
|
||||
* @verbatim
|
||||
* Revision History
|
||||
*
|
||||
* Date Who Description
|
||||
* 16/02/15 Mark Riddoch Initial implementation
|
||||
*
|
||||
* @endverbatim
|
||||
*/
|
||||
struct maxinfo_session;
|
||||
|
||||
/**
|
||||
* The INFO_INSTANCE structure. There is one instane of the maxinfo "router" for
|
||||
* each service that uses the MaxScale information schema.
|
||||
*/
|
||||
typedef struct maxinfo_instance {
|
||||
SPINLOCK lock; /*< The instance spinlock */
|
||||
SERVICE *service; /*< The debug cli service */
|
||||
struct maxinfo_session
|
||||
*sessions; /*< Linked list of sessions within this instance */
|
||||
struct maxinfo_instance
|
||||
*next; /*< The next pointer for the list of instances */
|
||||
} INFO_INSTANCE;
|
||||
|
||||
/**
|
||||
* The INFO_SESSION structure. As INFO_SESSION is created for each user that logs into
|
||||
* the MaxScale information schema.
|
||||
*/
|
||||
|
||||
typedef struct maxinfo_session {
|
||||
SESSION *session; /*< The MaxScale session */
|
||||
DCB *dcb; /*< DCB of the client side */
|
||||
GWBUF *queue; /*< Queue for building contiguous requests */
|
||||
struct maxinfo_session
|
||||
*next; /*< The next pointer for the list of sessions */
|
||||
} INFO_SESSION;
|
||||
|
||||
/**
|
||||
* The operators that can be in the parse tree
|
||||
*/
|
||||
typedef enum
|
||||
{
|
||||
MAXOP_SHOW,
|
||||
MAXOP_SELECT,
|
||||
MAXOP_TABLE,
|
||||
MAXOP_COLUMNS,
|
||||
MAXOP_ALL_COLUMNS,
|
||||
MAXOP_LITERAL,
|
||||
MAXOP_PREDICATE,
|
||||
MAXOP_LIKE,
|
||||
MAXOP_EQUAL
|
||||
} MAXINFO_OPERATOR;
|
||||
|
||||
/**
|
||||
* The Parse tree nodes for the MaxInfo parser
|
||||
*/
|
||||
typedef struct maxinfo_tree {
|
||||
MAXINFO_OPERATOR op; /*< The operator */
|
||||
char *value; /*< The value */
|
||||
struct maxinfo_tree *left; /*< The left hand side of the operator */
|
||||
struct maxinfo_tree *right; /*< The right hand side of the operator */
|
||||
} MAXINFO_TREE;
|
||||
|
||||
|
||||
|
||||
#define MYSQL_COMMAND(buf) (*((uint8_t *)GWBUF_DATA(buf) + 4))
|
||||
/**
|
||||
* MySQL protocol OpCodes needed for replication
|
||||
*/
|
||||
#define COM_QUIT 0x01
|
||||
#define COM_QUERY 0x03
|
||||
#define COM_STATISTICS 0x09
|
||||
#define COM_PING 0x0e
|
||||
|
||||
/**
|
||||
* Token values for the tokeniser used by the parser for maxinfo
|
||||
*/
|
||||
#define LT_STRING 1
|
||||
#define LT_SHOW 2
|
||||
#define LT_LIKE 3
|
||||
#define LT_SELECT 4
|
||||
#define LT_EQUAL 5
|
||||
#define LT_COMMA 6
|
||||
#define LT_FROM 7
|
||||
#define LT_STAR 8
|
||||
#define LT_VARIABLE 9
|
||||
|
||||
|
||||
/**
|
||||
* Possible parse errors
|
||||
*/
|
||||
typedef enum {
|
||||
PARSE_NOERROR,
|
||||
PARSE_MALFORMED_SHOW,
|
||||
PARSE_EXPECTED_LIKE,
|
||||
PARSE_SYNTAX_ERROR
|
||||
} PARSE_ERROR;
|
||||
|
||||
|
||||
extern MAXINFO_TREE *maxinfo_parse(char *, PARSE_ERROR *);
|
||||
extern void maxinfo_execute(DCB *, MAXINFO_TREE *);
|
||||
extern void maxinfo_send_error(DCB *, int, char *);
|
||||
extern void maxinfo_send_parse_error(DCB *, char *, PARSE_ERROR);
|
||||
extern void maxinfo_send_error(DCB *, int, char *);
|
||||
|
||||
#endif
|
@ -22,4 +22,5 @@ add_subdirectory(readwritesplit)
|
||||
if(BUILD_BINLOG)
|
||||
add_subdirectory(binlog)
|
||||
endif()
|
||||
add_subdirectory(maxinfo)
|
||||
|
||||
|
4
server/modules/routing/maxinfo/CMakeLists.txt
Normal file
4
server/modules/routing/maxinfo/CMakeLists.txt
Normal file
@ -0,0 +1,4 @@
|
||||
add_library(maxinfo SHARED maxinfo.c maxinfo_parse.c maxinfo_error.c maxinfo_exec.c)
|
||||
set_target_properties(maxinfo PROPERTIES INSTALL_RPATH ${CMAKE_INSTALL_RPATH}:${CMAKE_INSTALL_PREFIX}/lib)
|
||||
target_link_libraries(maxinfo pthread log_manager)
|
||||
install(TARGETS maxinfo DESTINATION modules)
|
657
server/modules/routing/maxinfo/maxinfo.c
Normal file
657
server/modules/routing/maxinfo/maxinfo.c
Normal file
@ -0,0 +1,657 @@
|
||||
/*
|
||||
* This file is distributed as part of MaxScale. It is free
|
||||
* software: you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation,
|
||||
* version 2.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program; if not, write to the Free Software Foundation, Inc., 51
|
||||
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Copyright MariaDB Corporation Ab 2014
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file maxinfo.c - A "routing module" that in fact merely gives access
|
||||
* to a MaxScale information schema usign the MySQL protocol
|
||||
*
|
||||
* @verbatim
|
||||
* Revision History
|
||||
*
|
||||
* Date Who Description
|
||||
* 16/02/15 Mark Riddoch Initial implementation
|
||||
*
|
||||
* @endverbatim
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <service.h>
|
||||
#include <session.h>
|
||||
#include <router.h>
|
||||
#include <modules.h>
|
||||
#include <modinfo.h>
|
||||
#include <modutil.h>
|
||||
#include <atomic.h>
|
||||
#include <spinlock.h>
|
||||
#include <dcb.h>
|
||||
#include <poll.h>
|
||||
#include <maxinfo.h>
|
||||
#include <skygw_utils.h>
|
||||
#include <log_manager.h>
|
||||
#include <resultset.h>
|
||||
#include <version.h>
|
||||
|
||||
|
||||
MODULE_INFO info = {
|
||||
MODULE_API_ROUTER,
|
||||
MODULE_GA,
|
||||
ROUTER_VERSION,
|
||||
"The MaxScale Information Schema"
|
||||
};
|
||||
|
||||
/** Defined in log_manager.cc */
|
||||
extern int lm_enabled_logfiles_bitmask;
|
||||
extern size_t log_ses_count[];
|
||||
extern __thread log_info_t tls_log_info;
|
||||
|
||||
static char *version_str = "V1.0.0";
|
||||
|
||||
static int maxinfo_statistics(INFO_INSTANCE *, INFO_SESSION *, GWBUF *);
|
||||
static int maxinfo_ping(INFO_INSTANCE *, INFO_SESSION *, GWBUF *);
|
||||
static int maxinfo_execute_query(INFO_INSTANCE *, INFO_SESSION *, char *);
|
||||
|
||||
|
||||
/* The router entry points */
|
||||
static ROUTER *createInstance(SERVICE *service, char **options);
|
||||
static void *newSession(ROUTER *instance, SESSION *session);
|
||||
static void closeSession(ROUTER *instance, void *router_session);
|
||||
static void freeSession(ROUTER *instance, void *router_session);
|
||||
static int execute(ROUTER *instance, void *router_session, GWBUF *queue);
|
||||
static void diagnostics(ROUTER *instance, DCB *dcb);
|
||||
static uint8_t getCapabilities (ROUTER* inst, void* router_session);
|
||||
static void handleError(
|
||||
ROUTER *instance,
|
||||
void *router_session,
|
||||
GWBUF *errbuf,
|
||||
DCB *backend_dcb,
|
||||
error_action_t action,
|
||||
bool *succp);
|
||||
|
||||
/** The module object definition */
|
||||
static ROUTER_OBJECT MyObject = {
|
||||
createInstance,
|
||||
newSession,
|
||||
closeSession,
|
||||
freeSession,
|
||||
execute,
|
||||
diagnostics,
|
||||
NULL,
|
||||
handleError,
|
||||
getCapabilities
|
||||
};
|
||||
|
||||
static SPINLOCK instlock;
|
||||
static INFO_INSTANCE *instances;
|
||||
|
||||
/**
|
||||
* Implementation of the mandatory version entry point
|
||||
*
|
||||
* @return version string of the module
|
||||
*/
|
||||
char *
|
||||
version()
|
||||
{
|
||||
return version_str;
|
||||
}
|
||||
|
||||
/**
|
||||
* The module initialisation routine, called when the module
|
||||
* is first loaded.
|
||||
*/
|
||||
void
|
||||
ModuleInit()
|
||||
{
|
||||
LOGIF(LM, (skygw_log_write(
|
||||
LOGFILE_MESSAGE,
|
||||
"Initialise MaxInfo router module %s.\n",
|
||||
version_str)));
|
||||
spinlock_init(&instlock);
|
||||
instances = NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* The module entry point routine. It is this routine that
|
||||
* must populate the structure that is referred to as the
|
||||
* "module object", this is a structure with the set of
|
||||
* external entry points for this module.
|
||||
*
|
||||
* @return The module object
|
||||
*/
|
||||
ROUTER_OBJECT *
|
||||
GetModuleObject()
|
||||
{
|
||||
return &MyObject;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance of the router for a particular service
|
||||
* within the gateway.
|
||||
*
|
||||
* @param service The service this router is being create for
|
||||
* @param options Any array of options for the query router
|
||||
*
|
||||
* @return The instance data for this new instance
|
||||
*/
|
||||
static ROUTER *
|
||||
createInstance(SERVICE *service, char **options)
|
||||
{
|
||||
INFO_INSTANCE *inst;
|
||||
int i;
|
||||
|
||||
if ((inst = malloc(sizeof(INFO_INSTANCE))) == NULL)
|
||||
return NULL;
|
||||
|
||||
inst->service = service;
|
||||
spinlock_init(&inst->lock);
|
||||
|
||||
if (options)
|
||||
{
|
||||
for (i = 0; options[i]; i++)
|
||||
{
|
||||
{
|
||||
LOGIF(LE, (skygw_log_write(
|
||||
LOGFILE_ERROR,
|
||||
"Unknown option for MaxInfo '%s'\n",
|
||||
options[i])));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* We have completed the creation of the instance data, so now
|
||||
* insert this router instance into the linked list of routers
|
||||
* that have been created with this module.
|
||||
*/
|
||||
spinlock_acquire(&instlock);
|
||||
inst->next = instances;
|
||||
instances = inst;
|
||||
spinlock_release(&instlock);
|
||||
|
||||
service->users = mysql_users_alloc();
|
||||
add_mysql_users_with_host_ipv4(service->users, "massi", "%", "2CFEB4BD447B9BC5D591249377EF5A7E340D1A1D", "Y", "");
|
||||
add_mysql_users_with_host_ipv4(service->users, "massi", "localhost", "2CFEB4BD447B9BC5D591249377EF5A7E340D1A1D", "Y", "");
|
||||
add_mysql_users_with_host_ipv4(service->users, "monitor", "%", "", "Y", "");
|
||||
add_mysql_users_with_host_ipv4(service->users, "monitor", "localhost", "", "Y", "");
|
||||
|
||||
return (ROUTER *)inst;
|
||||
}
|
||||
|
||||
/**
|
||||
* Associate a new session with this instance of the router.
|
||||
*
|
||||
* @param instance The router instance data
|
||||
* @param session The session itself
|
||||
* @return Session specific data for this session
|
||||
*/
|
||||
static void *
|
||||
newSession(ROUTER *instance, SESSION *session)
|
||||
{
|
||||
INFO_INSTANCE *inst = (INFO_INSTANCE *)instance;
|
||||
INFO_SESSION *client;
|
||||
|
||||
if ((client = (INFO_SESSION *)malloc(sizeof(INFO_SESSION))) == NULL)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
client->session = session;
|
||||
client->dcb = session->client;
|
||||
client->queue = NULL;
|
||||
|
||||
spinlock_acquire(&inst->lock);
|
||||
client->next = inst->sessions;
|
||||
inst->sessions = client;
|
||||
spinlock_release(&inst->lock);
|
||||
|
||||
session->state = SESSION_STATE_READY;
|
||||
|
||||
return (void *)client;
|
||||
}
|
||||
|
||||
/**
|
||||
* Close a session with the router, this is the mechanism
|
||||
* by which a router may cleanup data structure etc.
|
||||
*
|
||||
* @param instance The router instance data
|
||||
* @param router_session The session being closed
|
||||
*/
|
||||
static void
|
||||
closeSession(ROUTER *instance, void *router_session)
|
||||
{
|
||||
INFO_INSTANCE *inst = (INFO_INSTANCE *)instance;
|
||||
INFO_SESSION *session = (INFO_SESSION *)router_session;
|
||||
|
||||
|
||||
spinlock_acquire(&inst->lock);
|
||||
if (inst->sessions == session)
|
||||
inst->sessions = session->next;
|
||||
else
|
||||
{
|
||||
INFO_SESSION *ptr = inst->sessions;
|
||||
while (ptr && ptr->next != session)
|
||||
ptr = ptr->next;
|
||||
if (ptr)
|
||||
ptr->next = session->next;
|
||||
}
|
||||
spinlock_release(&inst->lock);
|
||||
/**
|
||||
* Router session is freed in session.c:session_close, when session who
|
||||
* owns it, is freed.
|
||||
*/
|
||||
}
|
||||
|
||||
/**
|
||||
* Free a maxinfo session
|
||||
*
|
||||
* @param router_instance The router session
|
||||
* @param router_client_session The router session as returned from newSession
|
||||
*/
|
||||
static void freeSession(
|
||||
ROUTER* router_instance,
|
||||
void* router_client_session)
|
||||
{
|
||||
free(router_client_session);
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Error Handler routine
|
||||
*
|
||||
* The routine will handle errors that occurred in backend writes.
|
||||
*
|
||||
* @param instance The router instance
|
||||
* @param router_session The router session
|
||||
* @param message The error message to reply
|
||||
* @param backend_dcb The backend DCB
|
||||
* @param action The action: REPLY, REPLY_AND_CLOSE, NEW_CONNECTION
|
||||
*
|
||||
*/
|
||||
static void handleError(
|
||||
ROUTER *instance,
|
||||
void *router_session,
|
||||
GWBUF *errbuf,
|
||||
DCB *backend_dcb,
|
||||
error_action_t action,
|
||||
bool *succp)
|
||||
|
||||
{
|
||||
DCB *client_dcb;
|
||||
SESSION *session = backend_dcb->session;
|
||||
session_state_t sesstate;
|
||||
|
||||
/** Reset error handle flag from a given DCB */
|
||||
if (action == ERRACT_RESET)
|
||||
{
|
||||
backend_dcb->dcb_errhandle_called = false;
|
||||
return;
|
||||
}
|
||||
|
||||
/** Don't handle same error twice on same DCB */
|
||||
if (backend_dcb->dcb_errhandle_called)
|
||||
{
|
||||
/** we optimistically assume that previous call succeed */
|
||||
*succp = true;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
backend_dcb->dcb_errhandle_called = true;
|
||||
}
|
||||
spinlock_acquire(&session->ses_lock);
|
||||
sesstate = session->state;
|
||||
client_dcb = session->client;
|
||||
|
||||
if (sesstate == SESSION_STATE_ROUTER_READY)
|
||||
{
|
||||
CHK_DCB(client_dcb);
|
||||
spinlock_release(&session->ses_lock);
|
||||
client_dcb->func.write(client_dcb, gwbuf_clone(errbuf));
|
||||
}
|
||||
else
|
||||
{
|
||||
spinlock_release(&session->ses_lock);
|
||||
}
|
||||
|
||||
/** false because connection is not available anymore */
|
||||
*succp = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* We have data from the client, this is a SQL command, or other MySQL
|
||||
* packet type.
|
||||
*
|
||||
* @param instance The router instance
|
||||
* @param router_session The router session returned from the newSession call
|
||||
* @param queue The queue of data buffers to route
|
||||
* @return The number of bytes sent
|
||||
*/
|
||||
static int
|
||||
execute(ROUTER *rinstance, void *router_session, GWBUF *queue)
|
||||
{
|
||||
INFO_INSTANCE *instance = (INFO_INSTANCE *)rinstance;
|
||||
INFO_SESSION *session = (INFO_SESSION *)router_session;
|
||||
uint8_t *data;
|
||||
int length, len, residual;
|
||||
char *sql;
|
||||
|
||||
if (session->queue)
|
||||
{
|
||||
queue = gwbuf_append(session->queue, queue);
|
||||
session->queue = NULL;
|
||||
queue = gwbuf_make_contiguous(queue);
|
||||
}
|
||||
data = (uint8_t *)GWBUF_DATA(queue);
|
||||
length = data[0] + (data[1] << 8) + (data[2] << 16);
|
||||
if (length + 4 > GWBUF_LENGTH(queue))
|
||||
{
|
||||
// Incomplete packet, must be buffered
|
||||
session->queue = queue;
|
||||
return 1;
|
||||
}
|
||||
|
||||
// We have a complete request in a signle buffer
|
||||
if (modutil_MySQL_Query(queue, &sql, &len, &residual))
|
||||
{
|
||||
sql = strndup(sql, len);
|
||||
return maxinfo_execute_query(instance, session, sql);
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (MYSQL_COMMAND(queue))
|
||||
{
|
||||
case COM_PING:
|
||||
return maxinfo_ping(instance, session, queue);
|
||||
case COM_STATISTICS:
|
||||
return maxinfo_statistics(instance, session, queue);
|
||||
default:
|
||||
LOGIF(LE, (skygw_log_write_flush(LOGFILE_ERROR,
|
||||
"maxinfo: Unexpected MySQL command 0x%x",
|
||||
MYSQL_COMMAND(queue))));
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display router diagnostics
|
||||
*
|
||||
* @param instance Instance of the router
|
||||
* @param dcb DCB to send diagnostics to
|
||||
*/
|
||||
static void
|
||||
diagnostics(ROUTER *instance, DCB *dcb)
|
||||
{
|
||||
return; /* Nothing to do currently */
|
||||
}
|
||||
|
||||
/**
|
||||
* Capabilities interface for the rotuer
|
||||
*
|
||||
* Not used for the maxinfo router
|
||||
*/
|
||||
static uint8_t
|
||||
getCapabilities(
|
||||
ROUTER* inst,
|
||||
void* router_session)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Return some basic statistics from the router in response to a COM_STATISTICS
|
||||
* request.
|
||||
*
|
||||
* @param router The router instance
|
||||
* @param session The connection that requested the statistics
|
||||
* @param queue The statistics request
|
||||
*
|
||||
* @return non-zero on sucessful send
|
||||
*/
|
||||
static int
|
||||
maxinfo_statistics(INFO_INSTANCE *router, INFO_SESSION *session, GWBUF *queue)
|
||||
{
|
||||
char result[1000], *ptr;
|
||||
GWBUF *ret;
|
||||
int len;
|
||||
extern int MaxScaleUptime();
|
||||
|
||||
snprintf(result, 1000,
|
||||
"Uptime: %u Threads: %u Sessions: %u ",
|
||||
MaxScaleUptime(),
|
||||
config_threadcount(),
|
||||
serviceSessionCountAll());
|
||||
if ((ret = gwbuf_alloc(4 + strlen(result))) == NULL)
|
||||
return 0;
|
||||
len = strlen(result);
|
||||
ptr = GWBUF_DATA(ret);
|
||||
*ptr++ = len & 0xff;
|
||||
*ptr++ = (len & 0xff00) >> 8;
|
||||
*ptr++ = (len & 0xff0000) >> 16;
|
||||
*ptr++ = 1;
|
||||
strncpy(ptr, result, len);
|
||||
|
||||
return session->dcb->func.write(session->dcb, ret);
|
||||
}
|
||||
|
||||
/**
|
||||
* Respond to a COM_PING command
|
||||
*
|
||||
* @param router The router instance
|
||||
* @param session The connection that requested the ping
|
||||
* @param queue The ping request
|
||||
*/
|
||||
static int
|
||||
maxinfo_ping(INFO_INSTANCE *router, INFO_SESSION *session, GWBUF *queue)
|
||||
{
|
||||
char *ptr;
|
||||
GWBUF *ret;
|
||||
int len;
|
||||
|
||||
if ((ret = gwbuf_alloc(5)) == NULL)
|
||||
return 0;
|
||||
ptr = GWBUF_DATA(ret);
|
||||
*ptr++ = 0x01;
|
||||
*ptr++ = 0;
|
||||
*ptr++ = 0;
|
||||
*ptr++ = 1;
|
||||
*ptr = 0; // OK
|
||||
|
||||
return session->dcb->func.write(session->dcb, ret);
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate the version comment with the MaxScale version
|
||||
*
|
||||
* @param result The result set
|
||||
* @param data Pointer to int which is row count
|
||||
* @return The populated row
|
||||
*/
|
||||
static RESULT_ROW *
|
||||
version_comment(RESULTSET *result, void *data)
|
||||
{
|
||||
int *context = (int *)data;
|
||||
RESULT_ROW *row;
|
||||
|
||||
if (*context == 0)
|
||||
{
|
||||
(*context)++;
|
||||
row = resultset_make_row(result);
|
||||
resultset_row_set(row, 0, MAXSCALE_VERSION);
|
||||
return row;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* The hardwired select @@vercom response
|
||||
*
|
||||
* @param dcb The DCB of the client
|
||||
*/
|
||||
static void
|
||||
respond_vercom(DCB *dcb)
|
||||
{
|
||||
RESULTSET *result;
|
||||
int context = 0;
|
||||
|
||||
if ((result = resultset_create(version_comment, &context)) == NULL)
|
||||
{
|
||||
maxinfo_send_error(dcb, 0, "No resources available");
|
||||
return;
|
||||
}
|
||||
resultset_add_column(result, "@@version_comment", 40, COL_TYPE_VARCHAR);
|
||||
resultset_stream_mysql(result, dcb);
|
||||
resultset_free(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate the version comment with the MaxScale version
|
||||
*
|
||||
* @param result The result set
|
||||
* @param data Pointer to int which is row count
|
||||
* @return The populated row
|
||||
*/
|
||||
static RESULT_ROW *
|
||||
starttime_row(RESULTSET *result, void *data)
|
||||
{
|
||||
int *context = (int *)data;
|
||||
RESULT_ROW *row;
|
||||
extern time_t MaxScaleStarted;
|
||||
struct tm tm;
|
||||
static char buf[40];
|
||||
|
||||
if (*context == 0)
|
||||
{
|
||||
(*context)++;
|
||||
row = resultset_make_row(result);
|
||||
sprintf(buf, "%u", MaxScaleStarted);
|
||||
resultset_row_set(row, 0, buf);
|
||||
return row;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* The hardwired select ... as starttime response
|
||||
*
|
||||
* @param dcb The DCB of the client
|
||||
*/
|
||||
static void
|
||||
respond_starttime(DCB *dcb)
|
||||
{
|
||||
RESULTSET *result;
|
||||
int context = 0;
|
||||
|
||||
if ((result = resultset_create(starttime_row, &context)) == NULL)
|
||||
{
|
||||
maxinfo_send_error(dcb, 0, "No resources available");
|
||||
return;
|
||||
}
|
||||
resultset_add_column(result, "starttime", 40, COL_TYPE_VARCHAR);
|
||||
resultset_stream_mysql(result, dcb);
|
||||
resultset_free(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a MySQL OK packet to the DCB
|
||||
*
|
||||
* @param dcb The DCB to send the OK packet to
|
||||
* @return result of a write call, non-zero if write was successful
|
||||
*/
|
||||
static int
|
||||
maxinfo_send_ok(DCB *dcb)
|
||||
{
|
||||
GWBUF *buf;
|
||||
uint8_t *ptr;
|
||||
|
||||
if ((buf = gwbuf_alloc(11)) == NULL)
|
||||
return 0;
|
||||
ptr = GWBUF_DATA(buf);
|
||||
*ptr++ = 7; // Payload length
|
||||
*ptr++ = 0;
|
||||
*ptr++ = 0;
|
||||
*ptr++ = 1; // Seqno
|
||||
*ptr++ = 0; // ok
|
||||
*ptr++ = 0;
|
||||
*ptr++ = 0;
|
||||
*ptr++ = 2;
|
||||
*ptr++ = 0;
|
||||
*ptr++ = 0;
|
||||
*ptr++ = 0;
|
||||
return dcb->func.write(dcb, buf);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a SQL query against the MaxScale Information Schema
|
||||
*
|
||||
* @param instance The instance strcture
|
||||
* @param session The session pointer
|
||||
* @param sql The SQL to execute
|
||||
*/
|
||||
static int
|
||||
maxinfo_execute_query(INFO_INSTANCE *instance, INFO_SESSION *session, char *sql)
|
||||
{
|
||||
MAXINFO_TREE *tree;
|
||||
PARSE_ERROR err;
|
||||
|
||||
LOGIF(LT, (skygw_log_write(LOGFILE_TRACE,
|
||||
"maxinfo: SQL statement: '%s' for 0x%x.",
|
||||
sql, session->dcb)));
|
||||
if (strcmp(sql, "select @@version_comment limit 1") == 0)
|
||||
{
|
||||
respond_vercom(session->dcb);
|
||||
return 1;
|
||||
}
|
||||
/* Below is a kludge for MonYog, if we see
|
||||
* select unix_timestamp... as starttime
|
||||
* just return the starttime of MaxScale
|
||||
*/
|
||||
if (strncasecmp(sql, "select UNIX_TIMESTAMP",
|
||||
strlen("select UNIX_TIMESTAMP")) == 0
|
||||
&& strstr(sql, "as starttime") != NULL)
|
||||
{
|
||||
respond_starttime(session->dcb);
|
||||
return 1;
|
||||
}
|
||||
if (strcasecmp(sql, "set names 'utf8'") == 0)
|
||||
{
|
||||
return maxinfo_send_ok(session->dcb);
|
||||
}
|
||||
if (strncasecmp(sql, "set session", 11) == 0)
|
||||
{
|
||||
return maxinfo_send_ok(session->dcb);
|
||||
}
|
||||
if (strncasecmp(sql, "SELECT `ENGINES`.`SUPPORT`", 26) == 0)
|
||||
{
|
||||
return maxinfo_send_ok(session->dcb);
|
||||
}
|
||||
if ((tree = maxinfo_parse(sql, &err)) == NULL)
|
||||
{
|
||||
maxinfo_send_parse_error(session->dcb, sql, err);
|
||||
LOGIF(LM, (skygw_log_write(
|
||||
LOGFILE_MESSAGE,
|
||||
"Failed to parse SQL statement: '%s'.",
|
||||
sql)));
|
||||
}
|
||||
else
|
||||
maxinfo_execute(session->dcb, tree);
|
||||
return 1;
|
||||
}
|
118
server/modules/routing/maxinfo/maxinfo_error.c
Normal file
118
server/modules/routing/maxinfo/maxinfo_error.c
Normal file
@ -0,0 +1,118 @@
|
||||
/*
|
||||
* This file is distributed as part of MaxScale. It is free
|
||||
* software: you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation,
|
||||
* version 2.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program; if not, write to the Free Software Foundation, Inc., 51
|
||||
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Copyright MariaDB Corporation Ab 2014
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file maxinfo_error.c - Handle error reporting for the maxinfo router
|
||||
*
|
||||
* @verbatim
|
||||
* Revision History
|
||||
*
|
||||
* Date Who Description
|
||||
* 17/02/15 Mark Riddoch Initial implementation
|
||||
*
|
||||
* @endverbatim
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <service.h>
|
||||
#include <session.h>
|
||||
#include <router.h>
|
||||
#include <modules.h>
|
||||
#include <modinfo.h>
|
||||
#include <modutil.h>
|
||||
#include <atomic.h>
|
||||
#include <spinlock.h>
|
||||
#include <dcb.h>
|
||||
#include <poll.h>
|
||||
#include <maxinfo.h>
|
||||
#include <skygw_utils.h>
|
||||
#include <log_manager.h>
|
||||
|
||||
|
||||
/** Defined in log_manager.cc */
|
||||
extern int lm_enabled_logfiles_bitmask;
|
||||
extern size_t log_ses_count[];
|
||||
extern __thread log_info_t tls_log_info;
|
||||
|
||||
/**
|
||||
* Process a parse error and send error report to client
|
||||
*
|
||||
* @param dcb The DCB to send to error
|
||||
* @param sql The SQL that had the parse error
|
||||
* @param err The parse error code
|
||||
*/
|
||||
void
|
||||
maxinfo_send_parse_error(DCB *dcb, char *sql, PARSE_ERROR err)
|
||||
{
|
||||
char *desc = "";
|
||||
char *msg;
|
||||
int len;
|
||||
|
||||
switch (err)
|
||||
{
|
||||
case PARSE_NOERROR:
|
||||
desc = "No error";
|
||||
break;
|
||||
case PARSE_MALFORMED_SHOW:
|
||||
desc = "Expected show <command> [like <pattern>]";
|
||||
break;
|
||||
case PARSE_EXPECTED_LIKE:
|
||||
desc = "Expected LIKE <pattern>";
|
||||
break;
|
||||
case PARSE_SYNTAX_ERROR:
|
||||
desc = "Syntax error";
|
||||
break;
|
||||
}
|
||||
|
||||
len = strlen(sql) + strlen(desc) + 20;
|
||||
if ((msg = (char *)malloc(len)) == NULL)
|
||||
return;
|
||||
sprintf(msg, "%s in query '%s'", desc, sql);
|
||||
maxinfo_send_error(dcb, 1149, msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct an error response
|
||||
*
|
||||
* @param dcb The DCB to send the error packet to
|
||||
* @param msg The slave server instance
|
||||
*/
|
||||
void
|
||||
maxinfo_send_error(DCB *dcb, int errcode, char *msg)
|
||||
{
|
||||
GWBUF *pkt;
|
||||
unsigned char *data;
|
||||
int len;
|
||||
|
||||
len = strlen(msg) + 9;
|
||||
if ((pkt = gwbuf_alloc(len + 4)) == NULL)
|
||||
return;
|
||||
data = GWBUF_DATA(pkt);
|
||||
data[0] = len & 0xff; // Payload length
|
||||
data[1] = (len >> 8) & 0xff;
|
||||
data[2] = (len >> 16) & 0xff;
|
||||
data[3] = 1; // Sequence id
|
||||
// Payload
|
||||
data[4] = 0xff; // Error indicator
|
||||
data[5] = errcode & 0xff; // Error Code
|
||||
data[6] = (errcode >> 8) & 0xff; // Error Code
|
||||
strncpy((char *)&data[7], "#42000", 6);
|
||||
strncpy((char *)&data[13], msg, strlen(msg)); // Error Message
|
||||
dcb->func.write(dcb, pkt);
|
||||
}
|
406
server/modules/routing/maxinfo/maxinfo_exec.c
Normal file
406
server/modules/routing/maxinfo/maxinfo_exec.c
Normal file
@ -0,0 +1,406 @@
|
||||
/*
|
||||
* This file is distributed as part of MaxScale. It is free
|
||||
* software: you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation,
|
||||
* version 2.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program; if not, write to the Free Software Foundation, Inc., 51
|
||||
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Copyright MariaDB Corporation Ab 2014
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file maxinfo_parse.c - Parse the limited set of SQL that the MaxScale
|
||||
* information schema can use
|
||||
*
|
||||
* @verbatim
|
||||
* Revision History
|
||||
*
|
||||
* Date Who Description
|
||||
* 17/02/15 Mark Riddoch Initial implementation
|
||||
*
|
||||
* @endverbatim
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <service.h>
|
||||
#include <session.h>
|
||||
#include <router.h>
|
||||
#include <modules.h>
|
||||
#include <version.h>
|
||||
#include <modinfo.h>
|
||||
#include <modutil.h>
|
||||
#include <atomic.h>
|
||||
#include <spinlock.h>
|
||||
#include <dcb.h>
|
||||
#include <poll.h>
|
||||
#include <maxinfo.h>
|
||||
#include <skygw_utils.h>
|
||||
#include <log_manager.h>
|
||||
#include <resultset.h>
|
||||
#include <config.h>
|
||||
|
||||
extern int lm_enabled_logfiles_bitmask;
|
||||
extern size_t log_ses_count[];
|
||||
extern __thread log_info_t tls_log_info;
|
||||
|
||||
static void exec_show(DCB *dcb, MAXINFO_TREE *tree);
|
||||
static void exec_select(DCB *dcb, MAXINFO_TREE *tree);
|
||||
static void exec_show_variables(DCB *dcb, MAXINFO_TREE *filter);
|
||||
static void exec_show_status(DCB *dcb, MAXINFO_TREE *filter);
|
||||
static int maxinfo_pattern_match(char *pattern, char *str);
|
||||
|
||||
/**
|
||||
* Execute a parse tree and return the result set or runtime error
|
||||
*
|
||||
* @param dcb The DCB that connects to the client
|
||||
* @param tree The parse tree for the query
|
||||
*/
|
||||
void
|
||||
maxinfo_execute(DCB *dcb, MAXINFO_TREE *tree)
|
||||
{
|
||||
switch (tree->op)
|
||||
{
|
||||
case MAXOP_SHOW:
|
||||
exec_show(dcb, tree);
|
||||
break;
|
||||
case MAXOP_SELECT:
|
||||
exec_select(dcb, tree);
|
||||
break;
|
||||
case MAXOP_TABLE:
|
||||
case MAXOP_COLUMNS:
|
||||
case MAXOP_LITERAL:
|
||||
case MAXOP_PREDICATE:
|
||||
case MAXOP_LIKE:
|
||||
case MAXOP_EQUAL:
|
||||
default:
|
||||
maxinfo_send_error(dcb, 0, "Unexpected operator in parse tree");
|
||||
}
|
||||
}
|
||||
|
||||
static struct {
|
||||
char *name;
|
||||
void (*func)(DCB *, MAXINFO_TREE *);
|
||||
} show_commands[] = {
|
||||
{ "variables", exec_show_variables },
|
||||
{ "status", exec_show_status },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
/**
|
||||
* Execute a show command parse tree and return the result set or runtime error
|
||||
*
|
||||
* @param dcb The DCB that connects to the client
|
||||
* @param tree The parse tree for the query
|
||||
*/
|
||||
static void
|
||||
exec_show(DCB *dcb, MAXINFO_TREE *tree)
|
||||
{
|
||||
int i;
|
||||
char errmsg[120];
|
||||
|
||||
for (i = 0; show_commands[i].name; i++)
|
||||
{
|
||||
if (strcasecmp(show_commands[i].name, tree->value) == 0)
|
||||
{
|
||||
(*show_commands[i].func)(dcb, tree->right);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (strlen(tree->value) > 80) // Prevent buffer overrun
|
||||
tree->value[80] = 0;
|
||||
sprintf(errmsg, "Unsupported show command '%s'", tree->value);
|
||||
maxinfo_send_error(dcb, 0, errmsg);
|
||||
LOGIF(LM, (skygw_log_write(LOGFILE_MESSAGE, errmsg)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the current MaxScale version
|
||||
*
|
||||
* @return The version string for MaxScale
|
||||
*/
|
||||
static char *
|
||||
getVersion()
|
||||
{
|
||||
return MAXSCALE_VERSION;
|
||||
}
|
||||
|
||||
static char *versionComment = "MariaDB MaxScale";
|
||||
/**
|
||||
* Return the current MaxScale version
|
||||
*
|
||||
* @return The version string for MaxScale
|
||||
*/
|
||||
static char *
|
||||
getVersionComment()
|
||||
{
|
||||
return versionComment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the current MaxScale Home Directory
|
||||
*
|
||||
* @return The version string for MaxScale
|
||||
*/
|
||||
static char *
|
||||
getMaxScaleHome()
|
||||
{
|
||||
return getenv("MAXSCALE_HOME");
|
||||
}
|
||||
|
||||
/* The various methods to fetch the variables */
|
||||
#define VT_STRING 1
|
||||
#define VT_INT 2
|
||||
|
||||
extern int MaxScaleUptime();
|
||||
|
||||
typedef void *(*STATSFUNC)();
|
||||
/**
|
||||
* Variables that may be sent in a show variables
|
||||
*/
|
||||
static struct {
|
||||
char *name;
|
||||
int type;
|
||||
STATSFUNC func;
|
||||
} variables[] = {
|
||||
{ "version", VT_STRING, (STATSFUNC)getVersion },
|
||||
{ "version_comment", VT_STRING, (STATSFUNC)getVersionComment },
|
||||
{ "basedir", VT_STRING, (STATSFUNC)getMaxScaleHome},
|
||||
{ "MAXSCALE_VERSION", VT_STRING, (STATSFUNC)getVersion },
|
||||
{ "MAXSCALE_THREADS", VT_INT, (STATSFUNC)config_threadcount },
|
||||
{ "MAXSCALE_NBPOLLS", VT_INT, (STATSFUNC)config_nbpolls },
|
||||
{ "MAXSCALE_POLLSLEEP", VT_INT, (STATSFUNC)config_pollsleep },
|
||||
{ "MAXSCALE_UPTIME", VT_INT, (STATSFUNC)MaxScaleUptime },
|
||||
{ "MAXSCALE_SESSIONS", VT_INT, (STATSFUNC)serviceSessionCountAll },
|
||||
{ NULL, 0, NULL }
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
int index;
|
||||
char *like;
|
||||
} VARCONTEXT;
|
||||
|
||||
/**
|
||||
* Callback function to populate rows of the show variable
|
||||
* command
|
||||
*
|
||||
* @param data The context point
|
||||
* @return The next row or NULL if end of rows
|
||||
*/
|
||||
static RESULT_ROW *
|
||||
variable_row(RESULTSET *result, void *data)
|
||||
{
|
||||
VARCONTEXT *context = (VARCONTEXT *)data;
|
||||
RESULT_ROW *row;
|
||||
char buf[80];
|
||||
|
||||
if (variables[context->index].name)
|
||||
{
|
||||
if (context->like &&
|
||||
maxinfo_pattern_match(context->like,
|
||||
variables[context->index].name))
|
||||
{
|
||||
context->index++;
|
||||
return variable_row(result, data);
|
||||
}
|
||||
row = resultset_make_row(result);
|
||||
resultset_row_set(row, 0, variables[context->index].name);
|
||||
switch (variables[context->index].type)
|
||||
{
|
||||
case VT_STRING:
|
||||
resultset_row_set(row, 1,
|
||||
(char *)(*variables[context->index].func)());
|
||||
break;
|
||||
case VT_INT:
|
||||
snprintf(buf, 80, "%d",
|
||||
(int)(*variables[context->index].func)());
|
||||
resultset_row_set(row, 1, buf);
|
||||
break;
|
||||
}
|
||||
context->index++;
|
||||
return row;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a show variables command applying an optional filter
|
||||
*
|
||||
* @param dcb The DCB connected to the client
|
||||
* @param filter A potential like clause or NULL
|
||||
*/
|
||||
static void
|
||||
exec_show_variables(DCB *dcb, MAXINFO_TREE *filter)
|
||||
{
|
||||
RESULTSET *result;
|
||||
VARCONTEXT context;
|
||||
|
||||
if (filter)
|
||||
context.like = filter->value;
|
||||
else
|
||||
context.like = NULL;
|
||||
context.index = 0;
|
||||
|
||||
if ((result = resultset_create(variable_row, &context)) == NULL)
|
||||
{
|
||||
maxinfo_send_error(dcb, 0, "No resources available");
|
||||
return;
|
||||
}
|
||||
resultset_add_column(result, "Variable_name", 40, COL_TYPE_VARCHAR);
|
||||
resultset_add_column(result, "Value", 40, COL_TYPE_VARCHAR);
|
||||
resultset_stream_mysql(result, dcb);
|
||||
resultset_free(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Variables that may be sent in a show variables
|
||||
*/
|
||||
static struct {
|
||||
char *name;
|
||||
int type;
|
||||
STATSFUNC func;
|
||||
} status[] = {
|
||||
{ "Uptime", VT_INT, (STATSFUNC)MaxScaleUptime },
|
||||
{ "Uptime_since_flush_status", VT_INT, (STATSFUNC)MaxScaleUptime },
|
||||
{ "Threads_created", VT_INT, (STATSFUNC)config_threadcount },
|
||||
{ "Threads_running", VT_INT, (STATSFUNC)config_threadcount },
|
||||
{ "Threadpool_threads", VT_INT, (STATSFUNC)config_threadcount },
|
||||
{ "Threads_connected", VT_INT, (STATSFUNC)serviceSessionCountAll },
|
||||
{ NULL, 0, NULL }
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback function to populate rows of the show variable
|
||||
* command
|
||||
*
|
||||
* @param data The context point
|
||||
* @return The next row or NULL if end of rows
|
||||
*/
|
||||
static RESULT_ROW *
|
||||
status_row(RESULTSET *result, void *data)
|
||||
{
|
||||
VARCONTEXT *context = (VARCONTEXT *)data;
|
||||
RESULT_ROW *row;
|
||||
char buf[80];
|
||||
|
||||
if (status[context->index].name)
|
||||
{
|
||||
if (context->like &&
|
||||
maxinfo_pattern_match(context->like,
|
||||
status[context->index].name))
|
||||
{
|
||||
context->index++;
|
||||
return status_row(result, data);
|
||||
}
|
||||
row = resultset_make_row(result);
|
||||
resultset_row_set(row, 0, status[context->index].name);
|
||||
switch (status[context->index].type)
|
||||
{
|
||||
case VT_STRING:
|
||||
resultset_row_set(row, 1,
|
||||
(char *)(*status[context->index].func)());
|
||||
break;
|
||||
case VT_INT:
|
||||
snprintf(buf, 80, "%d",
|
||||
(int)(*status[context->index].func)());
|
||||
resultset_row_set(row, 1, buf);
|
||||
break;
|
||||
}
|
||||
context->index++;
|
||||
return row;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a show status command applying an optional filter
|
||||
*
|
||||
* @param dcb The DCB connected to the client
|
||||
* @param filter A potential like clause or NULL
|
||||
*/
|
||||
static void
|
||||
exec_show_status(DCB *dcb, MAXINFO_TREE *filter)
|
||||
{
|
||||
RESULTSET *result;
|
||||
VARCONTEXT context;
|
||||
|
||||
if (filter)
|
||||
context.like = filter->value;
|
||||
else
|
||||
context.like = NULL;
|
||||
context.index = 0;
|
||||
|
||||
if ((result = resultset_create(status_row, &context)) == NULL)
|
||||
{
|
||||
maxinfo_send_error(dcb, 0, "No resources available");
|
||||
return;
|
||||
}
|
||||
resultset_add_column(result, "Variable_name", 40, COL_TYPE_VARCHAR);
|
||||
resultset_add_column(result, "Value", 40, COL_TYPE_VARCHAR);
|
||||
resultset_stream_mysql(result, dcb);
|
||||
resultset_free(result);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Execute a select command parse tree and return the result set
|
||||
* or runtime error
|
||||
*
|
||||
* @param dcb The DCB that connects to the client
|
||||
* @param tree The parse tree for the query
|
||||
*/
|
||||
static void
|
||||
exec_select(DCB *dcb, MAXINFO_TREE *tree)
|
||||
{
|
||||
maxinfo_send_error(dcb, 0, "Select not yet implemented");
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a "like" pattern match. Only works for leading and trailing %
|
||||
*
|
||||
* @param pattern Pattern to match
|
||||
* @param str String to match against pattern
|
||||
* @return Zero on match
|
||||
*/
|
||||
static int
|
||||
maxinfo_pattern_match(char *pattern, char *str)
|
||||
{
|
||||
int anchor, len, trailing;
|
||||
char *fixed;
|
||||
|
||||
if (*pattern != '%')
|
||||
{
|
||||
fixed = pattern;
|
||||
anchor = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
fixed = &pattern[1];
|
||||
}
|
||||
len = strlen(fixed);
|
||||
if (fixed[len - 1] == '%')
|
||||
trailing = 1;
|
||||
else
|
||||
trailing = 0;
|
||||
if (anchor == 1 && trailing == 0) // No wildcard
|
||||
return strcasecmp(pattern, str);
|
||||
else if (anchor == 1)
|
||||
return strncasecmp(str, pattern, len - trailing);
|
||||
else
|
||||
{
|
||||
char *portion = malloc(len + 1);
|
||||
int rval;
|
||||
strncpy(portion, fixed, len - trailing);
|
||||
rval = (strcasestr(portion, str) != NULL ? 0 : 1);
|
||||
free(portion);
|
||||
return rval;
|
||||
}
|
||||
}
|
320
server/modules/routing/maxinfo/maxinfo_parse.c
Normal file
320
server/modules/routing/maxinfo/maxinfo_parse.c
Normal file
@ -0,0 +1,320 @@
|
||||
/*
|
||||
* This file is distributed as part of MaxScale. It is free
|
||||
* software: you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation,
|
||||
* version 2.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program; if not, write to the Free Software Foundation, Inc., 51
|
||||
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Copyright MariaDB Corporation Ab 2014
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file maxinfo_parse.c - Parse the limited set of SQL that the MaxScale
|
||||
* information schema can use
|
||||
*
|
||||
* @verbatim
|
||||
* Revision History
|
||||
*
|
||||
* Date Who Description
|
||||
* 16/02/15 Mark Riddoch Initial implementation
|
||||
*
|
||||
* @endverbatim
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <service.h>
|
||||
#include <session.h>
|
||||
#include <router.h>
|
||||
#include <modules.h>
|
||||
#include <modinfo.h>
|
||||
#include <modutil.h>
|
||||
#include <atomic.h>
|
||||
#include <spinlock.h>
|
||||
#include <dcb.h>
|
||||
#include <poll.h>
|
||||
#include <maxinfo.h>
|
||||
#include <skygw_utils.h>
|
||||
#include <log_manager.h>
|
||||
|
||||
static MAXINFO_TREE *make_tree_node(MAXINFO_OPERATOR, char *, MAXINFO_TREE *, MAXINFO_TREE *);
|
||||
static void free_tree(MAXINFO_TREE *);
|
||||
static char *fetch_token(char *, int *, char **);
|
||||
static MAXINFO_TREE *parse_column_list(char **sql);
|
||||
static MAXINFO_TREE *parse_table_name(char **sql);
|
||||
|
||||
|
||||
/**
|
||||
* Parse a SQL subset for the maxinfo plugin and return a parse tree
|
||||
*
|
||||
* @param sql The SQL query
|
||||
* @return Parse tree or NULL on error
|
||||
*/
|
||||
MAXINFO_TREE *
|
||||
maxinfo_parse(char *sql, PARSE_ERROR *parse_error)
|
||||
{
|
||||
int token;
|
||||
char *ptr, *text;
|
||||
MAXINFO_TREE *tree = NULL;
|
||||
MAXINFO_TREE *col, *table;
|
||||
|
||||
*parse_error = PARSE_NOERROR;
|
||||
while ((ptr = fetch_token(sql, &token, &text)) != NULL)
|
||||
{
|
||||
switch (token)
|
||||
{
|
||||
case LT_SHOW:
|
||||
free(text); // not needed
|
||||
ptr = fetch_token(ptr, &token, &text);
|
||||
if (ptr == NULL || token != LT_STRING)
|
||||
{
|
||||
// Expected show "name"
|
||||
*parse_error = PARSE_MALFORMED_SHOW;
|
||||
return NULL;
|
||||
}
|
||||
tree = make_tree_node(MAXOP_SHOW, text, NULL, NULL);
|
||||
if ((ptr = fetch_token(ptr, &token, &text)) == NULL)
|
||||
return tree;
|
||||
else if (token == LT_LIKE)
|
||||
{
|
||||
if ((ptr = fetch_token(ptr, &token, &text)) != NULL)
|
||||
{
|
||||
tree->right = make_tree_node(MAXOP_LIKE,
|
||||
text, NULL, NULL);
|
||||
return tree;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Expected expression
|
||||
*parse_error = PARSE_EXPECTED_LIKE;
|
||||
free_tree(tree);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
// Malformed show
|
||||
free_tree(tree);
|
||||
*parse_error = PARSE_MALFORMED_SHOW;
|
||||
return NULL;
|
||||
#if 0
|
||||
case LT_SELECT:
|
||||
free(text); // not needed
|
||||
col = parse_column_list(&ptr);
|
||||
table = parse_table_name(&ptr);
|
||||
return make_tree_node(MAXOP_SELECT, NULL, col, table);
|
||||
#endif
|
||||
default:
|
||||
*parse_error = PARSE_SYNTAX_ERROR;
|
||||
if (tree)
|
||||
free_tree(tree);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
*parse_error = PARSE_SYNTAX_ERROR;
|
||||
if (tree)
|
||||
free_tree(tree);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a column list, may be a * or a valid list of string name
|
||||
* seperated by a comma
|
||||
*
|
||||
* @param sql Pointer to pointer to column list updated to point to the table name
|
||||
* @return A tree of column names
|
||||
*/
|
||||
static MAXINFO_TREE *
|
||||
parse_column_list(char **ptr)
|
||||
{
|
||||
int token, lookahead;
|
||||
char *text, *text2;
|
||||
MAXINFO_TREE *tree = NULL;
|
||||
|
||||
*ptr = fetch_token(*ptr, &token, &text);
|
||||
*ptr = fetch_token(*ptr, &lookahead, &text2);
|
||||
switch (token)
|
||||
{
|
||||
case LT_STRING:
|
||||
free(text2);
|
||||
switch (lookahead)
|
||||
{
|
||||
case LT_COMMA:
|
||||
return make_tree_node(MAXOP_COLUMNS, text, NULL,
|
||||
parse_column_list(ptr));
|
||||
case LT_FROM:
|
||||
return make_tree_node(MAXOP_COLUMNS, text, NULL,
|
||||
NULL);
|
||||
}
|
||||
break;
|
||||
case LT_STAR:
|
||||
free(text);
|
||||
free(text2);
|
||||
if (lookahead != LT_FROM)
|
||||
return make_tree_node(MAXOP_ALL_COLUMNS, NULL, NULL,
|
||||
NULL);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parse a table name
|
||||
*
|
||||
* @param sql Pointer to pointer to column list updated to point to the table name
|
||||
* @return A tree of table names
|
||||
*/
|
||||
static MAXINFO_TREE *
|
||||
parse_table_name(char **ptr)
|
||||
{
|
||||
int token;
|
||||
char *text;
|
||||
MAXINFO_TREE *tree = NULL;
|
||||
|
||||
*ptr = fetch_token(*ptr, &token, &text);
|
||||
if (token == LT_STRING)
|
||||
return make_tree_node(MAXOP_TABLE, text, NULL, NULL);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allocate and populate a parse tree node
|
||||
*
|
||||
* @param op The node operator
|
||||
* @param value The node value
|
||||
* @param left The left branch of the parse tree
|
||||
* @param right The right branch of the parse tree
|
||||
* @return The new parse tree node
|
||||
*/
|
||||
static MAXINFO_TREE *
|
||||
make_tree_node(MAXINFO_OPERATOR op, char *value, MAXINFO_TREE *left, MAXINFO_TREE *right)
|
||||
{
|
||||
MAXINFO_TREE *node;
|
||||
|
||||
if ((node = (MAXINFO_TREE *)malloc(sizeof(MAXINFO_TREE))) == NULL)
|
||||
return NULL;
|
||||
node->op = op;
|
||||
node->value = value;
|
||||
node->left = left;
|
||||
node->right = right;
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recusrsively free the storage associated with a parse tree
|
||||
*
|
||||
* @param tree The parse tree to free
|
||||
*/
|
||||
static void
|
||||
free_tree(MAXINFO_TREE *tree)
|
||||
{
|
||||
if (tree->left)
|
||||
free_tree(tree->left);
|
||||
if (tree->right)
|
||||
free_tree(tree->right);
|
||||
if (tree->value)
|
||||
free(tree->value);
|
||||
free(tree);
|
||||
}
|
||||
|
||||
/**
|
||||
* The set of keywords known to the tokeniser
|
||||
*/
|
||||
static struct {
|
||||
char *text;
|
||||
int token;
|
||||
} keywords[] = {
|
||||
{ "show", LT_SHOW },
|
||||
{ "select", LT_SELECT },
|
||||
{ "from", LT_FROM },
|
||||
{ "like", LT_LIKE },
|
||||
{ "=", LT_EQUAL },
|
||||
{ ",", LT_COMMA },
|
||||
{ "*", LT_STAR },
|
||||
{ NULL, 0 }
|
||||
};
|
||||
|
||||
/**
|
||||
* Limited SQL tokeniser. Understands a limited set of key words and
|
||||
* quoted strings.
|
||||
*
|
||||
* @param sql The SQL to tokenise
|
||||
* @param token The returned token
|
||||
* @param text The matching text
|
||||
* @return The next position to tokenise from
|
||||
*/
|
||||
static char *
|
||||
fetch_token(char *sql, int *token, char **text)
|
||||
{
|
||||
char *s1, *s2, quote = '\0';
|
||||
int i;
|
||||
|
||||
s1 = sql;
|
||||
while (*s1 && isspace(*s1))
|
||||
{
|
||||
s1++;
|
||||
}
|
||||
if (quote == '\0' && (*s1 == '\'' || *s1 == '\"'))
|
||||
{
|
||||
quote = *s1++;
|
||||
}
|
||||
if (*s1 == '/' && *(s1 + 1) == '*')
|
||||
{
|
||||
s1 += 2;
|
||||
// Skip the comment
|
||||
do {
|
||||
while (*s1 && *s1 != '*')
|
||||
s1++;
|
||||
} while (*(s1 + 1) && *(s1 + 1) != '/');
|
||||
s1 += 2;
|
||||
while (*s1 && isspace(*s1))
|
||||
{
|
||||
s1++;
|
||||
}
|
||||
if (quote == '\0' && (*s1 == '\'' || *s1 == '\"'))
|
||||
{
|
||||
quote = *s1++;
|
||||
}
|
||||
}
|
||||
s2 = s1;
|
||||
while (*s2)
|
||||
{
|
||||
if (quote == '\0' && (isspace(*s2)
|
||||
|| *s2 == ',' || *s2 == '='))
|
||||
break;
|
||||
else if (quote == *s2)
|
||||
{
|
||||
break;
|
||||
}
|
||||
s2++;
|
||||
}
|
||||
|
||||
if (*s1 == '@' && *(s1 + 1) == '@')
|
||||
{
|
||||
*text = strndup(s1 + 2, (s2 - s1) - 2);
|
||||
*token = LT_VARIABLE;
|
||||
return s2;
|
||||
}
|
||||
|
||||
if (s1 == s2)
|
||||
return NULL;
|
||||
|
||||
*text = strndup(s1, s2 - s1);
|
||||
for (i = 0; keywords[i].text; i++)
|
||||
{
|
||||
if (strcasecmp(keywords[i].text, *text) == 0)
|
||||
{
|
||||
*token = keywords[i].token;
|
||||
return s2;
|
||||
}
|
||||
}
|
||||
*token = LT_STRING;
|
||||
return s2;
|
||||
}
|
Reference in New Issue
Block a user