Merge branch '2.1' into 2.2
This commit is contained in:
@ -49,6 +49,7 @@ typedef struct
|
||||
bool detectStaleMaster; /**< Monitor flag for MySQL replication Stale Master detection */
|
||||
bool detectStaleSlave; /**< Monitor flag for MySQL replication Stale Master detection */
|
||||
bool multimaster; /**< Detect and handle multi-master topologies */
|
||||
bool ignore_external_masters; /**< Ignore masters outside of the monitor configuration */
|
||||
int disableMasterFailback; /**< Monitor flag for Galera Cluster Master failback */
|
||||
int availableWhenDonor; /**< Monitor flag for Galera Cluster Donor availability */
|
||||
int disableMasterRoleSetting; /**< Monitor flag to disable setting master role */
|
||||
|
||||
2109
server/modules/monitor/mysqlmon/mysql_mon.c
Normal file
2109
server/modules/monitor/mysqlmon/mysql_mon.c
Normal file
File diff suppressed because it is too large
Load Diff
@ -301,7 +301,7 @@ bool is_error_response(GWBUF *buffer)
|
||||
* @param dcb Backend DCB where authentication failed
|
||||
* @param buffer Buffer containing the response from the backend
|
||||
*/
|
||||
void log_error_response(DCB *dcb, GWBUF *buffer)
|
||||
static void handle_error_response(DCB *dcb, GWBUF *buffer)
|
||||
{
|
||||
uint8_t *data = (uint8_t*)GWBUF_DATA(buffer);
|
||||
size_t len = MYSQL_GET_PAYLOAD_LEN(data);
|
||||
@ -326,6 +326,16 @@ void log_error_response(DCB *dcb, GWBUF *buffer)
|
||||
|
||||
server_set_status(dcb->server, SERVER_MAINT);
|
||||
}
|
||||
else if (errcode == ER_ACCESS_DENIED_ERROR ||
|
||||
errcode == ER_DBACCESS_DENIED_ERROR ||
|
||||
errcode == ER_ACCESS_DENIED_NO_PASSWORD_ERROR)
|
||||
{
|
||||
if (dcb->session->state != SESSION_STATE_DUMMY)
|
||||
{
|
||||
// Authentication failed, reload users
|
||||
service_refresh_users(dcb->service);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -494,7 +504,7 @@ gw_read_backend_event(DCB *dcb)
|
||||
{
|
||||
/** The server responded with an error */
|
||||
proto->protocol_auth_state = MXS_AUTH_STATE_FAILED;
|
||||
log_error_response(dcb, readbuf);
|
||||
handle_error_response(dcb, readbuf);
|
||||
}
|
||||
|
||||
if (proto->protocol_auth_state == MXS_AUTH_STATE_CONNECTED)
|
||||
@ -887,7 +897,7 @@ gw_read_and_write(DCB *dcb)
|
||||
{
|
||||
/** The COM_CHANGE USER failed, generate a fake hangup event to
|
||||
* close the DCB and send an error to the client. */
|
||||
log_error_response(dcb, reply);
|
||||
handle_error_response(dcb, reply);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@ -71,7 +71,6 @@ static void mysql_client_auth_error_handling(DCB *dcb, int auth_val, int packet_
|
||||
static int gw_read_do_authentication(DCB *dcb, GWBUF *read_buffer, int nbytes_read);
|
||||
static int gw_read_normal_data(DCB *dcb, GWBUF *read_buffer, int nbytes_read);
|
||||
static int gw_read_finish_processing(DCB *dcb, GWBUF *read_buffer, uint64_t capabilities);
|
||||
static bool ensure_complete_packet(DCB *dcb, GWBUF **read_buffer, int nbytes_read);
|
||||
static void gw_process_one_new_client(DCB *client_dcb);
|
||||
static spec_com_res_t process_special_commands(DCB *client_dcb, GWBUF *read_buffer, int nbytes_read);
|
||||
static spec_com_res_t handle_query_kill(DCB* dcb, GWBUF* read_buffer, spec_com_res_t current,
|
||||
@ -816,51 +815,31 @@ static bool process_client_commands(DCB* dcb, int bytes_available, GWBUF** buffe
|
||||
int pktlen;
|
||||
uint8_t cmd = (uint8_t)MXS_COM_QUERY; // Treat empty packets as COM_QUERY
|
||||
|
||||
/**
|
||||
* Buffer has at least 5 bytes, the packet is in contiguous memory
|
||||
* and it's the first packet in the buffer.
|
||||
*/
|
||||
if (offset == 0 && GWBUF_LENGTH(queue) >= MYSQL_HEADER_LEN + 1)
|
||||
uint8_t packet_header[MYSQL_HEADER_LEN];
|
||||
|
||||
if (gwbuf_copy_data(queue, offset, MYSQL_HEADER_LEN, packet_header) != MYSQL_HEADER_LEN)
|
||||
{
|
||||
uint8_t *data = (uint8_t*)GWBUF_DATA(queue);
|
||||
pktlen = gw_mysql_get_byte3(data);
|
||||
if (pktlen)
|
||||
{
|
||||
cmd = *(data + MYSQL_HEADER_LEN);
|
||||
}
|
||||
ss_dassert(offset > 0);
|
||||
queue = split_and_store(dcb, queue, offset);
|
||||
break;
|
||||
}
|
||||
|
||||
pktlen = gw_mysql_get_byte3(packet_header);
|
||||
|
||||
/**
|
||||
* We have more than one packet in the buffer or the first 5 bytes
|
||||
* of a packet are split across two buffers.
|
||||
* Check if the packet is empty, and if not, if we have the command byte.
|
||||
* If we an empty packet or have at least 5 bytes of data, we can start
|
||||
* sending the data to the router.
|
||||
*/
|
||||
else
|
||||
if (pktlen && gwbuf_copy_data(queue, offset + MYSQL_HEADER_LEN, 1, &cmd) != 1)
|
||||
{
|
||||
uint8_t packet_header[MYSQL_HEADER_LEN];
|
||||
|
||||
if (gwbuf_copy_data(queue, offset, MYSQL_HEADER_LEN, packet_header) != MYSQL_HEADER_LEN)
|
||||
if ((queue = split_and_store(dcb, queue, offset)) == NULL)
|
||||
{
|
||||
ss_dassert(offset > 0);
|
||||
queue = split_and_store(dcb, queue, offset);
|
||||
break;
|
||||
}
|
||||
|
||||
pktlen = gw_mysql_get_byte3(packet_header);
|
||||
|
||||
/**
|
||||
* Check if the packet is empty, and if not, if we have the command byte.
|
||||
* If we an empty packet or have at least 5 bytes of data, we can start
|
||||
* sending the data to the router.
|
||||
*/
|
||||
if (pktlen && gwbuf_copy_data(queue, MYSQL_HEADER_LEN, 1, &cmd) != 1)
|
||||
{
|
||||
if ((queue = split_and_store(dcb, queue, offset)) == NULL)
|
||||
{
|
||||
ss_dassert(bytes_available == MYSQL_HEADER_LEN);
|
||||
return false;
|
||||
}
|
||||
ss_dassert(offset > 0);
|
||||
break;
|
||||
ss_dassert(bytes_available - offset == MYSQL_HEADER_LEN);
|
||||
return false;
|
||||
}
|
||||
ss_dassert(offset > 0);
|
||||
break;
|
||||
}
|
||||
|
||||
MySQLProtocol *proto = (MySQLProtocol*)dcb->protocol;
|
||||
@ -978,25 +957,28 @@ gw_read_normal_data(DCB *dcb, GWBUF *read_buffer, int nbytes_read)
|
||||
/** Ask what type of input the router/filter chain expects */
|
||||
capabilities = service_get_capabilities(session->service);
|
||||
|
||||
/** Update the current protocol command being executed */
|
||||
if (!process_client_commands(dcb, nbytes_read, &read_buffer))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** If the router requires statement input or we are still authenticating
|
||||
* we need to make sure that a complete SQL packet is read before continuing */
|
||||
/** If the router requires statement input we need to make sure that
|
||||
* a complete SQL packet is read before continuing. The current command
|
||||
* that is tracked by the protocol module is updated in route_by_statement() */
|
||||
if (rcap_type_required(capabilities, RCAP_TYPE_STMT_INPUT))
|
||||
{
|
||||
if (nbytes_read < 3 || nbytes_read <
|
||||
(int)(MYSQL_GET_PAYLOAD_LEN((uint8_t *) GWBUF_DATA(read_buffer)) + 4))
|
||||
uint8_t pktlen[MYSQL_HEADER_LEN];
|
||||
size_t n_copied = gwbuf_copy_data(read_buffer, 0, MYSQL_HEADER_LEN, pktlen);
|
||||
|
||||
if (n_copied != sizeof(pktlen) ||
|
||||
(uint32_t)nbytes_read < MYSQL_GET_PAYLOAD_LEN(pktlen) + MYSQL_HEADER_LEN)
|
||||
{
|
||||
dcb_readq_set(dcb, read_buffer);
|
||||
dcb_readq_append(dcb, read_buffer);
|
||||
return 0;
|
||||
}
|
||||
|
||||
set_qc_mode(session, &read_buffer);
|
||||
}
|
||||
/** Update the current protocol command being executed */
|
||||
else if (!process_client_commands(dcb, nbytes_read, &read_buffer))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** The query classifier classifies according to the service's server that has
|
||||
* the smallest version number. */
|
||||
@ -1027,6 +1009,30 @@ gw_read_normal_data(DCB *dcb, GWBUF *read_buffer, int nbytes_read)
|
||||
return rval;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a connection qualifies to be added into the persistent connection pool
|
||||
*
|
||||
* @param dcb The client DCB to check
|
||||
*/
|
||||
void check_pool_candidate(DCB* dcb)
|
||||
{
|
||||
MXS_SESSION *session = dcb->session;
|
||||
MySQLProtocol *proto = (MySQLProtocol*)dcb->protocol;
|
||||
|
||||
if (proto->current_command == MXS_COM_QUIT)
|
||||
{
|
||||
/** The client is closing the connection. We know that this will be the
|
||||
* last command the client sends so the backend connections are very likely
|
||||
* to be in an idle state.
|
||||
*
|
||||
* If the client is pipelining the queries (i.e. sending N request as
|
||||
* a batch and then expecting N responses) then it is possible that
|
||||
* the backend connections are not idle when the COM_QUIT is received.
|
||||
* In most cases we can assume that the connections are idle. */
|
||||
session_qualify_for_pool(session);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Client read event, common processing after single statement handling
|
||||
*
|
||||
@ -1047,25 +1053,10 @@ gw_read_finish_processing(DCB *dcb, GWBUF *read_buffer, uint64_t capabilities)
|
||||
/** Reset error handler when routing of the new query begins */
|
||||
dcb->dcb_errhandle_called = false;
|
||||
|
||||
if (proto->current_command == MXS_COM_QUIT)
|
||||
{
|
||||
/** The client is closing the connection. We know that this will be the
|
||||
* last command the client sends so the backend connections are very likely
|
||||
* to be in an idle state.
|
||||
*
|
||||
* If the client is pipelining the queries (i.e. sending N request as
|
||||
* a batch and then expecting N responses) then it is possible that
|
||||
* the backend connections are not idle when the COM_QUIT is received.
|
||||
* In most cases we can assume that the connections are idle. */
|
||||
session_qualify_for_pool(session);
|
||||
}
|
||||
|
||||
if (rcap_type_required(capabilities, RCAP_TYPE_STMT_INPUT))
|
||||
{
|
||||
/**
|
||||
* Feed each statement completely and separately
|
||||
* to router. The routing functions return 1 for
|
||||
* success or 0 for failure.
|
||||
* Feed each statement completely and separately to router.
|
||||
*/
|
||||
return_code = route_by_statement(session, capabilities, &read_buffer) ? 0 : 1;
|
||||
|
||||
@ -1080,9 +1071,10 @@ gw_read_finish_processing(DCB *dcb, GWBUF *read_buffer, uint64_t capabilities)
|
||||
}
|
||||
else if (NULL != session->router_session || (rcap_type_required(capabilities, RCAP_TYPE_NO_RSESSION)))
|
||||
{
|
||||
/** Feed whole packet to router, which will free it
|
||||
* and return 1 for success, 0 for failure
|
||||
*/
|
||||
/** Check if this connection qualifies for the connection pool */
|
||||
check_pool_candidate(dcb);
|
||||
|
||||
/** Feed the whole buffer to the router */
|
||||
return_code = MXS_SESSION_ROUTE_QUERY(session, read_buffer) ? 0 : 1;
|
||||
}
|
||||
/* else return_code is still 0 from when it was originally set */
|
||||
@ -1424,13 +1416,41 @@ static int gw_client_hangup_event(DCB *dcb)
|
||||
goto retblock;
|
||||
}
|
||||
|
||||
modutil_send_mysql_err_packet(dcb, 0, 0, 1927, "08S01", "Connection killed by MaxScale");
|
||||
if (!session_valid_for_pool(session))
|
||||
{
|
||||
// The client did not send a COM_QUIT packet
|
||||
modutil_send_mysql_err_packet(dcb, 0, 0, 1927, "08S01", "Connection killed by MaxScale");
|
||||
}
|
||||
dcb_close(dcb);
|
||||
|
||||
retblock:
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update protocol tracking information for an individual statement
|
||||
*
|
||||
* @param dcb Client DCB
|
||||
* @param buffer Buffer containing a single packet
|
||||
*/
|
||||
void update_current_command(DCB* dcb, GWBUF* buffer)
|
||||
{
|
||||
MySQLProtocol *proto = (MySQLProtocol*)dcb->protocol;
|
||||
uint8_t cmd = (uint8_t)MXS_COM_QUERY;
|
||||
|
||||
/**
|
||||
* As we are routing individual packets, we can extract the command byte here.
|
||||
* Empty packets are treated as COM_QUERY packets by default.
|
||||
*/
|
||||
gwbuf_copy_data(buffer, MYSQL_HEADER_LEN, 1, &cmd);
|
||||
proto->current_command = (mxs_mysql_cmd_t)cmd;
|
||||
|
||||
/**
|
||||
* Now that we have the current command, we can check if this connection
|
||||
* can be a candidate for the connection pool.
|
||||
*/
|
||||
check_pool_candidate(dcb);
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect if buffer includes partial mysql packet or multiple packets.
|
||||
@ -1464,21 +1484,11 @@ static int route_by_statement(MXS_SESSION* session, uint64_t capabilities, GWBUF
|
||||
{
|
||||
CHK_GWBUF(packetbuf);
|
||||
|
||||
MySQLProtocol* proto = (MySQLProtocol*)session->client_dcb->protocol;
|
||||
proto->current_command = (mxs_mysql_cmd_t)mxs_mysql_get_command(packetbuf);
|
||||
|
||||
/**
|
||||
* This means that buffer includes exactly one MySQL
|
||||
* statement.
|
||||
* backend func.write uses the information. MySQL backend
|
||||
* protocol, for example, stores the command identifier
|
||||
* to protocol structure. When some other thread reads
|
||||
* the corresponding response the command tells how to
|
||||
* handle response.
|
||||
*
|
||||
* Set it here instead of gw_read_client_event to make
|
||||
* sure it is set to each (MySQL) packet.
|
||||
* Update the currently command being executed.
|
||||
*/
|
||||
update_current_command(session->client_dcb, packetbuf);
|
||||
|
||||
if (rcap_type_required(capabilities, RCAP_TYPE_CONTIGUOUS_INPUT))
|
||||
{
|
||||
if (!GWBUF_IS_CONTIGUOUS(packetbuf))
|
||||
@ -1567,52 +1577,6 @@ return_rc:
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* if read queue existed appent read to it. if length of read buffer is less
|
||||
* than 3 or less than mysql packet then return. else copy mysql packets to
|
||||
* separate buffers from read buffer and continue. else if read queue didn't
|
||||
* exist, length of read is less than 3 or less than mysql packet then
|
||||
* create read queue and append to it and return. if length read is less than
|
||||
* mysql packet length append to read queue append to it and return.
|
||||
* else (complete packet was read) continue.
|
||||
*
|
||||
* @return True if we have a complete packet, otherwise false
|
||||
*/
|
||||
static bool ensure_complete_packet(DCB *dcb, GWBUF **read_buffer, int nbytes_read)
|
||||
{
|
||||
if (dcb_readq_has(dcb))
|
||||
{
|
||||
dcb_readq_append(dcb, *read_buffer);
|
||||
nbytes_read = dcb_readq_length(dcb);
|
||||
int plen = MYSQL_GET_PAYLOAD_LEN((uint8_t *) GWBUF_DATA(dcb_readq_get(dcb)));
|
||||
|
||||
if (nbytes_read < 3 || nbytes_read < plen + 4)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
/**
|
||||
* There is at least one complete mysql packet in
|
||||
* read_buffer.
|
||||
*/
|
||||
*read_buffer = dcb_readq_release(dcb);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
uint8_t* data = (uint8_t *) GWBUF_DATA(*read_buffer);
|
||||
|
||||
if (nbytes_read < 3 || nbytes_read < (int)MYSQL_GET_PAYLOAD_LEN(data) + 4)
|
||||
{
|
||||
dcb_readq_append(dcb, *read_buffer);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Some SQL commands/queries need to be detected and handled by the protocol
|
||||
* and MaxScale instead of being routed forward as is.
|
||||
|
||||
@ -657,6 +657,13 @@ avro_binlog_end_t avro_read_all_events(AVRO_INSTANCE *router)
|
||||
snprintf(next_file, sizeof(next_file), BINLOG_NAMEFMT, router->fileroot,
|
||||
blr_file_get_next_binlogname(router->binlog_name));
|
||||
}
|
||||
else if (hdr.event_type == MARIADB_ANNOTATE_ROWS_EVENT)
|
||||
{
|
||||
MXS_INFO("Annotate_rows_event: %.*s", hdr.event_size - BINLOG_EVENT_HDR_LEN, ptr);
|
||||
pos += original_size;
|
||||
router->current_pos = pos;
|
||||
continue;
|
||||
}
|
||||
else if (hdr.event_type == TABLE_MAP_EVENT)
|
||||
{
|
||||
handle_table_map_event(router, &hdr, ptr);
|
||||
@ -956,6 +963,8 @@ bool save_and_replace_table_create(AVRO_INSTANCE *router, TABLE_CREATE *created)
|
||||
{
|
||||
if (strcmp(key, table_ident) == 0)
|
||||
{
|
||||
TABLE_MAP* map = hashtable_fetch(router->table_maps, key);
|
||||
router->active_maps[map->id % MAX_MAPPED_TABLES] = NULL;
|
||||
hashtable_delete(router->table_maps, key);
|
||||
}
|
||||
}
|
||||
@ -1000,13 +1009,13 @@ void handle_query_event(AVRO_INSTANCE *router, REP_HEADER *hdr, int *pending_tra
|
||||
memcpy(db, (char*) ptr + PHDR_OFF + vblklen, dblen);
|
||||
db[dblen] = 0;
|
||||
|
||||
unify_whitespace(sql, len);
|
||||
size_t sqlsz = len, tmpsz = len;
|
||||
char *tmp = MXS_MALLOC(len);
|
||||
MXS_ABORT_IF_NULL(tmp);
|
||||
remove_mysql_comments((const char**)&sql, &sqlsz, &tmp, &tmpsz);
|
||||
sql = tmp;
|
||||
len = tmpsz;
|
||||
unify_whitespace(sql, len);
|
||||
|
||||
if (is_create_table_statement(router, sql, len))
|
||||
{
|
||||
|
||||
@ -104,72 +104,53 @@ bool handle_table_map_event(AVRO_INSTANCE *router, REP_HEADER *hdr, uint8_t *ptr
|
||||
{
|
||||
ss_dassert(create->columns > 0);
|
||||
TABLE_MAP *old = hashtable_fetch(router->table_maps, table_ident);
|
||||
TABLE_MAP *map = table_map_alloc(ptr, ev_len, create);
|
||||
MXS_ABORT_IF_NULL(map); // Fatal error at this point
|
||||
char* json_schema = json_new_schema_from_table(map);
|
||||
|
||||
if (old == NULL || old->version != create->version)
|
||||
if (json_schema)
|
||||
{
|
||||
TABLE_MAP *map = table_map_alloc(ptr, ev_len, create);
|
||||
char filepath[PATH_MAX + 1];
|
||||
snprintf(filepath, sizeof(filepath), "%s/%s.%06d.avro",
|
||||
router->avrodir, table_ident, map->version);
|
||||
|
||||
if (map)
|
||||
/** Close the file and open a new one */
|
||||
hashtable_delete(router->open_tables, table_ident);
|
||||
AVRO_TABLE *avro_table = avro_table_alloc(filepath, json_schema,
|
||||
codec_to_string(router->codec),
|
||||
router->block_size);
|
||||
|
||||
if (avro_table)
|
||||
{
|
||||
char* json_schema = json_new_schema_from_table(map);
|
||||
bool notify = old != NULL;
|
||||
|
||||
if (json_schema)
|
||||
if (old)
|
||||
{
|
||||
char filepath[PATH_MAX + 1];
|
||||
snprintf(filepath, sizeof(filepath), "%s/%s.%06d.avro",
|
||||
router->avrodir, table_ident, map->version);
|
||||
|
||||
/** Close the file and open a new one */
|
||||
hashtable_delete(router->open_tables, table_ident);
|
||||
AVRO_TABLE *avro_table = avro_table_alloc(filepath, json_schema,
|
||||
codec_to_string(router->codec),
|
||||
router->block_size);
|
||||
|
||||
if (avro_table)
|
||||
{
|
||||
bool notify = old != NULL;
|
||||
|
||||
if (old)
|
||||
{
|
||||
router->active_maps[old->id % MAX_MAPPED_TABLES] = NULL;
|
||||
}
|
||||
hashtable_delete(router->table_maps, table_ident);
|
||||
hashtable_add(router->table_maps, (void*) table_ident, map);
|
||||
hashtable_add(router->open_tables, table_ident, avro_table);
|
||||
save_avro_schema(router->avrodir, json_schema, map);
|
||||
router->active_maps[map->id % MAX_MAPPED_TABLES] = map;
|
||||
MXS_DEBUG("Table %s mapped to %lu", table_ident, map->id);
|
||||
rval = true;
|
||||
hashtable_add(router->table_maps, (void*)table_ident, map);
|
||||
hashtable_add(router->open_tables, table_ident, avro_table);
|
||||
save_avro_schema(router->avrodir, json_schema, map);
|
||||
router->active_maps[map->id % MAX_MAPPED_TABLES] = map;
|
||||
ss_dassert(router->active_maps[id % MAX_MAPPED_TABLES] == map);
|
||||
MXS_DEBUG("Table %s mapped to %lu", table_ident, map->id);
|
||||
rval = true;
|
||||
|
||||
if (notify)
|
||||
{
|
||||
notify_all_clients(router);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
MXS_ERROR("Failed to open new Avro file for writing.");
|
||||
}
|
||||
MXS_FREE(json_schema);
|
||||
}
|
||||
else
|
||||
if (notify)
|
||||
{
|
||||
MXS_ERROR("Failed to create JSON schema.");
|
||||
notify_all_clients(router);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
MXS_ERROR("Failed to allocate new table map.");
|
||||
MXS_ERROR("Failed to open new Avro file for writing.");
|
||||
}
|
||||
MXS_FREE(json_schema);
|
||||
}
|
||||
else
|
||||
{
|
||||
router->active_maps[old->id % MAX_MAPPED_TABLES] = NULL;
|
||||
table_map_remap(ptr, ev_len, old);
|
||||
router->active_maps[old->id % MAX_MAPPED_TABLES] = old;
|
||||
MXS_DEBUG("Table %s re-mapped to %lu", table_ident, old->id);
|
||||
/** No changes in the schema */
|
||||
rval = true;
|
||||
MXS_ERROR("Failed to create JSON schema.");
|
||||
}
|
||||
}
|
||||
else
|
||||
@ -363,8 +344,9 @@ bool handle_row_event(AVRO_INSTANCE *router, REP_HEADER *hdr, uint8_t *ptr)
|
||||
}
|
||||
else
|
||||
{
|
||||
MXS_ERROR("Row event and table map event have different column counts."
|
||||
" Only full row image is currently supported.");
|
||||
MXS_ERROR("Row event and table map event have different column "
|
||||
"counts for table %s.%s, only full row image is currently "
|
||||
"supported.", map->database, map->table);
|
||||
}
|
||||
}
|
||||
else
|
||||
@ -606,7 +588,6 @@ uint8_t* process_row_event_data(TABLE_MAP *map, TABLE_CREATE *create, avro_value
|
||||
}
|
||||
|
||||
MXS_INFO("[%ld] CHAR: field: %d bytes, data: %d bytes", i, field_length, bytes);
|
||||
ss_dassert(bytes || *ptr == '\0');
|
||||
char str[bytes + 1];
|
||||
memcpy(str, ptr, bytes);
|
||||
str[bytes] = '\0';
|
||||
|
||||
@ -321,11 +321,11 @@ void save_avro_schema(const char *path, const char* schema, TABLE_MAP *map)
|
||||
* @return Pointer to the start of the definition of NULL if the query is
|
||||
* malformed.
|
||||
*/
|
||||
static const char* get_table_definition(const char *sql, int* size)
|
||||
static const char* get_table_definition(const char *sql, int len, int* size)
|
||||
{
|
||||
const char *rval = NULL;
|
||||
const char *ptr = sql;
|
||||
const char *end = strchr(sql, '\0');
|
||||
const char *end = sql + len;
|
||||
while (ptr < end && *ptr != '(')
|
||||
{
|
||||
ptr++;
|
||||
@ -403,10 +403,12 @@ static bool get_table_name(const char* sql, char* dest)
|
||||
|
||||
/**
|
||||
* Extract the database name from a CREATE TABLE statement
|
||||
*
|
||||
* @param sql SQL statement
|
||||
* @param dest Destination where the database name is extracted. Must be at least
|
||||
* MYSQL_DATABASE_MAXLEN bytes long.
|
||||
* @return True if extraction was successful
|
||||
* MYSQL_DATABASE_MAXLEN bytes long.
|
||||
*
|
||||
* @return True if a database name was extracted
|
||||
*/
|
||||
static bool get_database_name(const char* sql, char* dest)
|
||||
{
|
||||
@ -426,22 +428,27 @@ static bool get_database_name(const char* sql, char* dest)
|
||||
ptr--;
|
||||
}
|
||||
|
||||
while (*ptr == '`' || *ptr == '.' || isspace(*ptr))
|
||||
if (*ptr == '.')
|
||||
{
|
||||
ptr--;
|
||||
// The query defines an explicit database
|
||||
|
||||
while (*ptr == '`' || *ptr == '.' || isspace(*ptr))
|
||||
{
|
||||
ptr--;
|
||||
}
|
||||
|
||||
const char* end = ptr + 1;
|
||||
|
||||
while (*ptr != '`' && *ptr != '.' && !isspace(*ptr))
|
||||
{
|
||||
ptr--;
|
||||
}
|
||||
|
||||
ptr++;
|
||||
memcpy(dest, ptr, end - ptr);
|
||||
dest[end - ptr] = '\0';
|
||||
rval = true;
|
||||
}
|
||||
|
||||
const char* end = ptr + 1;
|
||||
|
||||
while (*ptr != '`' && *ptr != '.' && !isspace(*ptr))
|
||||
{
|
||||
ptr--;
|
||||
}
|
||||
|
||||
ptr++;
|
||||
memcpy(dest, ptr, end - ptr);
|
||||
dest[end - ptr] = '\0';
|
||||
rval = true;
|
||||
}
|
||||
|
||||
return rval;
|
||||
@ -512,12 +519,16 @@ static const char *extract_field_name(const char* ptr, char* dest, size_t size)
|
||||
}
|
||||
}
|
||||
|
||||
if (strncasecmp(ptr, "constraint", 10) == 0 || strncasecmp(ptr, "index", 5) == 0 ||
|
||||
strncasecmp(ptr, "key", 3) == 0 || strncasecmp(ptr, "fulltext", 8) == 0 ||
|
||||
strncasecmp(ptr, "spatial", 7) == 0 || strncasecmp(ptr, "foreign", 7) == 0 ||
|
||||
strncasecmp(ptr, "unique", 6) == 0 || strncasecmp(ptr, "primary", 7) == 0)
|
||||
if (!bt)
|
||||
{
|
||||
return NULL;
|
||||
if (strncasecmp(ptr, "constraint", 10) == 0 || strncasecmp(ptr, "index", 5) == 0 ||
|
||||
strncasecmp(ptr, "key", 3) == 0 || strncasecmp(ptr, "fulltext", 8) == 0 ||
|
||||
strncasecmp(ptr, "spatial", 7) == 0 || strncasecmp(ptr, "foreign", 7) == 0 ||
|
||||
strncasecmp(ptr, "unique", 6) == 0 || strncasecmp(ptr, "primary", 7) == 0)
|
||||
{
|
||||
// Found a keyword
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
const char *start = ptr;
|
||||
@ -694,35 +705,42 @@ TABLE_CREATE* table_create_from_schema(const char* file, const char* db,
|
||||
* @param db Database where this query was executed
|
||||
* @return New CREATE_TABLE object or NULL if an error occurred
|
||||
*/
|
||||
TABLE_CREATE* table_create_alloc(const char* sql, int len, const char* event_db)
|
||||
TABLE_CREATE* table_create_alloc(const char* sql, int len, const char* db)
|
||||
{
|
||||
/** Extract the table definition so we can get the column names from it */
|
||||
int stmt_len = 0;
|
||||
const char* statement_sql = get_table_definition(sql, &stmt_len);
|
||||
const char* statement_sql = get_table_definition(sql, len, &stmt_len);
|
||||
ss_dassert(statement_sql);
|
||||
char table[MYSQL_TABLE_MAXLEN + 1];
|
||||
char database[MYSQL_DATABASE_MAXLEN + 1];
|
||||
const char *db = event_db;
|
||||
|
||||
const char* err = NULL;
|
||||
MXS_INFO("Create table: %.*s", len, sql);
|
||||
|
||||
if (!get_table_name(sql, table))
|
||||
if (!statement_sql)
|
||||
{
|
||||
MXS_ERROR("Malformed CREATE TABLE statement, could not extract table name: %s", sql);
|
||||
return NULL;
|
||||
err = "table definition";
|
||||
}
|
||||
else if (!get_table_name(sql, table))
|
||||
{
|
||||
err = "table name";
|
||||
}
|
||||
|
||||
/** The CREATE statement contains the database name */
|
||||
if (strlen(db) == 0)
|
||||
if (get_database_name(sql, database))
|
||||
{
|
||||
if (!get_database_name(sql, database))
|
||||
{
|
||||
MXS_ERROR("Malformed CREATE TABLE statement, could not extract "
|
||||
"database name: %s", sql);
|
||||
return NULL;
|
||||
}
|
||||
// The CREATE statement contains the database name
|
||||
db = database;
|
||||
}
|
||||
else if (*db == '\0')
|
||||
{
|
||||
// No explicit or current database
|
||||
err = "database name";
|
||||
}
|
||||
|
||||
if (err)
|
||||
{
|
||||
MXS_ERROR("Malformed CREATE TABLE statement, could not extract %s: %.*s", err, len, sql);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int* lengths = NULL;
|
||||
char **names = NULL;
|
||||
@ -893,6 +911,27 @@ static void remove_extras(char* str)
|
||||
ss_dassert(strlen(str) == len);
|
||||
}
|
||||
|
||||
|
||||
static void remove_backticks(char* src)
|
||||
{
|
||||
char* dest = src;
|
||||
|
||||
while (*src)
|
||||
{
|
||||
if (*src != '`')
|
||||
{
|
||||
// Non-backtick character, keep it
|
||||
*dest = *src;
|
||||
dest++;
|
||||
}
|
||||
|
||||
src++;
|
||||
}
|
||||
|
||||
ss_dassert(dest == src || (*dest != '\0' && dest < src));
|
||||
*dest = '\0';
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract both tables from a `CREATE TABLE t1 LIKE t2` statement
|
||||
*/
|
||||
@ -1095,10 +1134,12 @@ static bool tok_eq(const char *a, const char *b, size_t len)
|
||||
void read_alter_identifier(const char *sql, const char *end, char *dest, int size)
|
||||
{
|
||||
int len = 0;
|
||||
const char *tok = get_tok(sql, &len, end);
|
||||
if (tok && (tok = get_tok(tok + len, &len, end)) && (tok = get_tok(tok + len, &len, end)))
|
||||
const char *tok = get_tok(sql, &len, end); // ALTER
|
||||
if (tok && (tok = get_tok(tok + len, &len, end)) // TABLE
|
||||
&& (tok = get_tok(tok + len, &len, end))) // Table identifier
|
||||
{
|
||||
snprintf(dest, size, "%.*s", len, tok);
|
||||
remove_backticks(dest);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1174,20 +1215,33 @@ bool table_create_alter(TABLE_CREATE *create, const char *sql, const char *end)
|
||||
if (tok_eq(ptok, "add", plen) && tok_eq(tok, "column", len))
|
||||
{
|
||||
tok = get_tok(tok + len, &len, end);
|
||||
|
||||
create->column_names = MXS_REALLOC(create->column_names, sizeof(char*) * (create->columns + 1));
|
||||
create->column_types = MXS_REALLOC(create->column_types, sizeof(char*) * (create->columns + 1));
|
||||
create->column_lengths = MXS_REALLOC(create->column_lengths, sizeof(int) * (create->columns + 1));
|
||||
|
||||
char avro_token[len + 1];
|
||||
make_avro_token(avro_token, tok, len);
|
||||
char field_type[200] = ""; // Enough to hold all types
|
||||
int field_length = extract_type_length(tok + len, field_type);
|
||||
create->column_names[create->columns] = MXS_STRDUP_A(avro_token);
|
||||
create->column_types[create->columns] = MXS_STRDUP_A(field_type);
|
||||
create->column_lengths[create->columns] = field_length;
|
||||
create->columns++;
|
||||
updates++;
|
||||
bool is_new = true;
|
||||
|
||||
for (uint64_t i = 0; i < create->columns; i++)
|
||||
{
|
||||
if (strcmp(avro_token, create->column_names[i]) == 0)
|
||||
{
|
||||
is_new = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_new)
|
||||
{
|
||||
create->column_names = MXS_REALLOC(create->column_names, sizeof(char*) * (create->columns + 1));
|
||||
create->column_types = MXS_REALLOC(create->column_types, sizeof(char*) * (create->columns + 1));
|
||||
create->column_lengths = MXS_REALLOC(create->column_lengths, sizeof(int) * (create->columns + 1));
|
||||
|
||||
char field_type[200] = ""; // Enough to hold all types
|
||||
int field_length = extract_type_length(tok + len, field_type);
|
||||
create->column_names[create->columns] = MXS_STRDUP_A(avro_token);
|
||||
create->column_types[create->columns] = MXS_STRDUP_A(field_type);
|
||||
create->column_lengths[create->columns] = field_length;
|
||||
create->columns++;
|
||||
updates++;
|
||||
}
|
||||
tok = get_next_def(tok, end);
|
||||
len = 0;
|
||||
}
|
||||
@ -1393,20 +1447,3 @@ void table_map_free(TABLE_MAP *map)
|
||||
MXS_FREE(map);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Map a table to a different ID
|
||||
*
|
||||
* This updates the table ID that the @c TABLE_MAP object is assigned with
|
||||
*
|
||||
* @param ptr Pointer to the start of a table map event
|
||||
* @param hdr_len Post-header length
|
||||
* @param map Table map to remap
|
||||
*/
|
||||
void table_map_remap(uint8_t *ptr, uint8_t hdr_len, TABLE_MAP *map)
|
||||
{
|
||||
uint64_t table_id = 0;
|
||||
size_t id_size = hdr_len == 6 ? 4 : 6;
|
||||
memcpy(&table_id, ptr, id_size);
|
||||
map->id = table_id;
|
||||
}
|
||||
|
||||
@ -328,7 +328,6 @@ extern char* json_new_schema_from_table(TABLE_MAP *map);
|
||||
extern void save_avro_schema(const char *path, const char* schema, TABLE_MAP *map);
|
||||
extern bool handle_table_map_event(AVRO_INSTANCE *router, REP_HEADER *hdr, uint8_t *ptr);
|
||||
extern bool handle_row_event(AVRO_INSTANCE *router, REP_HEADER *hdr, uint8_t *ptr);
|
||||
extern void table_map_remap(uint8_t *ptr, uint8_t hdr_len, TABLE_MAP *map);
|
||||
|
||||
enum avrorouter_file_op
|
||||
{
|
||||
|
||||
@ -229,6 +229,7 @@ static void blr_start_master(void* data)
|
||||
return;
|
||||
}
|
||||
client->session = router->session;
|
||||
client->service = router->service;
|
||||
|
||||
/**
|
||||
* 'client' is the fake DCB that emulates a client session:
|
||||
@ -265,6 +266,7 @@ static void blr_start_master(void* data)
|
||||
return;
|
||||
}
|
||||
router->master->remote = MXS_STRDUP_A(router->service->dbref->server->name);
|
||||
router->master->service = router->service;
|
||||
|
||||
MXS_NOTICE("%s: attempting to connect to master"
|
||||
" server [%s]:%d, binlog='%s', pos=%lu%s%s",
|
||||
|
||||
Reference in New Issue
Block a user