/* * Copyright (c) 2016 MariaDB Corporation Ab * * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file and at www.mariadb.com/bsl11. * * Change Date: 2023-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2 or later of the General * Public License. */ #include "avrorouter.hh" #include "rpl.hh" #include #include #include #include #include void gtid_pos_t::extract(const REP_HEADER& hdr, uint8_t* ptr) { domain = extract_field(ptr + 8, 32); server_id = hdr.serverid; seq = extract_field(ptr, 64); event_num = 0; timestamp = hdr.timestamp; } bool gtid_pos_t::parse(const char* str) { bool rval = false; char buf[strlen(str) + 1]; strcpy(buf, str); char* saved, * dom = strtok_r(buf, ":-\n", &saved); char* serv_id = strtok_r(NULL, ":-\n", &saved); char* sequence = strtok_r(NULL, ":-\n", &saved); char* subseq = strtok_r(NULL, ":-\n", &saved); if (dom && serv_id && sequence) { domain = strtol(dom, NULL, 10); server_id = strtol(serv_id, NULL, 10); seq = strtol(sequence, NULL, 10); event_num = subseq ? strtol(subseq, NULL, 10) : 0; rval = true; } return rval; } gtid_pos_t gtid_pos_t::from_string(std::string str) { gtid_pos_t gtid; gtid.parse(str.c_str()); return gtid; } std::string gtid_pos_t::to_string() const { std::stringstream ss; ss << domain << "-" << server_id << "-" << seq; return ss.str(); } bool gtid_pos_t::empty() const { return timestamp == 0 && domain == 0 && server_id == 0 && seq == 0 && event_num == 0; } json_t* Column::to_json() const { json_t* obj = json_object(); json_object_set_new(obj, "name", json_string(name.c_str())); json_object_set_new(obj, "type", json_string(type.c_str())); json_object_set_new(obj, "length", json_integer(length)); return obj; } Column Column::from_json(json_t* json) { json_t* name = json_object_get(json, "name"); json_t* type = json_object_get(json, "type"); json_t* length = json_object_get(json, "length"); if (name && json_is_string(name) && type && json_is_string(type) && length && json_is_integer(length)) { return Column(json_string_value(name), json_string_value(type), json_integer_value(length)); } // Invalid JSON, return empty Column return Column(""); } json_t* TableCreateEvent::to_json() const { json_t* arr = json_array(); for (auto it = columns.begin(); it != columns.end(); it++) { json_array_append_new(arr, it->to_json()); } json_t* obj = json_object(); json_object_set_new(obj, "table", json_string(table.c_str())); json_object_set_new(obj, "database", json_string(database.c_str())); json_object_set_new(obj, "version", json_integer(version)); json_object_set_new(obj, "columns", arr); return obj; } STableCreateEvent TableCreateEvent::from_json(json_t* obj) { STableCreateEvent rval; json_t* table = json_object_get(obj, "table"); json_t* database = json_object_get(obj, "database"); json_t* version = json_object_get(obj, "version"); json_t* columns = json_object_get(obj, "columns"); if (json_is_string(table) && json_is_string(database) && json_is_integer(version) && json_is_array(columns)) { std::string tbl = json_string_value(table); std::string db = json_string_value(database); int ver = json_integer_value(version); std::vector cols; size_t i = 0; json_t* val; json_array_foreach(columns, i, val) { cols.emplace_back(Column::from_json(val)); } auto is_empty = [](const Column& col) { return col.name.empty(); }; if (std::none_of(cols.begin(), cols.end(), is_empty)) { rval.reset(new TableCreateEvent(db, tbl, ver, std::move(cols))); } } return rval; } /** * Extract the table definition from a CREATE TABLE statement * @param sql The SQL statement * @param size Length of the statement * @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 len, int* size) { const char* rval = NULL; const char* ptr = sql; const char* end = sql + len; while (ptr < end && *ptr != '(') { ptr++; } /** We assume at least the parentheses are in the statement */ if (ptr < end - 2) { int depth = 0; ptr++; const char* start = ptr; // Skip first parenthesis while (ptr < end) { switch (*ptr) { case '(': depth++; break; case ')': depth--; break; default: break; } /** We found the last closing parenthesis */ if (depth < 0) { *size = ptr - start; rval = start; break; } ptr++; } } return rval; } /** * Extract the table name from a CREATE TABLE statement * @param sql SQL statement * @param dest Destination where the table name is extracted. Must be at least * MYSQL_TABLE_MAXLEN bytes long. * @return True if extraction was successful */ static bool get_table_name(const char* sql, char* dest) { bool rval = false; const char* ptr = strchr(sql, '('); if (ptr) { ptr--; while (*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; } return rval; } /** * 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 a database name was extracted */ static bool get_database_name(const char* sql, char* dest) { bool rval = false; const char* ptr = strchr(sql, '('); if (ptr) { ptr--; while (ptr >= sql && (*ptr == '`' || isspace(*ptr))) { ptr--; } while (ptr >= sql && *ptr != '`' && *ptr != '.' && !isspace(*ptr)) { ptr--; } while (ptr >= sql && (*ptr == '`' || isspace(*ptr))) { ptr--; } if (ptr >= sql && *ptr == '.') { // The query defines an explicit database while (ptr >= sql && (*ptr == '`' || *ptr == '.' || isspace(*ptr))) { ptr--; } const char* end = ptr + 1; while (ptr >= sql && *ptr != '`' && *ptr != '.' && !isspace(*ptr)) { ptr--; } ptr++; memcpy(dest, ptr, end - ptr); dest[end - ptr] = '\0'; rval = true; } } return rval; } void make_valid_avro_identifier(char* ptr) { while (*ptr) { if (!isalnum(*ptr) && *ptr != '_') { *ptr = '_'; } ptr++; } } const char* next_field_definition(const char* ptr) { int depth = 0; bool quoted = false; char qchar; while (*ptr) { if (!quoted) { if (*ptr == '(') { depth++; } else if (*ptr == ')') { depth--; } else if (*ptr == '"' || *ptr == '\'') { qchar = *ptr; quoted = true; } else if (*ptr == ',' && depth == 0 && !quoted) { ptr++; break; } } else if (qchar == *ptr) { quoted = false; } ptr++; } return ptr; } static const char* extract_field_name(const char* ptr, char* dest, size_t size) { bool bt = false; while (*ptr && (isspace(*ptr) || (bt = *ptr == '`'))) { ptr++; if (bt) { break; } } if (!bt) { 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; if (!bt) { while (*ptr && !isspace(*ptr)) { ptr++; } } else { while (*ptr && *ptr != '`') { ptr++; } } if (ptr > start) { /** Valid identifier */ size_t bytes = ptr - start; memcpy(dest, start, bytes); dest[bytes] = '\0'; make_valid_avro_identifier(dest); mxb_assert(strlen(dest) > 0); } else { ptr = NULL; } return ptr; } int extract_type_length(const char* ptr, char* dest) { /** Skip any leading whitespace */ while (*ptr && (isspace(*ptr) || *ptr == '`')) { ptr++; } /** The field type definition starts here */ const char* start = ptr; /** Skip characters until we either hit a whitespace character or the start * of the length definition. */ while (*ptr && isalpha(*ptr)) { ptr++; } /** Store type */ for (const char* c = start; c < ptr; c++) { *dest++ = tolower(*c); } *dest++ = '\0'; /** Skip whitespace */ while (*ptr && isspace(*ptr)) { ptr++; } int rval = -1; // No length defined /** Start of length definition */ if (*ptr == '(') { ptr++; char* end; int val = strtol(ptr, &end, 10); if (*end == ')') { rval = val; } } return rval; } int count_columns(const char* ptr) { int i = 2; while ((ptr = strchr(ptr, ','))) { ptr++; i++; } return i; } /** * Process a table definition into an array of column names * @param nameptr table definition * @return Number of processed columns or -1 on error */ static void process_column_definition(const char* nameptr, std::vector& columns) { char colname[512]; while ((nameptr = extract_field_name(nameptr, colname, sizeof(colname)))) { char type[100] = ""; int len = extract_type_length(nameptr, type); nameptr = next_field_definition(nameptr); fix_reserved_word(colname); columns.emplace_back(colname, type, len); } } int resolve_table_version(const char* db, const char* table) { int version = 0; char buf[PATH_MAX + 1]; do { version++; snprintf(buf, sizeof(buf), "%s.%s.%06d.avsc", db, table, version); } while (access(buf, F_OK) == 0); return version; } /** * @brief Handle a query event which contains a CREATE TABLE statement * * @param ident Table identifier in database.table format * @param sql The CREATE TABLE statement * @param len Length of @c sql * * @return New CREATE_TABLE object or NULL if an error occurred */ STableCreateEvent table_create_alloc(char* ident, const char* sql, int len) { /** 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, len, &stmt_len); mxb_assert(statement_sql); char* tbl_start = strchr(ident, '.'); mxb_assert(tbl_start); *tbl_start++ = '\0'; char table[MYSQL_TABLE_MAXLEN + 1]; char database[MYSQL_DATABASE_MAXLEN + 1]; strcpy(database, ident); strcpy(table, tbl_start); std::vector columns; process_column_definition(statement_sql, columns); STableCreateEvent rval; if (!columns.empty()) { int version = resolve_table_version(database, table); rval.reset(new(std::nothrow) TableCreateEvent(database, table, version, std::move(columns))); } else { MXS_ERROR("No columns in a CREATE TABLE statement: %.*s", stmt_len, statement_sql); } return rval; } /** * @brief Extract a table map from a table map event * * This assumes that the complete event minus the replication header is stored * at @p ptr * @param ptr Pointer to the start of the event payload * @param post_header_len Length of the event specific header, 8 or 6 bytes * @return New TABLE_MAP or NULL if memory allocation failed */ TableMapEvent* table_map_alloc(uint8_t* ptr, uint8_t hdr_len, TableCreateEvent* create) { uint64_t table_id = 0; size_t id_size = hdr_len == 6 ? 4 : 6; memcpy(&table_id, ptr, id_size); ptr += id_size; uint16_t flags = 0; memcpy(&flags, ptr, 2); ptr += 2; uint8_t schema_name_len = *ptr++; char schema_name[schema_name_len + 2]; /** Copy the NULL byte after the schema name */ memcpy(schema_name, ptr, schema_name_len + 1); ptr += schema_name_len + 1; uint8_t table_name_len = *ptr++; char table_name[table_name_len + 2]; /** Copy the NULL byte after the table name */ memcpy(table_name, ptr, table_name_len + 1); ptr += table_name_len + 1; uint64_t column_count = mxq::leint_value(ptr); ptr += mxq::leint_bytes(ptr); /** Column types */ uint8_t* column_types = ptr; ptr += column_count; size_t metadata_size = 0; uint8_t* metadata = (uint8_t*) mxq::lestr_consume(&ptr, &metadata_size); uint8_t* nullmap = ptr; size_t nullmap_size = (column_count + 7) / 8; Bytes cols(column_types, column_types + column_count); Bytes nulls(nullmap, nullmap + nullmap_size); Bytes meta(metadata, metadata + metadata_size); return new(std::nothrow) TableMapEvent(schema_name, table_name, table_id, create->version, std::move(cols), std::move(nulls), std::move(meta)); } Rpl::Rpl(SERVICE* service, SRowEventHandler handler, pcre2_code* match, pcre2_code* exclude, gtid_pos_t gtid) : m_handler(handler) , m_service(service) , m_binlog_checksum(0) , m_event_types(0) , m_gtid(gtid) , m_match(match) , m_exclude(exclude) , m_md_match(m_match ? pcre2_match_data_create_from_pattern(m_match, NULL) : nullptr) , m_md_exclude(m_exclude ? pcre2_match_data_create_from_pattern(m_exclude, NULL) : nullptr) { /** For detection of CREATE/ALTER TABLE statements */ static const char* create_table_regex = "(?i)^[[:space:]]*create[a-z0-9[:space:]_]+table"; static const char* alter_table_regex = "(?i)^[[:space:]]*alter[[:space:]]+table"; int pcreerr; size_t erroff; m_create_table_re = pcre2_compile((PCRE2_SPTR) create_table_regex, PCRE2_ZERO_TERMINATED, 0, &pcreerr, &erroff, NULL); m_alter_table_re = pcre2_compile((PCRE2_SPTR) alter_table_regex, PCRE2_ZERO_TERMINATED, 0, &pcreerr, &erroff, NULL); mxb_assert_message(m_create_table_re && m_alter_table_re, "CREATE TABLE and ALTER TABLE regex compilation should not fail"); } void Rpl::flush() { m_handler->flush_tables(); } void Rpl::add_create(STableCreateEvent create) { auto it = m_created_tables.find(create->id()); if (it == m_created_tables.end() || create->version > it->second->version) { m_created_tables[create->id()] = create; } } /** * Read one token (i.e. SQL keyword) */ static const char* get_token(const char* ptr, const char* end, char* dest) { while (ptr < end && isspace(*ptr)) { ptr++; } const char* start = ptr; while (ptr < end && !isspace(*ptr)) { ptr++; } size_t len = ptr - start; memcpy(dest, start, len); dest[len] = '\0'; return ptr; } /** * Consume one token */ static bool chomp_one_token(const char* expected, const char** ptr, const char* end, char* buf) { bool rval = false; const char* next = get_token(*ptr, end, buf); if (strcasecmp(buf, expected) == 0) { rval = true; *ptr = next; } return rval; } /** * Consume all tokens in a group */ static bool chomp_tokens(const char** tokens, const char** ptr, const char* end, char* buf) { bool next = true; bool rval = false; do { next = false; for (int i = 0; tokens[i]; i++) { if (chomp_one_token(tokens[i], ptr, end, buf)) { rval = true; next = true; break; } } } while (next); return rval; } /** * Remove any extra characters from a string */ static void remove_extras(char* str) { char* end = strchr(str, '\0') - 1; while (end > str && (*end == '`' || *end == ')' || *end == '(')) { *end-- = '\0'; } char* start = str; while (start < end && (*start == '`' || *start == ')' || *start == '(')) { start++; } size_t len = strlen(start); memmove(str, start, len); str[len] = '\0'; mxb_assert(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++; } mxb_assert(dest == src || (*dest != '\0' && dest < src)); *dest = '\0'; } static const char* TOK_CREATE[] = { "CREATE", NULL }; static const char* TOK_TABLE[] = { "TABLE", NULL }; static const char* TOK_GROUP_REPLACE[] = { "OR", "REPLACE", NULL }; static const char* TOK_GROUP_EXISTS[] = { "IF", "NOT", "EXISTS", NULL }; /** * Extract both tables from a `CREATE TABLE t1 LIKE t2` statement */ static bool extract_create_like_identifier(const char* sql, size_t len, char* target, char* source) { bool rval = false; char buffer[len + 1]; buffer[0] = '\0'; const char* ptr = sql; const char* end = ptr + sizeof(buffer); if (chomp_tokens(TOK_CREATE, &ptr, end, buffer)) { chomp_tokens(TOK_GROUP_REPLACE, &ptr, end, buffer); if (chomp_tokens(TOK_TABLE, &ptr, end, buffer)) { chomp_tokens(TOK_GROUP_EXISTS, &ptr, end, buffer); // Read the target table name ptr = get_token(ptr, end, buffer); strcpy(target, buffer); // Skip the LIKE token ptr = get_token(ptr, end, buffer); // Read the source table name ptr = get_token(ptr, end, buffer); remove_extras(buffer); strcpy(source, buffer); rval = true; } } return rval; } /** * Create a table from another table */ STableCreateEvent Rpl::table_create_copy(const char* sql, size_t len, const char* db) { STableCreateEvent rval; char target[MYSQL_TABLE_MAXLEN + 1] = ""; char source[MYSQL_TABLE_MAXLEN + 1] = ""; if (extract_create_like_identifier(sql, len, target, source)) { char table_ident[MYSQL_TABLE_MAXLEN + MYSQL_DATABASE_MAXLEN + 2] = ""; if (strchr(source, '.') == NULL) { strcpy(table_ident, db); strcat(table_ident, "."); } strcat(table_ident, source); auto it = m_created_tables.find(table_ident); if (it != m_created_tables.end()) { rval.reset(new(std::nothrow) TableCreateEvent(*it->second)); char* table = strchr(target, '.'); table = table ? table + 1 : target; rval->table = table; rval->version = 1; rval->was_used = false; } else { MXS_ERROR("Could not find table '%s' that '%s' is being created from: %.*s", table_ident, target, (int)len, sql); } } return rval; } static const char* get_next_def(const char* sql, const char* end) { int depth = 0; while (sql < end) { if (*sql == ',' && depth == 0) { return sql + 1; } else if (*sql == '(') { depth++; } else if (*sql == ')') { depth--; } sql++; } return NULL; } static const char* get_tok(const char* sql, int* toklen, const char* end) { const char* start = sql; while (isspace(*start)) { start++; } int len = 0; int depth = 0; while (start + len < end) { if (isspace(start[len]) && depth == 0) { *toklen = len; return start; } else if (start[len] == '(') { depth++; } else if (start[len] == ')') { depth--; } len++; } if (len > 0 && start + len <= end) { *toklen = len; return start; } return NULL; } static void rskip_whitespace(const char* sql, const char** end) { const char* ptr = *end; while (ptr > sql && isspace(*ptr)) { ptr--; } *end = ptr; } static void rskip_token(const char* sql, const char** end) { const char* ptr = *end; while (ptr > sql && !isspace(*ptr)) { ptr--; } *end = ptr; } static bool get_placement_specifier(const char* sql, const char* end, const char** tgt, int* tgt_len) { bool rval = false; mxb_assert(end > sql); end--; *tgt = NULL; *tgt_len = 0; // Skip any trailing whitespace rskip_whitespace(sql, &end); if (*end == '`') { // Identifier, possibly AFTER `column` const char* id_end = end; end--; while (end > sql && *end != '`') { end--; } const char* id_start = end + 1; mxb_assert(*end == '`' && *id_end == '`'); end--; rskip_whitespace(sql, &end); rskip_token(sql, &end); // end points to the character _before_ the token end++; if (strncasecmp(end, "AFTER", 5) == 0) { // This column comes after the specified column rval = true; *tgt = id_start; *tgt_len = id_end - id_start; } } else { // Something else, possibly FIRST or un-backtick'd AFTER const char* id_end = end + 1; // Points to either a trailing space or one-after-the-end rskip_token(sql, &end); // end points to the character _before_ the token end++; if (strncasecmp(end, "FIRST", 5) == 0) { // Put this column first rval = true; } else { const char* id_start = end + 1; // Skip the whitespace and until the start of the current token rskip_whitespace(sql, &end); rskip_token(sql, &end); // end points to the character _before_ the token end++; if (strncasecmp(end, "AFTER", 5) == 0) { // This column comes after the specified column rval = true; *tgt = id_start; *tgt_len = id_end - id_start; } } } return rval; } static bool tok_eq(const char* a, const char* b, size_t len) { size_t i = 0; while (i < len) { if (tolower(a[i]) - tolower(b[i]) != 0) { return false; } i++; } return true; } static void skip_whitespace(const char** saved) { const char* ptr = *saved; while (*ptr && isspace(*ptr)) { ptr++; } *saved = ptr; } static void skip_token(const char** saved) { const char* ptr = *saved; while (*ptr && !isspace(*ptr) && *ptr != '(' && *ptr != '.') { ptr++; } *saved = ptr; } static void skip_non_backtick(const char** saved) { const char* ptr = *saved; while (*ptr && *ptr != '`') { ptr++; } *saved = ptr; } const char* keywords[] = { "CREATE", "DROP", "ALTER", "IF", "EXISTS", "REPLACE", "OR", "TABLE", "NOT", NULL }; static bool token_is_keyword(const char* tok, size_t len) { for (int i = 0; keywords[i]; i++) { if (strncasecmp(keywords[i], tok, len) == 0 && strlen(keywords[i]) == len) { return true; } } return false; } void read_table_identifier(const char* db, const char* sql, const char* end, char* dest, int size) { const char* start; int len = 0; bool is_keyword = true; while (is_keyword) { skip_whitespace(&sql); // Leading whitespace if (*sql == '`') { // Quoted identifier, not a keyword is_keyword = false; sql++; start = sql; skip_non_backtick(&sql); len = sql - start; sql++; } else { start = sql; skip_token(&sql); len = sql - start; is_keyword = token_is_keyword(start, len); } } skip_whitespace(&sql); // Space after first identifier if (*sql != '.') { // No explicit database snprintf(dest, size, "%s.%.*s", db, len, start); } else { // Explicit database, skip the period sql++; skip_whitespace(&sql); // Space after first identifier const char* id_start; int id_len = 0; if (*sql == '`') { sql++; id_start = sql; skip_non_backtick(&sql); id_len = sql - id_start; sql++; } else { id_start = sql; skip_token(&sql); id_len = sql - id_start; } snprintf(dest, size, "%.*s.%.*s", len, start, id_len, id_start); } } void make_avro_token(char* dest, const char* src, int length) { while (length > 0 && (*src == '(' || *src == ')' || *src == '`' || isspace(*src))) { src++; length--; } const char* end = src; for (int i = 0; i < length; i++) { if (end[i] == '(' || end[i] == ')' || end[i] == '`' || isspace(end[i])) { length = i; break; } } memcpy(dest, src, length); dest[length] = '\0'; fix_reserved_word(dest); } static bool not_column_operation(const char* tok, int len) { const char* keywords[] = { "PRIMARY", "UNIQUE", "FULLTEXT", "SPATIAL", "PERIOD", "PRIMARY", "KEY", "KEYS", "INDEX", "FOREIGN", "CONSTRAINT", NULL }; for (int i = 0; keywords[i]; i++) { if (tok_eq(tok, keywords[i], strlen(keywords[i]))) { return true; } } return false; } bool Rpl::table_create_alter(STableCreateEvent create, const char* sql, const char* end) { const char* tbl = strcasestr(sql, "table"), * def; if ((def = strchr(tbl, ' '))) { int len = 0; const char* tok = get_tok(def, &len, end); if (tok) { MXS_INFO("Alter table '%.*s'; %.*s\n", len, tok, (int)(end - sql), sql); def = tok + len; } int updates = 0; while (tok && (tok = get_tok(tok + len, &len, end))) { const char* ptok = tok; int plen = len; tok = get_tok(tok + len, &len, end); if (tok) { if (not_column_operation(tok, len)) { MXS_INFO("Statement doesn't affect columns, not processing: %s", sql); return true; } else if (tok_eq(tok, "column", len)) { // Skip the optional COLUMN keyword tok = get_tok(tok + len, &len, end); } char avro_token[len + 1]; make_avro_token(avro_token, tok, len); if (tok_eq(ptok, "add", plen)) { bool is_new = true; for (auto it = create->columns.begin(); it != create->columns.end(); it++) { if (it->name == avro_token) { is_new = false; break; } } if (is_new) { char field_type[200] = ""; // Enough to hold all types int field_length = extract_type_length(tok + len, field_type); create->columns.emplace_back(std::string(avro_token), std::string(field_type), field_length); updates++; } tok = get_next_def(tok, end); len = 0; } else if (tok_eq(ptok, "drop", plen)) { for (auto it = create->columns.begin(); it != create->columns.end(); it++) { if (it->name == avro_token) { create->columns.erase(it); break; } } updates++; tok = get_next_def(tok, end); len = 0; } else if (tok_eq(ptok, "change", plen)) { for (auto it = create->columns.begin(); it != create->columns.end(); it++) { if (it->name == avro_token) { if ((tok = get_tok(tok + len, &len, end))) { 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); it->name = avro_token; it->type = field_type; it->length = field_length; updates++; } } } tok = get_next_def(tok, end); len = 0; } } else { break; } } /** Only increment the create version if it has an associated .avro * file. The .avro file is only created if it is actually used. */ if (updates > 0 && create->was_used) { create->version++; create->was_used = false; /** * Although the table was only altered, we can treat it like it * would have been just created. This allows for a minimal API that * only creates and replaces tables. * * TODO: Add DROP TABLE entry point for pruning old tables */ m_handler->create_table(create); } } return true; } bool Rpl::table_matches(const std::string& ident) { bool rval = false; if (!m_match || pcre2_match(m_match, (PCRE2_SPTR)ident.c_str(), PCRE2_ZERO_TERMINATED, 0, 0, m_md_match, NULL) > 0) { if (!m_exclude || pcre2_match(m_exclude, (PCRE2_SPTR)ident.c_str(), PCRE2_ZERO_TERMINATED, 0, 0, m_md_exclude, NULL) == PCRE2_ERROR_NOMATCH) { rval = true; } } return rval; }