/* Copyright (c) 2007, 2024, Oracle and/or its affiliates. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, as published by the Free Software Foundation. This program is designed to work with certain software (including but not limited to OpenSSL) that is licensed under separate terms, as designated in a particular file or component or in included license documentation. The authors of MySQL hereby grant you an additional permission to link the program and your derivative works with the separately licensed software that they have either included with the program or referenced in the documentation. 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, version 2.0, 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 St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef RPL_RECORD_H #define RPL_RECORD_H #include #include #include "my_inttypes.h" class Relay_log_info; struct TABLE; struct MY_BITMAP; class table_def; class Bit_reader; enum class enum_row_image_type { WRITE_AI, UPDATE_BI, UPDATE_AI, DELETE_BI }; #if defined(MYSQL_SERVER) size_t pack_row(TABLE *table, MY_BITMAP const *cols, uchar *row_data, const uchar *data, enum_row_image_type row_image_type, ulonglong value_options = 0); /** Unpack a row image (either before-image or after-image) into @c table->record[0]. The row is assumed to only consist of the fields for which the corresponding bit in bitset @c column_image is set; the other parts of the record are left alone. If the replica table has more columns than the source table, then the extra columns are not touched by this function. If the source table has more columns than the replica table, then the position is moved to after the extra columns, but the values are not used. If the replica has a GIPK and the source does not, then the extra column is not touched by this function. If the source table has a GIPK and the replica does not, then the position is shifted forward by 1. - The layout of a row is: For WRITE_ROWS_EVENT: +--------------+ | after-image | +--------------+ For DELETE_ROWS_EVENT: +--------------+ | before-image | +--------------+ For UPDATE_ROWS_EVENT: +--------------+-------------+ | before-image | after-image | +--------------+-------------+ For PARTIAL_UPDATE_ROWS_EVENT: +--------------+--------------+-------------+ | before-image | shared-image | after-image | +--------------+--------------+-------------+ - Each of before-image and after-image has the following format: +--------+-------+-------+ +-------+ | length | col_1 | col_2 | ... | col_N | +--------+-------+-------+ +-------+ length is a 4-byte integer in little-endian format, equal to the total length in bytes of col_1, col_2, ..., col_N. - The shared-image has one of the following formats: +-----------------+ | value_options=0 | +-----------------+ or +-----------------+--------------+ | value_options=1 | partial_bits | +-----------------+--------------+ where: - value_options is a bitmap, stored as an integer, in the format of net_field_length. Currently only one bit is allowed: 1=PARTIAL_JSON_UPDATES (so therefore the integer is always 0 or 1, so in reality value_options is only one byte). When PARTIAL_JSON_UPDATES=0, there is nothing else in the shared-image. When PARTIAL_JSON_UPDATES=1, there is a partial_bits field. - partial_bits has one bit for each *JSON* column in the table (regardless of whether it is included in the before-image and/or after-image). The bit is 0 if the JSON update is stored as a full document in the after-image, and 1 if the JSON update in partial form in the after-image. - Both when reading the before-image and when reading the after-image it is necessary to know the partialness of JSON columns: when reading the before-image, before looking up the row in the table, we need to set the column in the table's 'read_set' (even if the column was not in the before-image), in order to guarantee that the storage engine reads that column, so that there is any base document that the diff can be applied on. When reading the after-image, we need to know which columns are partial so that we can correctly parse the data for that column. Therefore, when this function parses the before-image of a PARTIAL_UPDATE_ROWS_LOG_EVENT, it reads both the before-image and the shared-image, but leaves the read position after the before-image. So when it parses the after-image of a PARTIAL_UPDATE_ROWS_LOG_EVENT, the read position is at the beginning of the shared-image, so it parses both the shared-image and the after-image. @param[in] rli Applier execution context @param[in,out] table Table to unpack into @param[in] source_column_count Number of columns that the source had in its table @param[in] row_data Beginning of the row image @param[in] column_image Pointer to a bit vector where the N'th bit is 0 for columns that are not included in the event, and 1 for columns that are included in the event. @param[out] row_image_end_p If this function returns successfully, it sets row_image_end to point to the next byte after the row image that it has read. @param[in] event_end Pointer to the end of the event. @param[in] row_image_type The type of row image that we are going to read: WRITE_AI, UPDATE_BI, UPDATE_AI, or DELETE_BI. @param[in] event_has_value_options true for PARTIAL_UPDATE_ROWS_EVENT, false for UPDATE_ROWS_EVENT. @param only_seek If true, this is a seek operation rather than a read operation. It will only compute the row_image_end_p pointer, and not read anything into the table and not apply any JSON diffs. (This is used in HASH_SCAN, which (1) unpacks and hashes the before-image for all rows in the event, (2)vscans the table, and for each matching row it (3) unpacks the after-image and applies on the table. In step (1) it needs to unpack the after-image too, in order to move the read position forwards, and then it should use only_seek=true. This is an optimization, but more importantly, when the after-image contains partial JSON, the partial JSON cannot be applied in step (1) since there is no JSON document to apply it on.) @returns false on success, true on error. */ bool unpack_row(Relay_log_info const *rli, TABLE *table, uint const source_column_count, uchar const *const row_data, MY_BITMAP const *column_image, uchar const **const row_image_end_p, uchar const *const event_end, enum_row_image_type row_image_type, bool event_has_value_options, bool only_seek); /** Return a pointer within a row event's row data, to the data of the first column that exists on the replica. This skips the 'null bits' field, which precedes the column definitions in the row image. In case a GIPK exists in the event but not in this replica's table definition, it skips the GIPK too. @param raw_data The data received from the source @param column_image The column bitmap @param column_count The number of column in the image bitmap @param null_bits The bits that are null @param tabledef The source table definition structure @param source_has_gipk If the source table has a GIPK @param replica_has_gipk If the replica table has a GIPK @return const uchar* The adjusted point to the source data */ const uchar *translate_beginning_of_raw_data( const uchar *raw_data, MY_BITMAP const *column_image, size_t column_count, Bit_reader &null_bits, table_def *tabledef, bool source_has_gipk, bool replica_has_gipk); // Fill table's record[0] with default values. int prepare_record(TABLE *const table, const MY_BITMAP *cols, const bool check); #endif /** Template base class of Bit_reader / Bit_writer. */ template class Bit_stream_base { protected: /// Pointer to beginning of buffer where bits are read or written. T *m_ptr; /// Current position in buffer. uint m_current_bit; public: /** Construct a new Bit_stream (either reader or writer). @param ptr Pointer where bits will be read or written. */ Bit_stream_base(T *ptr) : m_ptr(ptr), m_current_bit(0) {} /** Set the buffer pointer. @param ptr Pointer where bits will be read or written. */ void set_ptr(T *ptr) { m_ptr = ptr; } /** Set the buffer pointer, using an unsigned datatype. @param ptr Pointer where bits will be read or written. */ void set_ptr(UT *ptr) { m_ptr = (T *)ptr; } /// @return the current position. uint tell() const { return m_current_bit; } /** Print all the bits before the current position to the debug trace. @param str Descriptive text that will be prefixed before the bit string. */ void dbug_print(const char *str [[maybe_unused]]) const; }; /** Auxiliary class to write a stream of bits to a memory location. Call set() to write a bit and move the position one bit forward. */ class Bit_writer : public Bit_stream_base { public: Bit_writer(char *ptr = nullptr) : Bit_stream_base(ptr) {} Bit_writer(uchar *ptr) : Bit_writer((char *)ptr) {} /** Write the next bit and move the write position one bit forward. @param set_to_on If true, set the bit to 1, otherwise set it to 0. */ void set(bool set_to_on) { uint byte = m_current_bit / 8; uint bit_within_byte = m_current_bit % 8; m_current_bit++; if (bit_within_byte == 0) m_ptr[byte] = set_to_on ? 1 : 0; else if (set_to_on) m_ptr[byte] |= 1 << bit_within_byte; } }; /** Auxiliary class to read or write a stream of bits to a memory location. Call get() to read a bit and move the position one bit forward. */ class Bit_reader : public Bit_stream_base { public: Bit_reader(const char *ptr = nullptr) : Bit_stream_base(ptr) {} Bit_reader(const uchar *ptr) : Bit_reader((const char *)ptr) {} /** Read the next bit and move the read position one bit forward. @return true if the bit was 1, false if the bit was 0. */ bool get() { uint byte = m_current_bit / 8; uint bit_within_byte = m_current_bit % 8; m_current_bit++; return (m_ptr[byte] & (1 << bit_within_byte)) != 0; } }; #endif // ifdef RPL_RECORD_H