X-Git-Url: https://git.mdrn.pl/wl-app.git/blobdiff_plain/53b27422d140022594fc241cca91c3183be57bca..48b2fe9f7c2dc3d9aeaaa6dbfb27c7da4f3235ff:/iOS/Pods/Realm/include/core/realm/impl/transact_log.hpp?ds=sidebyside diff --git a/iOS/Pods/Realm/include/core/realm/impl/transact_log.hpp b/iOS/Pods/Realm/include/core/realm/impl/transact_log.hpp new file mode 100644 index 0000000..ee3c710 --- /dev/null +++ b/iOS/Pods/Realm/include/core/realm/impl/transact_log.hpp @@ -0,0 +1,2808 @@ +/************************************************************************* + * + * Copyright 2016 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **************************************************************************/ + +#ifndef REALM_IMPL_TRANSACT_LOG_HPP +#define REALM_IMPL_TRANSACT_LOG_HPP + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace realm { +namespace _impl { + +/// Transaction log instruction encoding +/// NOTE: Any change to this enum is a file-format breaking change. +enum Instruction { + instr_InsertGroupLevelTable = 1, + instr_EraseGroupLevelTable = 2, // Remove columnless table from group + instr_RenameGroupLevelTable = 3, + instr_MoveGroupLevelTable = 4, // UNSUPPORTED/UNUSED. FIXME: remove in next breaking change + instr_SelectTable = 5, + instr_Set = 6, + instr_SetUnique = 7, + instr_SetDefault = 8, + instr_AddInteger = 9, // Add value to integer field + instr_NullifyLink = 10, // Set link to null due to target being erased + instr_InsertSubstring = 11, + instr_EraseFromString = 12, + instr_InsertEmptyRows = 13, + instr_EraseRows = 14, // Remove (multiple) rows + instr_SwapRows = 15, + instr_MoveRow = 16, + instr_MergeRows = 17, // Replace links pointing to row A with links to row B + instr_ClearTable = 18, // Remove all rows in selected table + instr_OptimizeTable = 19, + instr_SelectDescriptor = 20, // Select descriptor from currently selected root table + instr_InsertColumn = + 21, // Insert new non-nullable column into to selected descriptor (nullable is instr_InsertNullableColumn) + instr_InsertLinkColumn = 22, // do, but for a link-type column + instr_InsertNullableColumn = 23, // Insert nullable column + instr_EraseColumn = 24, // Remove column from selected descriptor + instr_EraseLinkColumn = 25, // Remove link-type column from selected descriptor + instr_RenameColumn = 26, // Rename column in selected descriptor + instr_MoveColumn = 27, // Move column in selected descriptor (UNSUPPORTED/UNUSED) FIXME: remove + instr_AddSearchIndex = 28, // Add a search index to a column + instr_RemoveSearchIndex = 29, // Remove a search index from a column + instr_SetLinkType = 30, // Strong/weak + instr_SelectLinkList = 31, + instr_LinkListSet = 32, // Assign to link list entry + instr_LinkListInsert = 33, // Insert entry into link list + instr_LinkListMove = 34, // Move an entry within a link list + instr_LinkListSwap = 35, // Swap two entries within a link list + instr_LinkListErase = 36, // Remove an entry from a link list + instr_LinkListNullify = 37, // Remove an entry from a link list due to linked row being erased + instr_LinkListClear = 38, // Ramove all entries from a link list + instr_LinkListSetAll = 39, // Assign to link list entry + instr_AddRowWithKey = 40, // Insert a row with a given key +}; + +class TransactLogStream { +public: + /// Ensure contiguous free space in the transaction log + /// buffer. This method must update `out_free_begin` + /// and `out_free_end` such that they refer to a chunk + /// of free space whose size is at least \a n. + /// + /// \param n The required amount of contiguous free space. Must be + /// small (probably not greater than 1024) + /// \param n Must be small (probably not greater than 1024) + virtual void transact_log_reserve(size_t size, char** out_free_begin, char** out_free_end) = 0; + + /// Copy the specified data into the transaction log buffer. This + /// function should be called only when the specified data does + /// not fit inside the chunk of free space currently referred to + /// by `out_free_begin` and `out_free_end`. + /// + /// This method must update `out_begin` and + /// `out_end` such that, upon return, they still + /// refer to a (possibly empty) chunk of free space. + virtual void transact_log_append(const char* data, size_t size, char** out_free_begin, char** out_free_end) = 0; +}; + +class TransactLogBufferStream : public TransactLogStream { +public: + void transact_log_reserve(size_t size, char** out_free_begin, char** out_free_end) override; + void transact_log_append(const char* data, size_t size, char** out_free_begin, char** out_free_end) override; + + const char* transact_log_data() const; + + util::Buffer m_buffer; +}; + + +// LCOV_EXCL_START (because the NullInstructionObserver is trivial) +class NullInstructionObserver { +public: + /// The following methods are also those that TransactLogParser expects + /// to find on the `InstructionHandler`. + + // No selection needed: + bool select_table(size_t, size_t, const size_t*) + { + return true; + } + bool select_descriptor(size_t, const size_t*) + { + return true; + } + bool select_link_list(size_t, size_t, size_t) + { + return true; + } + bool insert_group_level_table(size_t, size_t, StringData) + { + return true; + } + bool erase_group_level_table(size_t, size_t) + { + return true; + } + bool rename_group_level_table(size_t, StringData) + { + return true; + } + + // Must have table selected: + bool insert_empty_rows(size_t, size_t, size_t, bool) + { + return true; + } + bool add_row_with_key(size_t, size_t, size_t, int64_t) + { + return true; + } + bool erase_rows(size_t, size_t, size_t, bool) + { + return true; + } + bool swap_rows(size_t, size_t) + { + return true; + } + bool move_row(size_t, size_t) + { + return true; + } + bool merge_rows(size_t, size_t) + { + return true; + } + bool clear_table(size_t) + { + return true; + } + bool set_int(size_t, size_t, int_fast64_t, Instruction, size_t) + { + return true; + } + bool add_int(size_t, size_t, int_fast64_t) + { + return true; + } + bool set_bool(size_t, size_t, bool, Instruction) + { + return true; + } + bool set_float(size_t, size_t, float, Instruction) + { + return true; + } + bool set_double(size_t, size_t, double, Instruction) + { + return true; + } + bool set_string(size_t, size_t, StringData, Instruction, size_t) + { + return true; + } + bool set_binary(size_t, size_t, BinaryData, Instruction) + { + return true; + } + bool set_olddatetime(size_t, size_t, OldDateTime, Instruction) + { + return true; + } + bool set_timestamp(size_t, size_t, Timestamp, Instruction) + { + return true; + } + bool set_table(size_t, size_t, Instruction) + { + return true; + } + bool set_mixed(size_t, size_t, const Mixed&, Instruction) + { + return true; + } + bool set_link(size_t, size_t, size_t, size_t, Instruction) + { + return true; + } + bool set_null(size_t, size_t, Instruction, size_t) + { + return true; + } + bool nullify_link(size_t, size_t, size_t) + { + return true; + } + bool insert_substring(size_t, size_t, size_t, StringData) + { + return true; + } + bool erase_substring(size_t, size_t, size_t, size_t) + { + return true; + } + bool optimize_table() + { + return true; + } + + // Must have descriptor selected: + bool insert_link_column(size_t, DataType, StringData, size_t, size_t) + { + return true; + } + bool insert_column(size_t, DataType, StringData, bool) + { + return true; + } + bool erase_link_column(size_t, size_t, size_t) + { + return true; + } + bool erase_column(size_t) + { + return true; + } + bool rename_column(size_t, StringData) + { + return true; + } + bool add_search_index(size_t) + { + return true; + } + bool remove_search_index(size_t) + { + return true; + } + bool set_link_type(size_t, LinkType) + { + return true; + } + + // Must have linklist selected: + bool link_list_set(size_t, size_t, size_t) + { + return true; + } + bool link_list_insert(size_t, size_t, size_t) + { + return true; + } + bool link_list_move(size_t, size_t) + { + return true; + } + bool link_list_swap(size_t, size_t) + { + return true; + } + bool link_list_erase(size_t, size_t) + { + return true; + } + bool link_list_nullify(size_t, size_t) + { + return true; + } + bool link_list_clear(size_t) + { + return true; + } + + void parse_complete() + { + } +}; +// LCOV_EXCL_STOP (NullInstructionObserver) + + +/// See TransactLogConvenientEncoder for information about the meaning of the +/// arguments of each of the functions in this class. +class TransactLogEncoder { +public: + /// The following methods are also those that TransactLogParser expects + /// to find on the `InstructionHandler`. + + // No selection needed: + bool select_table(size_t group_level_ndx, size_t levels, const size_t* path); + bool select_descriptor(size_t levels, const size_t* path); + bool select_link_list(size_t col_ndx, size_t row_ndx, size_t link_target_group_level_ndx); + bool insert_group_level_table(size_t table_ndx, size_t num_tables, StringData name); + bool erase_group_level_table(size_t table_ndx, size_t num_tables); + bool rename_group_level_table(size_t table_ndx, StringData new_name); + + /// Must have table selected. + bool insert_empty_rows(size_t row_ndx, size_t num_rows_to_insert, size_t prior_num_rows, bool unordered); + bool add_row_with_key(size_t row_ndx, size_t prior_num_rows, size_t key_col_ndx, int64_t key); + bool erase_rows(size_t row_ndx, size_t num_rows_to_erase, size_t prior_num_rows, bool unordered); + bool swap_rows(size_t row_ndx_1, size_t row_ndx_2); + bool move_row(size_t from_ndx, size_t to_ndx); + bool merge_rows(size_t row_ndx, size_t new_row_ndx); + bool clear_table(size_t old_table_size); + + bool set_int(size_t col_ndx, size_t row_ndx, int_fast64_t, Instruction = instr_Set, size_t = 0); + bool add_int(size_t col_ndx, size_t row_ndx, int_fast64_t); + bool set_bool(size_t col_ndx, size_t row_ndx, bool, Instruction = instr_Set); + bool set_float(size_t col_ndx, size_t row_ndx, float, Instruction = instr_Set); + bool set_double(size_t col_ndx, size_t row_ndx, double, Instruction = instr_Set); + bool set_string(size_t col_ndx, size_t row_ndx, StringData, Instruction = instr_Set, size_t = 0); + bool set_binary(size_t col_ndx, size_t row_ndx, BinaryData, Instruction = instr_Set); + bool set_olddatetime(size_t col_ndx, size_t row_ndx, OldDateTime, Instruction = instr_Set); + bool set_timestamp(size_t col_ndx, size_t row_ndx, Timestamp, Instruction = instr_Set); + bool set_table(size_t col_ndx, size_t row_ndx, Instruction = instr_Set); + bool set_mixed(size_t col_ndx, size_t row_ndx, const Mixed&, Instruction = instr_Set); + bool set_link(size_t col_ndx, size_t row_ndx, size_t, size_t target_group_level_ndx, Instruction = instr_Set); + bool set_null(size_t col_ndx, size_t row_ndx, Instruction = instr_Set, size_t = 0); + bool nullify_link(size_t col_ndx, size_t row_ndx, size_t target_group_level_ndx); + bool insert_substring(size_t col_ndx, size_t row_ndx, size_t pos, StringData); + bool erase_substring(size_t col_ndx, size_t row_ndx, size_t pos, size_t size); + bool optimize_table(); + + // Must have descriptor selected: + bool insert_link_column(size_t col_ndx, DataType, StringData name, size_t link_target_table_ndx, + size_t backlink_col_ndx); + bool insert_column(size_t col_ndx, DataType, StringData name, bool nullable = false); + bool erase_link_column(size_t col_ndx, size_t link_target_table_ndx, size_t backlink_col_ndx); + bool erase_column(size_t col_ndx); + bool rename_column(size_t col_ndx, StringData new_name); + bool add_search_index(size_t col_ndx); + bool remove_search_index(size_t col_ndx); + bool set_link_type(size_t col_ndx, LinkType); + + // Must have linklist selected: + bool link_list_set(size_t link_ndx, size_t value, size_t prior_size); + bool link_list_set_all(const IntegerColumn& values); + bool link_list_insert(size_t link_ndx, size_t value, size_t prior_size); + bool link_list_move(size_t from_link_ndx, size_t to_link_ndx); + bool link_list_swap(size_t link1_ndx, size_t link2_ndx); + bool link_list_erase(size_t link_ndx, size_t prior_size); + bool link_list_nullify(size_t link_ndx, size_t prior_size); + bool link_list_clear(size_t old_list_size); + + /// End of methods expected by parser. + + + TransactLogEncoder(TransactLogStream& out_stream); + void set_buffer(char* new_free_begin, char* new_free_end); + char* write_position() const + { + return m_transact_log_free_begin; + } + +private: + using IntegerList = std::tuple; + using UnsignedList = std::tuple; + + // Make sure this is in agreement with the actual integer encoding + // scheme (see encode_int()). + static constexpr int max_enc_bytes_per_int = 10; + static constexpr int max_enc_bytes_per_double = sizeof(double); + static constexpr int max_enc_bytes_per_num = + max_enc_bytes_per_int < max_enc_bytes_per_double ? max_enc_bytes_per_double : max_enc_bytes_per_int; +// Space is reserved in chunks to avoid excessive over allocation. +#ifdef REALM_DEBUG + static constexpr int max_numbers_per_chunk = 2; // Increase the chance of chunking in debug mode +#else + static constexpr int max_numbers_per_chunk = 8; +#endif + + // This value is used in Set* instructions in place of the 'type' field in + // the stream to indicate that the value of the Set* instruction is NULL, + // which doesn't have a type. + static constexpr int set_null_sentinel() + { + return -1; + } + + TransactLogStream& m_stream; + + // These two delimit a contiguous region of free space in a + // transaction log buffer following the last written data. It may + // be empty. + char* m_transact_log_free_begin = nullptr; + char* m_transact_log_free_end = nullptr; + + char* reserve(size_t size); + /// \param ptr Must be in the range [m_transact_log_free_begin, m_transact_log_free_end] + void advance(char* ptr) noexcept; + + template + size_t max_size(T); + + size_t max_size_list() + { + return 0; + } + + template + size_t max_size_list(T val, Args... args) + { + return max_size(val) + max_size_list(args...); + } + + template + char* encode(char* ptr, T value); + + char* encode_list(char* ptr) + { + advance(ptr); + return ptr; + } + + template + char* encode_list(char* ptr, T value, Args... args) + { + return encode_list(encode(ptr, value), args...); + } + + template + void append_simple_instr(L... numbers); + + template + void append_mixed_instr(Instruction instr, const Mixed& value, L... numbers); + + template + static char* encode_int(char*, T value); + friend class TransactLogParser; +}; + +class TransactLogConvenientEncoder { +public: + virtual void insert_group_level_table(size_t table_ndx, size_t num_tables, StringData name); + virtual void erase_group_level_table(size_t table_ndx, size_t num_tables); + virtual void rename_group_level_table(size_t table_ndx, StringData new_name); + virtual void insert_column(const Descriptor&, size_t col_ndx, DataType type, StringData name, LinkTargetInfo& link, + bool nullable = false); + virtual void erase_column(const Descriptor&, size_t col_ndx); + virtual void rename_column(const Descriptor&, size_t col_ndx, StringData name); + + virtual void set_int(const Table*, size_t col_ndx, size_t ndx, int_fast64_t value, Instruction variant = instr_Set); + virtual void add_int(const Table*, size_t col_ndx, size_t ndx, int_fast64_t value); + virtual void set_bool(const Table*, size_t col_ndx, size_t ndx, bool value, Instruction variant = instr_Set); + virtual void set_float(const Table*, size_t col_ndx, size_t ndx, float value, Instruction variant = instr_Set); + virtual void set_double(const Table*, size_t col_ndx, size_t ndx, double value, Instruction variant = instr_Set); + virtual void set_string(const Table*, size_t col_ndx, size_t ndx, StringData value, Instruction variant = instr_Set); + virtual void set_binary(const Table*, size_t col_ndx, size_t ndx, BinaryData value, Instruction variant = instr_Set); + virtual void set_olddatetime(const Table*, size_t col_ndx, size_t ndx, OldDateTime value, + Instruction variant = instr_Set); + virtual void set_timestamp(const Table*, size_t col_ndx, size_t ndx, Timestamp value, Instruction variant = instr_Set); + virtual void set_table(const Table*, size_t col_ndx, size_t ndx, Instruction variant = instr_Set); + virtual void set_mixed(const Table*, size_t col_ndx, size_t ndx, const Mixed& value, Instruction variant = instr_Set); + virtual void set_link(const Table*, size_t col_ndx, size_t ndx, size_t value, Instruction variant = instr_Set); + virtual void set_null(const Table*, size_t col_ndx, size_t ndx, Instruction variant = instr_Set); + virtual void set_link_list(const LinkView&, const IntegerColumn& values); + virtual void insert_substring(const Table*, size_t col_ndx, size_t row_ndx, size_t pos, StringData); + virtual void erase_substring(const Table*, size_t col_ndx, size_t row_ndx, size_t pos, size_t size); + + /// \param prior_num_rows The number of rows in the table prior to the + /// modification. + virtual void insert_empty_rows(const Table*, size_t row_ndx, size_t num_rows_to_insert, size_t prior_num_rows); + virtual void add_row_with_key(const Table* t, size_t row_ndx, size_t prior_num_rows, size_t key_col_ndx, + int64_t key); + + /// \param prior_num_rows The number of rows in the table prior to the + /// modification. + virtual void erase_rows(const Table*, size_t row_ndx, size_t num_rows_to_erase, size_t prior_num_rows, + bool is_move_last_over); + + virtual void swap_rows(const Table*, size_t row_ndx_1, size_t row_ndx_2); + virtual void move_row(const Table*, size_t from_ndx, size_t to_ndx); + virtual void merge_rows(const Table*, size_t row_ndx, size_t new_row_ndx); + virtual void add_search_index(const Descriptor&, size_t col_ndx); + virtual void remove_search_index(const Descriptor&, size_t col_ndx); + virtual void set_link_type(const Table*, size_t col_ndx, LinkType); + virtual void clear_table(const Table*, size_t prior_num_rows); + virtual void optimize_table(const Table*); + + virtual void link_list_set(const LinkView&, size_t link_ndx, size_t value); + virtual void link_list_insert(const LinkView&, size_t link_ndx, size_t value); + virtual void link_list_move(const LinkView&, size_t from_link_ndx, size_t to_link_ndx); + virtual void link_list_swap(const LinkView&, size_t link_ndx_1, size_t link_ndx_2); + virtual void link_list_erase(const LinkView&, size_t link_ndx); + virtual void link_list_clear(const LinkView&); + + //@{ + + /// Implicit nullifications due to removal of target row. This is redundant + /// information from the point of view of replication, as the removal of the + /// target row will reproduce the implicit nullifications in the target + /// Realm anyway. The purpose of this instruction is to allow observers + /// (reactor pattern) to be explicitly notified about the implicit + /// nullifications. + + virtual void nullify_link(const Table*, size_t col_ndx, size_t ndx); + virtual void link_list_nullify(const LinkView&, size_t link_ndx); + + //@} + + void on_table_destroyed(const Table*) noexcept; + void on_spec_destroyed(const Spec*) noexcept; + void on_link_list_destroyed(const LinkView&) noexcept; + +protected: + TransactLogConvenientEncoder(TransactLogStream& encoder); + + void reset_selection_caches() noexcept; + void set_buffer(char* new_free_begin, char* new_free_end) + { + m_encoder.set_buffer(new_free_begin, new_free_end); + } + char* write_position() const + { + return m_encoder.write_position(); + } + +private: + TransactLogEncoder m_encoder; + // These are mutable because they are caches. + mutable util::Buffer m_subtab_path_buf; + mutable const Table* m_selected_table; + mutable const Spec* m_selected_spec; + // Has to be atomic to support concurrent reset when a linklist + // is unselected. This can happen on a different thread. In case + // of races, setting of a new value must win. + mutable std::atomic m_selected_link_list; + + void unselect_all() noexcept; + void select_table(const Table*); // unselects descriptor and link list + void select_desc(const Descriptor&); // unselects link list + void select_link_list(const LinkView&); // unselects descriptor + + void record_subtable_path(const Table&, size_t*& out_begin, size_t*& out_end); + void do_select_table(const Table*); + void do_select_desc(const Descriptor&); + void do_select_link_list(const LinkView&); + + friend class TransactReverser; +}; + + +class TransactLogParser { +public: + class BadTransactLog; // Exception + + TransactLogParser(); + ~TransactLogParser() noexcept; + + /// See `TransactLogEncoder` for a list of methods that the `InstructionHandler` must define. + /// parse() promises that the path passed by reference to + /// InstructionHandler::select_descriptor() will remain valid + /// during subsequent calls to all descriptor modifying functions. + template + void parse(InputStream&, InstructionHandler&); + + template + void parse(NoCopyInputStream&, InstructionHandler&); + +private: + util::Buffer m_input_buffer; + + // The input stream is assumed to consist of chunks of memory organised such that + // every instruction resides in a single chunk only. + NoCopyInputStream* m_input; + // pointer into transaction log, each instruction is parsed from m_input_begin and onwards. + // Each instruction are assumed to be contiguous in memory. + const char* m_input_begin; + // pointer to one past current instruction log chunk. If m_input_begin reaches m_input_end, + // a call to next_input_buffer will move m_input_begin and m_input_end to a new chunk of + // memory. Setting m_input_end to 0 disables this check, and is used if it is already known + // that all of the instructions are in memory. + const char* m_input_end; + util::StringBuffer m_string_buffer; + static const int m_max_levels = 1024; + util::Buffer m_path; + + REALM_NORETURN void parser_error() const; + + template + void parse_one(InstructionHandler&); + bool has_next() noexcept; + + template + T read_int(); + + void read_bytes(char* data, size_t size); + BinaryData read_buffer(util::StringBuffer&, size_t size); + + bool read_bool(); + float read_float(); + double read_double(); + + StringData read_string(util::StringBuffer&); + BinaryData read_binary(util::StringBuffer&); + Timestamp read_timestamp(); + void read_mixed(Mixed*); + + // Advance m_input_begin and m_input_end to reflect the next block of instructions + // Returns false if no more input was available + bool next_input_buffer(); + + // return true if input was available + bool read_char(char&); // throws + + bool is_valid_data_type(int type); + bool is_valid_link_type(int type); +}; + + +class TransactLogParser::BadTransactLog : public std::exception { +public: + const char* what() const noexcept override + { + return "Bad transaction log"; + } +}; + + +/// Implementation: + +inline void TransactLogBufferStream::transact_log_reserve(size_t n, char** inout_new_begin, char** out_new_end) +{ + char* data = m_buffer.data(); + REALM_ASSERT(*inout_new_begin >= data); + REALM_ASSERT(*inout_new_begin <= (data + m_buffer.size())); + size_t size = *inout_new_begin - data; + m_buffer.reserve_extra(size, n); + data = m_buffer.data(); // May have changed + *inout_new_begin = data + size; + *out_new_end = data + m_buffer.size(); +} + +inline void TransactLogBufferStream::transact_log_append(const char* data, size_t size, char** out_new_begin, + char** out_new_end) +{ + transact_log_reserve(size, out_new_begin, out_new_end); + *out_new_begin = realm::safe_copy_n(data, size, *out_new_begin); +} + +inline const char* TransactLogBufferStream::transact_log_data() const +{ + return m_buffer.data(); +} + +inline TransactLogEncoder::TransactLogEncoder(TransactLogStream& stream) + : m_stream(stream) +{ +} + +inline void TransactLogEncoder::set_buffer(char* free_begin, char* free_end) +{ + REALM_ASSERT(free_begin <= free_end); + m_transact_log_free_begin = free_begin; + m_transact_log_free_end = free_end; +} + +inline void TransactLogConvenientEncoder::reset_selection_caches() noexcept +{ + unselect_all(); +} + +inline char* TransactLogEncoder::reserve(size_t n) +{ + if (size_t(m_transact_log_free_end - m_transact_log_free_begin) < n) { + m_stream.transact_log_reserve(n, &m_transact_log_free_begin, &m_transact_log_free_end); + } + return m_transact_log_free_begin; +} + +inline void TransactLogEncoder::advance(char* ptr) noexcept +{ + REALM_ASSERT_DEBUG(m_transact_log_free_begin <= ptr); + REALM_ASSERT_DEBUG(ptr <= m_transact_log_free_end); + m_transact_log_free_begin = ptr; +} + + +// The integer encoding is platform independent. Also, it does not +// depend on the type of the specified integer. Integers of any type +// can be encoded as long as the specified buffer is large enough (see +// below). The decoding does not have to use the same type. Decoding +// will fail if, and only if the encoded value falls outside the range +// of the requested destination type. +// +// The encoding uses one or more bytes. It never uses more than 8 bits +// per byte. The last byte in the sequence is the first one that has +// its 8th bit set to zero. +// +// Consider a particular non-negative value V. Let W be the number of +// bits needed to encode V using the trivial binary encoding of +// integers. The total number of bytes produced is then +// ceil((W+1)/7). The first byte holds the 7 least significant bits of +// V. The last byte holds at most 6 bits of V including the most +// significant one. The value of the first bit of the last byte is +// always 2**((N-1)*7) where N is the total number of bytes. +// +// A negative value W is encoded by setting the sign bit to one and +// then encoding the positive result of -(W+1) as described above. The +// advantage of this representation is that it converts small negative +// values to small positive values which require a small number of +// bytes. This would not have been true for 2's complements +// representation, for example. The sign bit is always stored as the +// 7th bit of the last byte. +// +// value bits value + sign max bytes +// -------------------------------------------------- +// int8_t 7 8 2 +// uint8_t 8 9 2 +// int16_t 15 16 3 +// uint16_t 16 17 3 +// int32_t 31 32 5 +// uint32_t 32 33 5 +// int64_t 63 64 10 +// uint64_t 64 65 10 +// +template +char* TransactLogEncoder::encode_int(char* ptr, T value) +{ + static_assert(std::numeric_limits::is_integer, "Integer required"); + bool negative = util::is_negative(value); + if (negative) { + // The following conversion is guaranteed by C++11 to never + // overflow (contrast this with "-value" which indeed could + // overflow). See C99+TC3 section 6.2.6.2 paragraph 2. + REALM_DIAG_PUSH(); + REALM_DIAG_IGNORE_UNSIGNED_MINUS(); + value = -(value + 1); + REALM_DIAG_POP(); + } + // At this point 'value' is always a positive number. Also, small + // negative numbers have been converted to small positive numbers. + REALM_ASSERT(!util::is_negative(value)); + // One sign bit plus number of value bits + const int num_bits = 1 + std::numeric_limits::digits; + // Only the first 7 bits are available per byte. Had it not been + // for the fact that maximum guaranteed bit width of a char is 8, + // this value could have been increased to 15 (one less than the + // number of value bits in 'unsigned'). + const int bits_per_byte = 7; + const int max_bytes = (num_bits + (bits_per_byte - 1)) / bits_per_byte; + static_assert(max_bytes <= max_enc_bytes_per_int, "Bad max_enc_bytes_per_int"); + // An explicit constant maximum number of iterations is specified + // in the hope that it will help the optimizer (to do loop + // unrolling, for example). + typedef unsigned char uchar; + for (int i = 0; i < max_bytes; ++i) { + if (value >> (bits_per_byte - 1) == 0) + break; + *reinterpret_cast(ptr) = uchar((1U << bits_per_byte) | unsigned(value & ((1U << bits_per_byte) - 1))); + ++ptr; + value >>= bits_per_byte; + } + *reinterpret_cast(ptr) = uchar(negative ? (1U << (bits_per_byte - 1)) | unsigned(value) : value); + return ++ptr; +} + +template +char* TransactLogEncoder::encode(char* ptr, T value) +{ + auto value_2 = value + 0; // Perform integral promotion + return encode_int(ptr, value_2); +} + +template <> +inline char* TransactLogEncoder::encode(char* ptr, char value) +{ + // Write the char as-is without encoding. + *ptr++ = value; + return ptr; +} + +template <> +inline char* TransactLogEncoder::encode(char* ptr, Instruction inst) +{ + return encode(ptr, inst); +} + +template <> +inline char* TransactLogEncoder::encode(char* ptr, bool value) +{ + return encode(ptr, value); +} + +template <> +inline char* TransactLogEncoder::encode(char* ptr, float value) +{ + static_assert(std::numeric_limits::is_iec559 && + sizeof(float) * std::numeric_limits::digits == 32, + "Unsupported 'float' representation"); + const char* val_ptr = reinterpret_cast(&value); + return realm::safe_copy_n(val_ptr, sizeof value, ptr); +} + +template <> +inline char* TransactLogEncoder::encode(char* ptr, double value) +{ + static_assert(std::numeric_limits::is_iec559 && + sizeof(double) * std::numeric_limits::digits == 64, + "Unsupported 'double' representation"); + const char* val_ptr = reinterpret_cast(&value); + return realm::safe_copy_n(val_ptr, sizeof value, ptr); +} + +template <> +inline char* TransactLogEncoder::encode(char* ptr, DataType type) +{ + return encode(ptr, type); +} + +template <> +inline char* TransactLogEncoder::encode(char* ptr, StringData s) +{ + ptr = encode_int(ptr, s.size()); + return std::copy_n(s.data(), s.size(), ptr); +} + +template <> +inline char* TransactLogEncoder::encode(char* ptr, + TransactLogEncoder::IntegerList list) +{ + IntegerColumnIterator i = std::get<0>(list); + IntegerColumnIterator end = std::get<1>(list); + + while (end - i > max_numbers_per_chunk) { + for (int j = 0; j < max_numbers_per_chunk; ++j) + ptr = encode_int(ptr, *i++); + advance(ptr); + size_t max_required_bytes_2 = max_enc_bytes_per_num * max_numbers_per_chunk; + ptr = reserve(max_required_bytes_2); // Throws + } + + while (i != end) + ptr = encode_int(ptr, *i++); + + return ptr; +} + +template <> +inline char* TransactLogEncoder::encode(char* ptr, + TransactLogEncoder::UnsignedList list) +{ + const size_t* i = std::get<0>(list); + const size_t* end = std::get<1>(list); + + while (i != end) + ptr = encode_int(ptr, *i++); + + return ptr; +} + +template +size_t TransactLogEncoder::max_size(T) +{ + return max_enc_bytes_per_num; +} + +template <> +inline size_t TransactLogEncoder::max_size(char) +{ + return 1; +} + +template <> +inline size_t TransactLogEncoder::max_size(bool) +{ + return 1; +} + +template <> +inline size_t TransactLogEncoder::max_size(Instruction) +{ + return 1; +} + +template <> +inline size_t TransactLogEncoder::max_size(DataType) +{ + return 1; +} + +template <> +inline size_t TransactLogEncoder::max_size(StringData s) +{ + return max_enc_bytes_per_num + s.size(); +} + +template <> +inline size_t TransactLogEncoder::max_size(IntegerList) +{ + // We only allocate space for 'max_numbers_per_chunk' at a time + return max_enc_bytes_per_num * max_numbers_per_chunk; +} + +template <> +inline size_t TransactLogEncoder::max_size(UnsignedList list) +{ + const size_t* begin = std::get<0>(list); + const size_t* end = std::get<1>(list); + // list contains (end - begin) elements + return max_enc_bytes_per_num * (end - begin); +} + +template +void TransactLogEncoder::append_simple_instr(L... numbers) +{ + size_t max_required_bytes = max_size_list(numbers...); + char* ptr = reserve(max_required_bytes); // Throws + encode_list(ptr, numbers...); +} + +template +void TransactLogEncoder::append_mixed_instr(Instruction instr, const Mixed& value, L... numbers) +{ + DataType type = value.get_type(); + switch (type) { + case type_Int: + append_simple_instr(instr, numbers..., type, value.get_int()); // Throws + return; + case type_Bool: + append_simple_instr(instr, numbers..., type, value.get_bool()); // Throws + return; + case type_Float: + append_simple_instr(instr, numbers..., type, value.get_float()); // Throws + return; + case type_Double: + append_simple_instr(instr, numbers..., type, value.get_double()); // Throws + return; + case type_OldDateTime: { + auto value_2 = value.get_olddatetime().get_olddatetime(); + append_simple_instr(instr, numbers..., type, value_2); // Throws + return; + } + case type_String: { + append_simple_instr(instr, numbers..., type, value.get_string()); // Throws + return; + } + case type_Binary: { + BinaryData value_2 = value.get_binary(); + StringData value_3(value_2.data(), value_2.size()); + append_simple_instr(instr, numbers..., type, value_3); // Throws + return; + } + case type_Timestamp: { + Timestamp ts = value.get_timestamp(); + int64_t seconds = ts.get_seconds(); + int32_t nano_seconds = ts.get_nanoseconds(); + append_simple_instr(instr, numbers..., type, seconds, nano_seconds); // Throws + return; + } + case type_Table: + append_simple_instr(instr, numbers..., type); // Throws + return; + case type_Mixed: + // Mixed in mixed is not possible + REALM_TERMINATE("Mixed in Mixed not possible"); + case type_Link: + case type_LinkList: + // FIXME: Need to handle new link types here. + REALM_TERMINATE("Link types in Mixed not supported."); + } + REALM_TERMINATE("Invalid Mixed."); +} + +inline void TransactLogConvenientEncoder::unselect_all() noexcept +{ + m_selected_table = nullptr; + m_selected_spec = nullptr; + // no race with on_link_list_destroyed since both are setting to nullptr + m_selected_link_list = nullptr; +} + +inline void TransactLogConvenientEncoder::select_table(const Table* table) +{ + if (table != m_selected_table) + do_select_table(table); // Throws + m_selected_spec = nullptr; + // no race with on_link_list_destroyed since both are setting to nullptr + m_selected_link_list = nullptr; +} + +inline void TransactLogConvenientEncoder::select_desc(const Descriptor& desc) +{ + typedef _impl::DescriptorFriend df; + if (&df::get_spec(desc) != m_selected_spec) + do_select_desc(desc); // Throws + // no race with on_link_list_destroyed since both are setting to nullptr + m_selected_link_list = nullptr; +} + +inline void TransactLogConvenientEncoder::select_link_list(const LinkView& list) +{ + // A race between this and a call to on_link_list_destroyed() must + // end up with m_selected_link_list pointing to the list argument given + // here. We assume that the list given to on_link_list_destroyed() can + // *never* be the same as the list argument given here. We resolve the + // race by a) always updating m_selected_link_list in do_select_link_list() + // and b) only atomically and conditionally updating it in + // on_link_list_destroyed(). + if (&list != m_selected_link_list) { + do_select_link_list(list); // Throws + } + m_selected_spec = nullptr; +} + + +inline bool TransactLogEncoder::insert_group_level_table(size_t table_ndx, size_t prior_num_tables, StringData name) +{ + append_simple_instr(instr_InsertGroupLevelTable, table_ndx, prior_num_tables, name); // Throws + return true; +} + +inline void TransactLogConvenientEncoder::insert_group_level_table(size_t table_ndx, size_t prior_num_tables, + StringData name) +{ + unselect_all(); + m_encoder.insert_group_level_table(table_ndx, prior_num_tables, name); // Throws +} + +inline bool TransactLogEncoder::erase_group_level_table(size_t table_ndx, size_t prior_num_tables) +{ + append_simple_instr(instr_EraseGroupLevelTable, table_ndx, prior_num_tables); // Throws + return true; +} + +inline void TransactLogConvenientEncoder::erase_group_level_table(size_t table_ndx, size_t prior_num_tables) +{ + unselect_all(); + m_encoder.erase_group_level_table(table_ndx, prior_num_tables); // Throws +} + +inline bool TransactLogEncoder::rename_group_level_table(size_t table_ndx, StringData new_name) +{ + append_simple_instr(instr_RenameGroupLevelTable, table_ndx, new_name); // Throws + return true; +} + +inline void TransactLogConvenientEncoder::rename_group_level_table(size_t table_ndx, StringData new_name) +{ + unselect_all(); + m_encoder.rename_group_level_table(table_ndx, new_name); // Throws +} + +inline bool TransactLogEncoder::insert_column(size_t col_ndx, DataType type, StringData name, bool nullable) +{ + Instruction instr = (nullable ? instr_InsertNullableColumn : instr_InsertColumn); + append_simple_instr(instr, col_ndx, type, name); // Throws + return true; +} + +inline bool TransactLogEncoder::insert_link_column(size_t col_ndx, DataType type, StringData name, + size_t link_target_table_ndx, size_t backlink_col_ndx) +{ + REALM_ASSERT(_impl::TableFriend::is_link_type(ColumnType(type))); + append_simple_instr(instr_InsertLinkColumn, col_ndx, type, link_target_table_ndx, backlink_col_ndx, + name); // Throws + return true; +} + + +inline void TransactLogConvenientEncoder::insert_column(const Descriptor& desc, size_t col_ndx, DataType type, + StringData name, LinkTargetInfo& link, bool nullable) +{ + select_desc(desc); // Throws + if (link.is_valid()) { + typedef _impl::TableFriend tf; + typedef _impl::DescriptorFriend df; + size_t target_table_ndx = link.m_target_table->get_index_in_group(); + const Table& origin_table = df::get_root_table(desc); + REALM_ASSERT(origin_table.is_group_level()); + const Spec& target_spec = tf::get_spec(*(link.m_target_table)); + size_t origin_table_ndx = origin_table.get_index_in_group(); + size_t backlink_col_ndx = target_spec.find_backlink_column(origin_table_ndx, col_ndx); + REALM_ASSERT_3(backlink_col_ndx, ==, link.m_backlink_col_ndx); + m_encoder.insert_link_column(col_ndx, type, name, target_table_ndx, backlink_col_ndx); // Throws + } + else { + m_encoder.insert_column(col_ndx, type, name, nullable); // Throws + } +} + +inline bool TransactLogEncoder::erase_column(size_t col_ndx) +{ + append_simple_instr(instr_EraseColumn, col_ndx); // Throws + return true; +} + +inline bool TransactLogEncoder::erase_link_column(size_t col_ndx, size_t link_target_table_ndx, + size_t backlink_col_ndx) +{ + append_simple_instr(instr_EraseLinkColumn, col_ndx, link_target_table_ndx, backlink_col_ndx); // Throws + return true; +} + +inline void TransactLogConvenientEncoder::erase_column(const Descriptor& desc, size_t col_ndx) +{ + select_desc(desc); // Throws + + DataType type = desc.get_column_type(col_ndx); + typedef _impl::TableFriend tf; + if (!tf::is_link_type(ColumnType(type))) { + m_encoder.erase_column(col_ndx); // Throws + } + else { // it's a link column: + REALM_ASSERT(desc.is_root()); + typedef _impl::DescriptorFriend df; + const Table& origin_table = df::get_root_table(desc); + REALM_ASSERT(origin_table.is_group_level()); + const Table& target_table = *tf::get_link_target_table_accessor(origin_table, col_ndx); + size_t target_table_ndx = target_table.get_index_in_group(); + const Spec& target_spec = tf::get_spec(target_table); + size_t origin_table_ndx = origin_table.get_index_in_group(); + size_t backlink_col_ndx = target_spec.find_backlink_column(origin_table_ndx, col_ndx); + m_encoder.erase_link_column(col_ndx, target_table_ndx, backlink_col_ndx); // Throws + } +} + +inline bool TransactLogEncoder::rename_column(size_t col_ndx, StringData new_name) +{ + append_simple_instr(instr_RenameColumn, col_ndx, new_name); // Throws + return true; +} + +inline void TransactLogConvenientEncoder::rename_column(const Descriptor& desc, size_t col_ndx, StringData name) +{ + select_desc(desc); // Throws + m_encoder.rename_column(col_ndx, name); // Throws +} + + +inline bool TransactLogEncoder::set_int(size_t col_ndx, size_t ndx, int_fast64_t value, Instruction variant, + size_t prior_num_rows) +{ + REALM_ASSERT_EX(variant == instr_Set || variant == instr_SetDefault || variant == instr_SetUnique, variant); + if (REALM_UNLIKELY(variant == instr_SetUnique)) + append_simple_instr(variant, type_Int, col_ndx, ndx, prior_num_rows, value); // Throws + else + append_simple_instr(variant, type_Int, col_ndx, ndx, value); // Throws + return true; +} + +inline void TransactLogConvenientEncoder::set_int(const Table* t, size_t col_ndx, size_t ndx, int_fast64_t value, + Instruction variant) +{ + select_table(t); // Throws + size_t prior_num_rows = (variant == instr_SetUnique ? t->size() : 0); + m_encoder.set_int(col_ndx, ndx, value, variant, prior_num_rows); // Throws +} + + +inline bool TransactLogEncoder::add_int(size_t col_ndx, size_t ndx, int_fast64_t value) +{ + append_simple_instr(instr_AddInteger, col_ndx, ndx, value); // Throws + return true; +} + +inline void TransactLogConvenientEncoder::add_int(const Table* t, size_t col_ndx, size_t ndx, int_fast64_t value) +{ + select_table(t); // Throws + m_encoder.add_int(col_ndx, ndx, value); +} + +inline bool TransactLogEncoder::set_bool(size_t col_ndx, size_t ndx, bool value, Instruction variant) +{ + REALM_ASSERT_EX(variant == instr_Set || variant == instr_SetDefault, variant); + append_simple_instr(variant, type_Bool, col_ndx, ndx, value); // Throws + return true; +} + +inline void TransactLogConvenientEncoder::set_bool(const Table* t, size_t col_ndx, size_t ndx, bool value, + Instruction variant) +{ + select_table(t); // Throws + m_encoder.set_bool(col_ndx, ndx, value, variant); // Throws +} + +inline bool TransactLogEncoder::set_float(size_t col_ndx, size_t ndx, float value, Instruction variant) +{ + REALM_ASSERT_EX(variant == instr_Set || variant == instr_SetDefault, variant); + append_simple_instr(variant, type_Float, col_ndx, ndx, value); // Throws + return true; +} + +inline void TransactLogConvenientEncoder::set_float(const Table* t, size_t col_ndx, size_t ndx, float value, + Instruction variant) +{ + select_table(t); // Throws + m_encoder.set_float(col_ndx, ndx, value, variant); // Throws +} + +inline bool TransactLogEncoder::set_double(size_t col_ndx, size_t ndx, double value, Instruction variant) +{ + REALM_ASSERT_EX(variant == instr_Set || variant == instr_SetDefault, variant); + append_simple_instr(instr_Set, type_Double, col_ndx, ndx, value); // Throws + return true; +} + +inline void TransactLogConvenientEncoder::set_double(const Table* t, size_t col_ndx, size_t ndx, double value, + Instruction variant) +{ + select_table(t); // Throws + m_encoder.set_double(col_ndx, ndx, value, variant); // Throws +} + +inline bool TransactLogEncoder::set_string(size_t col_ndx, size_t ndx, StringData value, Instruction variant, + size_t prior_num_rows) +{ + REALM_ASSERT_EX(variant == instr_Set || variant == instr_SetDefault || variant == instr_SetUnique, variant); + if (value.is_null()) { + set_null(col_ndx, ndx, variant, prior_num_rows); // Throws + } + else { + if (REALM_UNLIKELY(variant == instr_SetUnique)) + append_simple_instr(variant, type_String, col_ndx, ndx, prior_num_rows, value); // Throws + else + append_simple_instr(variant, type_String, col_ndx, ndx, value); // Throws + } + return true; +} + +inline void TransactLogConvenientEncoder::set_string(const Table* t, size_t col_ndx, size_t ndx, StringData value, + Instruction variant) +{ + select_table(t); // Throws + size_t prior_num_rows = (variant == instr_SetUnique ? t->size() : 0); + m_encoder.set_string(col_ndx, ndx, value, variant, prior_num_rows); // Throws +} + +inline bool TransactLogEncoder::set_binary(size_t col_ndx, size_t row_ndx, BinaryData value, Instruction variant) +{ + REALM_ASSERT_EX(variant == instr_Set || variant == instr_SetDefault, variant); + if (value.is_null()) { + set_null(col_ndx, row_ndx, variant); // Throws + } + else { + StringData value_2(value.data(), value.size()); + append_simple_instr(variant, type_Binary, col_ndx, row_ndx, value_2); // Throws + } + return true; +} + +inline void TransactLogConvenientEncoder::set_binary(const Table* t, size_t col_ndx, size_t ndx, BinaryData value, + Instruction variant) +{ + select_table(t); // Throws + m_encoder.set_binary(col_ndx, ndx, value, variant); // Throws +} + +inline bool TransactLogEncoder::set_olddatetime(size_t col_ndx, size_t ndx, OldDateTime value, Instruction variant) +{ + REALM_ASSERT_EX(variant == instr_Set || variant == instr_SetDefault, variant); + append_simple_instr(variant, type_OldDateTime, col_ndx, ndx, value.get_olddatetime()); // Throws + return true; +} + +inline void TransactLogConvenientEncoder::set_olddatetime(const Table* t, size_t col_ndx, size_t ndx, + OldDateTime value, Instruction variant) +{ + select_table(t); // Throws + m_encoder.set_olddatetime(col_ndx, ndx, value, variant); // Throws +} + +inline bool TransactLogEncoder::set_timestamp(size_t col_ndx, size_t ndx, Timestamp value, Instruction variant) +{ + REALM_ASSERT_EX(variant == instr_Set || variant == instr_SetDefault, variant); + append_simple_instr(variant, type_Timestamp, col_ndx, ndx, value.get_seconds(), + value.get_nanoseconds()); // Throws + return true; +} + +inline void TransactLogConvenientEncoder::set_timestamp(const Table* t, size_t col_ndx, size_t ndx, Timestamp value, + Instruction variant) +{ + select_table(t); // Throws + m_encoder.set_timestamp(col_ndx, ndx, value, variant); // Throws +} + +inline bool TransactLogEncoder::set_table(size_t col_ndx, size_t ndx, Instruction variant) +{ + REALM_ASSERT_EX(variant == instr_Set || variant == instr_SetDefault, variant); + append_simple_instr(variant, type_Table, col_ndx, ndx); // Throws + return true; +} + +inline void TransactLogConvenientEncoder::set_table(const Table* t, size_t col_ndx, size_t ndx, Instruction variant) +{ + select_table(t); // Throws + m_encoder.set_table(col_ndx, ndx, variant); // Throws +} + +inline bool TransactLogEncoder::set_mixed(size_t col_ndx, size_t ndx, const Mixed& value, Instruction variant) +{ + REALM_ASSERT_EX(variant == instr_Set || variant == instr_SetDefault, variant); + append_mixed_instr(variant, value, type_Mixed, col_ndx, ndx); // Throws + return true; +} + +inline void TransactLogConvenientEncoder::set_mixed(const Table* t, size_t col_ndx, size_t ndx, const Mixed& value, + Instruction variant) +{ + select_table(t); // Throws + m_encoder.set_mixed(col_ndx, ndx, value, variant); // Throws +} + +inline bool TransactLogEncoder::set_link(size_t col_ndx, size_t ndx, size_t value, size_t target_group_level_ndx, + Instruction variant) +{ + REALM_ASSERT_EX(variant == instr_Set || variant == instr_SetDefault, variant); + // Map `realm::npos` to zero, and `n` to `n+1`, where `n` is a target row + // index. + size_t value_2 = size_t(1) + value; + append_simple_instr(variant, type_Link, col_ndx, ndx, value_2, target_group_level_ndx); // Throws + return true; +} + +inline void TransactLogConvenientEncoder::set_link(const Table* t, size_t col_ndx, size_t ndx, size_t value, + Instruction variant) +{ + select_table(t); // Throws + size_t target_group_level_ndx = t->get_descriptor()->get_column_link_target(col_ndx); + m_encoder.set_link(col_ndx, ndx, value, target_group_level_ndx, variant); // Throws +} + +inline bool TransactLogEncoder::set_null(size_t col_ndx, size_t ndx, Instruction variant, size_t prior_num_rows) +{ + REALM_ASSERT_EX(variant == instr_Set || variant == instr_SetDefault || variant == instr_SetUnique, variant); + if (REALM_UNLIKELY(variant == instr_SetUnique)) { + append_simple_instr(variant, set_null_sentinel(), col_ndx, ndx, prior_num_rows); // Throws + } + else { + append_simple_instr(variant, set_null_sentinel(), col_ndx, ndx); // Throws + } + return true; +} + +inline void TransactLogConvenientEncoder::set_null(const Table* t, size_t col_ndx, size_t row_ndx, + Instruction variant) +{ + select_table(t); // Throws + size_t prior_num_rows = (variant == instr_SetUnique ? t->size() : 0); + m_encoder.set_null(col_ndx, row_ndx, variant, prior_num_rows); // Throws +} + +inline bool TransactLogEncoder::nullify_link(size_t col_ndx, size_t ndx, size_t target_group_level_ndx) +{ + append_simple_instr(instr_NullifyLink, col_ndx, ndx, target_group_level_ndx); // Throws + return true; +} + +inline void TransactLogConvenientEncoder::nullify_link(const Table* t, size_t col_ndx, size_t ndx) +{ + select_table(t); // Throws + size_t target_group_level_ndx = t->get_descriptor()->get_column_link_target(col_ndx); + m_encoder.nullify_link(col_ndx, ndx, target_group_level_ndx); // Throws +} + +inline bool TransactLogEncoder::insert_substring(size_t col_ndx, size_t row_ndx, size_t pos, StringData value) +{ + append_simple_instr(instr_InsertSubstring, col_ndx, row_ndx, pos, value); // Throws + return true; +} + +inline void TransactLogConvenientEncoder::insert_substring(const Table* t, size_t col_ndx, size_t row_ndx, size_t pos, + StringData value) +{ + if (value.size() > 0) { + select_table(t); // Throws + m_encoder.insert_substring(col_ndx, row_ndx, pos, value); // Throws + } +} + +inline bool TransactLogEncoder::erase_substring(size_t col_ndx, size_t row_ndx, size_t pos, size_t size) +{ + append_simple_instr(instr_EraseFromString, col_ndx, row_ndx, pos, size); // Throws + return true; +} + +inline void TransactLogConvenientEncoder::erase_substring(const Table* t, size_t col_ndx, size_t row_ndx, size_t pos, + size_t size) +{ + if (size > 0) { + select_table(t); // Throws + m_encoder.erase_substring(col_ndx, row_ndx, pos, size); // Throws + } +} + +inline bool TransactLogEncoder::insert_empty_rows(size_t row_ndx, size_t num_rows_to_insert, size_t prior_num_rows, + bool unordered) +{ + append_simple_instr(instr_InsertEmptyRows, row_ndx, num_rows_to_insert, prior_num_rows, unordered); // Throws + return true; +} + +inline void TransactLogConvenientEncoder::insert_empty_rows(const Table* t, size_t row_ndx, size_t num_rows_to_insert, + size_t prior_num_rows) +{ + select_table(t); // Throws + bool unordered = false; + m_encoder.insert_empty_rows(row_ndx, num_rows_to_insert, prior_num_rows, unordered); // Throws +} + +inline bool TransactLogEncoder::add_row_with_key(size_t row_ndx, size_t prior_num_rows, size_t key_col_ndx, + int64_t key) +{ + append_simple_instr(instr_AddRowWithKey, row_ndx, prior_num_rows, key_col_ndx, key); // Throws + return true; +} + +inline void TransactLogConvenientEncoder::add_row_with_key(const Table* t, size_t row_ndx, size_t prior_num_rows, + size_t key_col_ndx, int64_t key) +{ + select_table(t); // Throws + m_encoder.add_row_with_key(row_ndx, prior_num_rows, key_col_ndx, key); // Throws +} + +inline bool TransactLogEncoder::erase_rows(size_t row_ndx, size_t num_rows_to_erase, size_t prior_num_rows, + bool unordered) +{ + append_simple_instr(instr_EraseRows, row_ndx, num_rows_to_erase, prior_num_rows, unordered); // Throws + return true; +} + + +inline void TransactLogConvenientEncoder::erase_rows(const Table* t, size_t row_ndx, size_t num_rows_to_erase, + size_t prior_num_rows, bool is_move_last_over) +{ + select_table(t); // Throws + bool unordered = is_move_last_over; + m_encoder.erase_rows(row_ndx, num_rows_to_erase, prior_num_rows, unordered); // Throws +} + +inline bool TransactLogEncoder::swap_rows(size_t row_ndx_1, size_t row_ndx_2) +{ + append_simple_instr(instr_SwapRows, row_ndx_1, row_ndx_2); // Throws + return true; +} + +inline void TransactLogConvenientEncoder::swap_rows(const Table* t, size_t row_ndx_1, size_t row_ndx_2) +{ + REALM_ASSERT(row_ndx_1 < row_ndx_2); + select_table(t); // Throws + m_encoder.swap_rows(row_ndx_1, row_ndx_2); +} + +inline bool TransactLogEncoder::move_row(size_t from_ndx, size_t to_ndx) +{ + append_simple_instr(instr_MoveRow, from_ndx, to_ndx); // Throws + return true; +} + +inline void TransactLogConvenientEncoder::move_row(const Table* t, size_t from_ndx, size_t to_ndx) +{ + REALM_ASSERT(from_ndx != to_ndx); + select_table(t); // Throws + m_encoder.move_row(from_ndx, to_ndx); +} + +inline bool TransactLogEncoder::merge_rows(size_t row_ndx, size_t new_row_ndx) +{ + append_simple_instr(instr_MergeRows, row_ndx, new_row_ndx); // Throws + return true; +} + +inline void TransactLogConvenientEncoder::merge_rows(const Table* t, size_t row_ndx, size_t new_row_ndx) +{ + select_table(t); // Throws + m_encoder.merge_rows(row_ndx, new_row_ndx); +} + +inline bool TransactLogEncoder::add_search_index(size_t col_ndx) +{ + append_simple_instr(instr_AddSearchIndex, col_ndx); // Throws + return true; +} + +inline void TransactLogConvenientEncoder::add_search_index(const Descriptor& desc, size_t col_ndx) +{ + select_desc(desc); // Throws + m_encoder.add_search_index(col_ndx); // Throws +} + + +inline bool TransactLogEncoder::remove_search_index(size_t col_ndx) +{ + append_simple_instr(instr_RemoveSearchIndex, col_ndx); // Throws + return true; +} + +inline void TransactLogConvenientEncoder::remove_search_index(const Descriptor& desc, size_t col_ndx) +{ + select_desc(desc); // Throws + m_encoder.remove_search_index(col_ndx); // Throws +} + +inline bool TransactLogEncoder::set_link_type(size_t col_ndx, LinkType link_type) +{ + append_simple_instr(instr_SetLinkType, col_ndx, int(link_type)); // Throws + return true; +} + +inline void TransactLogConvenientEncoder::set_link_type(const Table* t, size_t col_ndx, LinkType link_type) +{ + select_table(t); // Throws + m_encoder.set_link_type(col_ndx, link_type); // Throws +} + + +inline bool TransactLogEncoder::clear_table(size_t old_size) +{ + append_simple_instr(instr_ClearTable, old_size); // Throws + return true; +} + +inline void TransactLogConvenientEncoder::clear_table(const Table* t, size_t prior_num_rows) +{ + select_table(t); // Throws + m_encoder.clear_table(prior_num_rows); // Throws +} + +inline bool TransactLogEncoder::optimize_table() +{ + append_simple_instr(instr_OptimizeTable); // Throws + return true; +} + +inline void TransactLogConvenientEncoder::optimize_table(const Table* t) +{ + select_table(t); // Throws + m_encoder.optimize_table(); // Throws +} + +inline bool TransactLogEncoder::link_list_set(size_t link_ndx, size_t value, size_t prior_size) +{ + append_simple_instr(instr_LinkListSet, link_ndx, value, prior_size); // Throws + return true; +} + +inline void TransactLogConvenientEncoder::link_list_set(const LinkView& list, size_t link_ndx, size_t value) +{ + select_link_list(list); // Throws + m_encoder.link_list_set(link_ndx, value, list.size()); // Throws +} + +inline bool TransactLogEncoder::link_list_nullify(size_t link_ndx, size_t prior_size) +{ + append_simple_instr(instr_LinkListNullify, link_ndx, prior_size); // Throws + return true; +} + +inline void TransactLogConvenientEncoder::link_list_nullify(const LinkView& list, size_t link_ndx) +{ + select_link_list(list); // Throws + size_t prior_size = list.size(); // Instruction is emitted before the fact. + m_encoder.link_list_nullify(link_ndx, prior_size); // Throws +} + +inline bool TransactLogEncoder::link_list_set_all(const IntegerColumn& values) +{ + size_t num_values = values.size(); + append_simple_instr( + instr_LinkListSetAll, num_values, + std::make_tuple(IntegerColumnIterator(&values, 0), IntegerColumnIterator(&values, num_values))); // Throws + return true; +} + +inline void TransactLogConvenientEncoder::set_link_list(const LinkView& list, const IntegerColumn& values) +{ + select_link_list(list); // Throws + m_encoder.link_list_set_all(values); // Throws +} + +inline bool TransactLogEncoder::link_list_insert(size_t link_ndx, size_t value, size_t prior_size) +{ + append_simple_instr(instr_LinkListInsert, link_ndx, value, prior_size); // Throws + return true; +} + +inline void TransactLogConvenientEncoder::link_list_insert(const LinkView& list, size_t link_ndx, size_t value) +{ + select_link_list(list); // Throws + size_t prior_size = list.size() - 1; // The instruction is emitted after the fact. + m_encoder.link_list_insert(link_ndx, value, prior_size); // Throws +} + +inline bool TransactLogEncoder::link_list_move(size_t from_link_ndx, size_t to_link_ndx) +{ + REALM_ASSERT(from_link_ndx != to_link_ndx); + append_simple_instr(instr_LinkListMove, from_link_ndx, to_link_ndx); // Throws + return true; +} + +inline void TransactLogConvenientEncoder::link_list_move(const LinkView& list, size_t from_link_ndx, + size_t to_link_ndx) +{ + select_link_list(list); // Throws + m_encoder.link_list_move(from_link_ndx, to_link_ndx); // Throws +} + +inline bool TransactLogEncoder::link_list_swap(size_t link1_ndx, size_t link2_ndx) +{ + append_simple_instr(instr_LinkListSwap, link1_ndx, link2_ndx); // Throws + return true; +} + +inline void TransactLogConvenientEncoder::link_list_swap(const LinkView& list, size_t link1_ndx, size_t link2_ndx) +{ + select_link_list(list); // Throws + m_encoder.link_list_swap(link1_ndx, link2_ndx); // Throws +} + +inline bool TransactLogEncoder::link_list_erase(size_t link_ndx, size_t prior_size) +{ + append_simple_instr(instr_LinkListErase, link_ndx, prior_size); // Throws + return true; +} + +inline void TransactLogConvenientEncoder::link_list_erase(const LinkView& list, size_t link_ndx) +{ + select_link_list(list); // Throws + size_t prior_size = list.size(); // The instruction is emitted before the fact. + m_encoder.link_list_erase(link_ndx, prior_size); // Throws +} + +inline bool TransactLogEncoder::link_list_clear(size_t old_list_size) +{ + append_simple_instr(instr_LinkListClear, old_list_size); // Throws + return true; +} + +inline void TransactLogConvenientEncoder::on_table_destroyed(const Table* t) noexcept +{ + if (m_selected_table == t) + m_selected_table = nullptr; +} + +inline void TransactLogConvenientEncoder::on_spec_destroyed(const Spec* s) noexcept +{ + if (m_selected_spec == s) + m_selected_spec = nullptr; +} + + +inline void TransactLogConvenientEncoder::on_link_list_destroyed(const LinkView& list) noexcept +{ + const LinkView* lw_ptr = &list; + // atomically clear m_selected_link_list iff it already points to 'list': + // (lw_ptr will be modified if the swap fails, but we ignore that) + m_selected_link_list.compare_exchange_strong(lw_ptr, nullptr, std::memory_order_relaxed, + std::memory_order_relaxed); +} + + +inline TransactLogParser::TransactLogParser() + : m_input_buffer(1024) // Throws +{ +} + + +inline TransactLogParser::~TransactLogParser() noexcept +{ +} + + +template +void TransactLogParser::parse(NoCopyInputStream& in, InstructionHandler& handler) +{ + m_input = ∈ + m_input_begin = m_input_end = nullptr; + + while (has_next()) + parse_one(handler); // Throws +} + +template +void TransactLogParser::parse(InputStream& in, InstructionHandler& handler) +{ + NoCopyInputStreamAdaptor in_2(in, m_input_buffer.data(), m_input_buffer.size()); + parse(in_2, handler); // Throws +} + +inline bool TransactLogParser::has_next() noexcept +{ + return m_input_begin != m_input_end || next_input_buffer(); +} + +template +void TransactLogParser::parse_one(InstructionHandler& handler) +{ + char instr_ch; + if (!read_char(instr_ch)) + parser_error(); // Throws + // std::cerr << "parsing " << util::promote(instr) << " @ " << std::hex << long(m_input_begin) << std::dec << + // "\n"; + Instruction instr = Instruction(instr_ch); + switch (instr) { + case instr_SetDefault: + case instr_SetUnique: + case instr_Set: { + int type = read_int(); // Throws + size_t col_ndx = read_int(); // Throws + size_t row_ndx = read_int(); // Throws + size_t prior_num_rows = 0; + if (REALM_UNLIKELY(instr == instr_SetUnique)) + prior_num_rows = read_int(); // Throws + + if (type == TransactLogEncoder::set_null_sentinel()) { + // Special case for set_null + if (!handler.set_null(col_ndx, row_ndx, instr, prior_num_rows)) // Throws + parser_error(); + return; + } + + switch (DataType(type)) { + case type_Int: { + int_fast64_t value = read_int(); // Throws + if (!handler.set_int(col_ndx, row_ndx, value, instr, prior_num_rows)) // Throws + parser_error(); + return; + } + case type_Bool: { + bool value = read_bool(); // Throws + if (!handler.set_bool(col_ndx, row_ndx, value, instr)) // Throws + parser_error(); + return; + } + case type_Float: { + float value = read_float(); // Throws + if (!handler.set_float(col_ndx, row_ndx, value, instr)) // Throws + parser_error(); + return; + } + case type_Double: { + double value = read_double(); // Throws + if (!handler.set_double(col_ndx, row_ndx, value, instr)) // Throws + parser_error(); + return; + } + case type_String: { + StringData value = read_string(m_string_buffer); // Throws + if (!handler.set_string(col_ndx, row_ndx, value, instr, prior_num_rows)) // Throws + parser_error(); + return; + } + case type_Binary: { + BinaryData value = read_binary(m_string_buffer); // Throws + if (!handler.set_binary(col_ndx, row_ndx, value, instr)) // Throws + parser_error(); + return; + } + case type_OldDateTime: { + int_fast64_t value = read_int(); // Throws + if (!handler.set_olddatetime(col_ndx, row_ndx, value, instr)) // Throws + parser_error(); + return; + } + case type_Timestamp: { + int64_t seconds = read_int(); // Throws + int32_t nanoseconds = read_int(); // Throws + Timestamp value = Timestamp(seconds, nanoseconds); + if (!handler.set_timestamp(col_ndx, row_ndx, value, instr)) // Throws + parser_error(); + return; + } + case type_Table: { + if (!handler.set_table(col_ndx, row_ndx, instr)) // Throws + parser_error(); + return; + } + case type_Mixed: { + Mixed value; + read_mixed(&value); // Throws + if (!handler.set_mixed(col_ndx, row_ndx, value, instr)) // Throws + parser_error(); + return; + } + case type_Link: { + size_t value = read_int(); // Throws + // Map zero to realm::npos, and `n+1` to `n`, where `n` is a target row index. + size_t target_row_ndx = size_t(value - 1); + size_t target_group_level_ndx = read_int(); // Throws + if (!handler.set_link(col_ndx, row_ndx, target_row_ndx, target_group_level_ndx, instr)) // Throws + parser_error(); + return; + } + case type_LinkList: { + // Unsupported column type for Set. + parser_error(); + return; + } + } + parser_error(); + return; + } + case instr_AddInteger: { + size_t col_ndx = read_int(); // Throws + size_t row_ndx = read_int(); // Throws + int_fast64_t value = read_int(); // Throws + if (!handler.add_int(col_ndx, row_ndx, value)) // Throws + parser_error(); + return; + } + case instr_NullifyLink: { + size_t col_ndx = read_int(); // Throws + size_t row_ndx = read_int(); // Throws + size_t target_group_level_ndx = read_int(); // Throws + if (!handler.nullify_link(col_ndx, row_ndx, target_group_level_ndx)) // Throws + parser_error(); + return; + } + case instr_InsertSubstring: { + size_t col_ndx = read_int(); // Throws + size_t row_ndx = read_int(); // Throws + size_t pos = read_int(); // Throws + StringData value = read_string(m_string_buffer); // Throws + if (!handler.insert_substring(col_ndx, row_ndx, pos, value)) // Throws + parser_error(); + return; + } + case instr_EraseFromString: { + size_t col_ndx = read_int(); // Throws + size_t row_ndx = read_int(); // Throws + size_t pos = read_int(); // Throws + size_t size = read_int(); // Throws + if (!handler.erase_substring(col_ndx, row_ndx, pos, size)) // Throws + parser_error(); + return; + } + case instr_InsertEmptyRows: { + size_t row_ndx = read_int(); // Throws + size_t num_rows_to_insert = read_int(); // Throws + size_t prior_num_rows = read_int(); // Throws + bool unordered = read_bool(); // Throws + if (!handler.insert_empty_rows(row_ndx, num_rows_to_insert, prior_num_rows, unordered)) // Throws + parser_error(); + return; + } + case instr_AddRowWithKey: { + size_t row_ndx = read_int(); // Throws + size_t prior_num_rows = read_int(); // Throws + size_t key_col_ndx = read_int(); // Throws + int64_t key = read_int(); // Throws + if (!handler.add_row_with_key(row_ndx, prior_num_rows, key_col_ndx, key)) // Throws + parser_error(); + return; + } + case instr_EraseRows: { + size_t row_ndx = read_int(); // Throws + size_t num_rows_to_erase = read_int(); // Throws + size_t prior_num_rows = read_int(); // Throws + bool unordered = read_bool(); // Throws + if (!handler.erase_rows(row_ndx, num_rows_to_erase, prior_num_rows, unordered)) // Throws + parser_error(); + return; + } + case instr_SwapRows: { + size_t row_ndx_1 = read_int(); // Throws + size_t row_ndx_2 = read_int(); // Throws + if (!handler.swap_rows(row_ndx_1, row_ndx_2)) // Throws + parser_error(); + return; + } + case instr_MoveRow: { + size_t from_ndx = read_int(); // Throws + size_t to_ndx = read_int(); // Throws + if (!handler.move_row(from_ndx, to_ndx)) // Throws + parser_error(); + return; + } + case instr_MergeRows: { + size_t row_ndx = read_int(); // Throws + size_t new_row_ndx = read_int(); // Throws + if (!handler.merge_rows(row_ndx, new_row_ndx)) // Throws + parser_error(); + return; + } + case instr_SelectTable: { + int levels = read_int(); // Throws + if (levels < 0 || levels > m_max_levels) + parser_error(); + m_path.reserve(0, 2 * levels); // Throws + size_t* path = m_path.data(); + size_t group_level_ndx = read_int(); // Throws + for (int i = 0; i != levels; ++i) { + size_t col_ndx = read_int(); // Throws + size_t row_ndx = read_int(); // Throws + path[2 * i + 0] = col_ndx; + path[2 * i + 1] = row_ndx; + } + if (!handler.select_table(group_level_ndx, levels, path)) // Throws + parser_error(); + return; + } + case instr_ClearTable: { + size_t old_size = read_int(); // Throws + if (!handler.clear_table(old_size)) // Throws + parser_error(); + return; + } + case instr_LinkListSet: { + size_t link_ndx = read_int(); // Throws + size_t value = read_int(); // Throws + size_t prior_size = read_int(); // Throws + if (!handler.link_list_set(link_ndx, value, prior_size)) // Throws + parser_error(); + return; + } + case instr_LinkListSetAll: { + // todo, log that it's a SetAll we're doing + size_t size = read_int(); // Throws + for (size_t i = 0; i < size; i++) { + size_t link = read_int(); // Throws + if (!handler.link_list_set(i, link, size)) // Throws + parser_error(); + } + return; + } + case instr_LinkListInsert: { + size_t link_ndx = read_int(); // Throws + size_t value = read_int(); // Throws + size_t prior_size = read_int(); // Throws + if (!handler.link_list_insert(link_ndx, value, prior_size)) // Throws + parser_error(); + return; + } + case instr_LinkListMove: { + size_t from_link_ndx = read_int(); // Throws + size_t to_link_ndx = read_int(); // Throws + if (!handler.link_list_move(from_link_ndx, to_link_ndx)) // Throws + parser_error(); + return; + } + case instr_LinkListSwap: { + size_t link1_ndx = read_int(); // Throws + size_t link2_ndx = read_int(); // Throws + if (!handler.link_list_swap(link1_ndx, link2_ndx)) // Throws + parser_error(); + return; + } + case instr_LinkListErase: { + size_t link_ndx = read_int(); // Throws + size_t prior_size = read_int(); // Throws + if (!handler.link_list_erase(link_ndx, prior_size)) // Throws + parser_error(); + return; + } + case instr_LinkListNullify: { + size_t link_ndx = read_int(); // Throws + size_t prior_size = read_int(); // Throws + if (!handler.link_list_nullify(link_ndx, prior_size)) // Throws + parser_error(); + return; + } + case instr_LinkListClear: { + size_t old_list_size = read_int(); // Throws + if (!handler.link_list_clear(old_list_size)) // Throws + parser_error(); + return; + } + case instr_SelectLinkList: { + size_t col_ndx = read_int(); // Throws + size_t row_ndx = read_int(); // Throws + size_t target_group_level_ndx = read_int(); // Throws + if (!handler.select_link_list(col_ndx, row_ndx, target_group_level_ndx)) // Throws + parser_error(); + return; + } + case instr_MoveColumn: { + // FIXME: remove this in the next breaking change. + // This instruction is no longer supported and not used by either + // bindings or sync, so if we see it here, there was a problem parsing. + parser_error(); + return; + } + case instr_AddSearchIndex: { + size_t col_ndx = read_int(); // Throws + if (!handler.add_search_index(col_ndx)) // Throws + parser_error(); + return; + } + case instr_RemoveSearchIndex: { + size_t col_ndx = read_int(); // Throws + if (!handler.remove_search_index(col_ndx)) // Throws + parser_error(); + return; + } + case instr_SetLinkType: { + size_t col_ndx = read_int(); // Throws + int link_type = read_int(); // Throws + if (!is_valid_link_type(link_type)) + parser_error(); + if (!handler.set_link_type(col_ndx, LinkType(link_type))) // Throws + parser_error(); + return; + } + case instr_InsertColumn: + case instr_InsertNullableColumn: { + size_t col_ndx = read_int(); // Throws + int type = read_int(); // Throws + if (!is_valid_data_type(type)) + parser_error(); + if (REALM_UNLIKELY(type == type_Link || type == type_LinkList)) + parser_error(); + StringData name = read_string(m_string_buffer); // Throws + bool nullable = (Instruction(instr) == instr_InsertNullableColumn); + if (REALM_UNLIKELY(nullable && (type == type_Mixed))) { + // Nullability not supported for Mixed columns. + parser_error(); + } + if (!handler.insert_column(col_ndx, DataType(type), name, nullable)) // Throws + parser_error(); + return; + } + case instr_InsertLinkColumn: { + size_t col_ndx = read_int(); // Throws + int type = read_int(); // Throws + if (!is_valid_data_type(type)) + parser_error(); + if (REALM_UNLIKELY(type != type_Link && type != type_LinkList)) + parser_error(); + size_t link_target_table_ndx = read_int(); // Throws + size_t backlink_col_ndx = read_int(); // Throws + StringData name = read_string(m_string_buffer); // Throws + if (!handler.insert_link_column(col_ndx, DataType(type), name, link_target_table_ndx, + backlink_col_ndx)) // Throws + parser_error(); + return; + } + case instr_EraseColumn: { + size_t col_ndx = read_int(); // Throws + if (!handler.erase_column(col_ndx)) // Throws + parser_error(); + return; + } + case instr_EraseLinkColumn: { + size_t col_ndx = read_int(); // Throws + size_t link_target_table_ndx = read_int(); // Throws + size_t backlink_col_ndx = read_int(); // Throws + if (!handler.erase_link_column(col_ndx, link_target_table_ndx, backlink_col_ndx)) // Throws + parser_error(); + return; + } + case instr_RenameColumn: { + size_t col_ndx = read_int(); // Throws + StringData name = read_string(m_string_buffer); // Throws + if (!handler.rename_column(col_ndx, name)) // Throws + parser_error(); + return; + } + case instr_SelectDescriptor: { + int levels = read_int(); // Throws + if (levels < 0 || levels > m_max_levels) + parser_error(); + m_path.reserve(0, levels); // Throws + size_t* path = m_path.data(); + for (int i = 0; i != levels; ++i) { + size_t col_ndx = read_int(); // Throws + path[i] = col_ndx; + } + if (!handler.select_descriptor(levels, path)) // Throws + parser_error(); + return; + } + case instr_InsertGroupLevelTable: { + size_t table_ndx = read_int(); // Throws + size_t num_tables = read_int(); // Throws + StringData name = read_string(m_string_buffer); // Throws + if (!handler.insert_group_level_table(table_ndx, num_tables, name)) // Throws + parser_error(); + return; + } + case instr_EraseGroupLevelTable: { + size_t table_ndx = read_int(); // Throws + size_t prior_num_tables = read_int(); // Throws + if (!handler.erase_group_level_table(table_ndx, prior_num_tables)) // Throws + parser_error(); + return; + } + case instr_RenameGroupLevelTable: { + size_t table_ndx = read_int(); // Throws + StringData new_name = read_string(m_string_buffer); // Throws + if (!handler.rename_group_level_table(table_ndx, new_name)) // Throws + parser_error(); + return; + } + case instr_MoveGroupLevelTable: { + // This instruction is no longer supported and not used by either + // bindings or sync, so if we see it here, there was a problem parsing. + // FIXME: remove this in the next breaking change. + parser_error(); + return; + } + case instr_OptimizeTable: { + if (!handler.optimize_table()) // Throws + parser_error(); + return; + } + } + + throw BadTransactLog(); +} + + +template +T TransactLogParser::read_int() +{ + T value = 0; + int part = 0; + const int max_bytes = (std::numeric_limits::digits + 1 + 6) / 7; + for (int i = 0; i != max_bytes; ++i) { + char c; + if (!read_char(c)) + goto bad_transact_log; + part = static_cast(c); + if (0xFF < part) + goto bad_transact_log; // Only the first 8 bits may be used in each byte + if ((part & 0x80) == 0) { + T p = part & 0x3F; + if (util::int_shift_left_with_overflow_detect(p, i * 7)) + goto bad_transact_log; + value |= p; + break; + } + if (i == max_bytes - 1) + goto bad_transact_log; // Too many bytes + value |= T(part & 0x7F) << (i * 7); + } + if (part & 0x40) { + // The real value is negative. Because 'value' is positive at + // this point, the following negation is guaranteed by C++11 + // to never overflow. See C99+TC3 section 6.2.6.2 paragraph 2. + REALM_DIAG_PUSH(); + REALM_DIAG_IGNORE_UNSIGNED_MINUS(); + value = -value; + REALM_DIAG_POP(); + if (util::int_subtract_with_overflow_detect(value, 1)) + goto bad_transact_log; + } + return value; + +bad_transact_log: + throw BadTransactLog(); +} + + +inline void TransactLogParser::read_bytes(char* data, size_t size) +{ + for (;;) { + const size_t avail = m_input_end - m_input_begin; + if (size <= avail) + break; + realm::safe_copy_n(m_input_begin, avail, data); + if (!next_input_buffer()) + throw BadTransactLog(); + data += avail; + size -= avail; + } + const char* to = m_input_begin + size; + realm::safe_copy_n(m_input_begin, size, data); + m_input_begin = to; +} + + +inline BinaryData TransactLogParser::read_buffer(util::StringBuffer& buf, size_t size) +{ + const size_t avail = m_input_end - m_input_begin; + if (avail >= size) { + m_input_begin += size; + return BinaryData(m_input_begin - size, size); + } + + buf.clear(); + buf.resize(size); // Throws + read_bytes(buf.data(), size); + return BinaryData(buf.data(), size); +} + + +inline bool TransactLogParser::read_bool() +{ + return read_int(); +} + + +inline float TransactLogParser::read_float() +{ + static_assert(std::numeric_limits::is_iec559 && + sizeof(float) * std::numeric_limits::digits == 32, + "Unsupported 'float' representation"); + float value; + read_bytes(reinterpret_cast(&value), sizeof value); // Throws + return value; +} + + +inline double TransactLogParser::read_double() +{ + static_assert(std::numeric_limits::is_iec559 && + sizeof(double) * std::numeric_limits::digits == 64, + "Unsupported 'double' representation"); + double value; + read_bytes(reinterpret_cast(&value), sizeof value); // Throws + return value; +} + + +inline StringData TransactLogParser::read_string(util::StringBuffer& buf) +{ + size_t size = read_int(); // Throws + + if (size > Table::max_string_size) + parser_error(); + + BinaryData buffer = read_buffer(buf, size); + return StringData{buffer.data(), size}; +} + +inline Timestamp TransactLogParser::read_timestamp() +{ + int64_t seconds = read_int(); // Throws + int32_t nanoseconds = read_int(); // Throws + return Timestamp(seconds, nanoseconds); +} + + +inline BinaryData TransactLogParser::read_binary(util::StringBuffer& buf) +{ + size_t size = read_int(); // Throws + + return read_buffer(buf, size); +} + + +inline void TransactLogParser::read_mixed(Mixed* mixed) +{ + DataType type = DataType(read_int()); // Throws + switch (type) { + case type_Int: { + int_fast64_t value = read_int(); // Throws + mixed->set_int(value); + return; + } + case type_Bool: { + bool value = read_bool(); // Throws + mixed->set_bool(value); + return; + } + case type_Float: { + float value = read_float(); // Throws + mixed->set_float(value); + return; + } + case type_Double: { + double value = read_double(); // Throws + mixed->set_double(value); + return; + } + case type_OldDateTime: { + int_fast64_t value = read_int(); // Throws + mixed->set_olddatetime(value); + return; + } + case type_Timestamp: { + Timestamp value = read_timestamp(); // Throws + mixed->set_timestamp(value); + return; + } + case type_String: { + StringData value = read_string(m_string_buffer); // Throws + mixed->set_string(value); + return; + } + case type_Binary: { + BinaryData value = read_binary(m_string_buffer); // Throws + mixed->set_binary(value); + return; + } + case type_Table: { + *mixed = Mixed::subtable_tag(); + return; + } + case type_Mixed: + break; + case type_Link: + case type_LinkList: + // FIXME: Need to handle new link types here + break; + } + throw BadTransactLog(); +} + + +inline bool TransactLogParser::next_input_buffer() +{ + return m_input->next_block(m_input_begin, m_input_end); +} + + +inline bool TransactLogParser::read_char(char& c) +{ + if (m_input_begin == m_input_end && !next_input_buffer()) + return false; + c = *m_input_begin++; + return true; +} + + +inline bool TransactLogParser::is_valid_data_type(int type) +{ + switch (DataType(type)) { + case type_Int: + case type_Bool: + case type_Float: + case type_Double: + case type_String: + case type_Binary: + case type_OldDateTime: + case type_Timestamp: + case type_Table: + case type_Mixed: + case type_Link: + case type_LinkList: + return true; + } + return false; +} + + +inline bool TransactLogParser::is_valid_link_type(int type) +{ + switch (LinkType(type)) { + case link_Strong: + case link_Weak: + return true; + } + return false; +} + + +class TransactReverser { +public: + bool select_table(size_t group_level_ndx, size_t levels, const size_t* path) + { + sync_table(); + m_encoder.select_table(group_level_ndx, levels, path); + m_pending_ts_instr = get_inst(); + return true; + } + + bool select_descriptor(size_t levels, const size_t* path) + { + sync_descriptor(); + m_encoder.select_descriptor(levels, path); + m_pending_ds_instr = get_inst(); + return true; + } + + bool insert_group_level_table(size_t table_ndx, size_t num_tables, StringData) + { + sync_table(); + m_encoder.erase_group_level_table(table_ndx, num_tables + 1); + append_instruction(); + return true; + } + + bool erase_group_level_table(size_t table_ndx, size_t num_tables) + { + sync_table(); + m_encoder.insert_group_level_table(table_ndx, num_tables - 1, ""); + append_instruction(); + return true; + } + + bool rename_group_level_table(size_t, StringData) + { + sync_table(); + return true; + } + + bool optimize_table() + { + return true; // No-op + } + + bool insert_empty_rows(size_t row_ndx, size_t num_rows_to_insert, size_t prior_num_rows, bool unordered) + { + size_t num_rows_to_erase = num_rows_to_insert; + size_t prior_num_rows_2 = prior_num_rows + num_rows_to_insert; + m_encoder.erase_rows(row_ndx, num_rows_to_erase, prior_num_rows_2, unordered); // Throws + append_instruction(); + return true; + } + + bool add_row_with_key(size_t row_ndx, size_t prior_num_rows, size_t, int64_t) + { + bool unordered = true; + m_encoder.erase_rows(row_ndx, 1, prior_num_rows + 1, unordered); // Throws + append_instruction(); + return true; + } + + bool erase_rows(size_t row_ndx, size_t num_rows_to_erase, size_t prior_num_rows, bool unordered) + { + size_t num_rows_to_insert = num_rows_to_erase; + // Number of rows in table after removal, but before inverse insertion + size_t prior_num_rows_2 = prior_num_rows - num_rows_to_erase; + m_encoder.insert_empty_rows(row_ndx, num_rows_to_insert, prior_num_rows_2, unordered); // Throws + append_instruction(); + return true; + } + + bool swap_rows(size_t row_ndx_1, size_t row_ndx_2) + { + m_encoder.swap_rows(row_ndx_1, row_ndx_2); + append_instruction(); + return true; + } + + bool move_row(size_t from_ndx, size_t to_ndx) + { + m_encoder.move_row(to_ndx, from_ndx); + append_instruction(); + return true; + } + + bool merge_rows(size_t row_ndx, size_t new_row_ndx) + { + // There is no instruction we can generate here to change back. + // However, we do need to refresh accessors for any tables + // connected through backlinks, so we generate updates on each + // affected row by merging to itself. + m_encoder.merge_rows(row_ndx, row_ndx); + append_instruction(); + m_encoder.merge_rows(new_row_ndx, new_row_ndx); + append_instruction(); + return true; + } + + bool set_int(size_t col_ndx, size_t row_ndx, int_fast64_t value, Instruction variant, size_t prior_num_rows) + { + m_encoder.set_int(col_ndx, row_ndx, value, variant, prior_num_rows); + append_instruction(); + return true; + } + + bool add_int(size_t col_ndx, size_t row_ndx, int_fast64_t value) + { + m_encoder.add_int(col_ndx, row_ndx, -value); + append_instruction(); + return true; + } + + bool set_bool(size_t col_ndx, size_t row_ndx, bool value, Instruction variant) + { + m_encoder.set_bool(col_ndx, row_ndx, value, variant); + append_instruction(); + return true; + } + + bool set_float(size_t col_ndx, size_t row_ndx, float value, Instruction variant) + { + m_encoder.set_float(col_ndx, row_ndx, value, variant); + append_instruction(); + return true; + } + + bool set_double(size_t col_ndx, size_t row_ndx, double value, Instruction variant) + { + m_encoder.set_double(col_ndx, row_ndx, value, variant); + append_instruction(); + return true; + } + + bool set_string(size_t col_ndx, size_t row_ndx, StringData value, Instruction variant, size_t prior_num_rows) + { + m_encoder.set_string(col_ndx, row_ndx, value, variant, prior_num_rows); + append_instruction(); + return true; + } + + bool set_binary(size_t col_ndx, size_t row_ndx, BinaryData value, Instruction variant) + { + m_encoder.set_binary(col_ndx, row_ndx, value, variant); + append_instruction(); + return true; + } + + bool set_olddatetime(size_t col_ndx, size_t row_ndx, OldDateTime value, Instruction variant) + { + m_encoder.set_olddatetime(col_ndx, row_ndx, value, variant); + append_instruction(); + return true; + } + + bool set_timestamp(size_t col_ndx, size_t row_ndx, Timestamp value, Instruction variant) + { + m_encoder.set_timestamp(col_ndx, row_ndx, value, variant); + append_instruction(); + return true; + } + + bool set_table(size_t col_ndx, size_t row_ndx, Instruction variant) + { + m_encoder.set_table(col_ndx, row_ndx, variant); + append_instruction(); + return true; + } + + bool set_mixed(size_t col_ndx, size_t row_ndx, const Mixed& value, Instruction variant) + { + m_encoder.set_mixed(col_ndx, row_ndx, value, variant); + append_instruction(); + return true; + } + + bool set_null(size_t col_ndx, size_t row_ndx, Instruction variant, size_t prior_num_rows) + { + m_encoder.set_null(col_ndx, row_ndx, variant, prior_num_rows); + append_instruction(); + return true; + } + + bool set_link(size_t col_ndx, size_t row_ndx, size_t value, size_t target_group_level_ndx, Instruction variant) + { + m_encoder.set_link(col_ndx, row_ndx, value, target_group_level_ndx, variant); + append_instruction(); + return true; + } + + bool insert_substring(size_t, size_t, size_t, StringData) + { + return true; // No-op + } + + bool erase_substring(size_t, size_t, size_t, size_t) + { + return true; // No-op + } + + bool clear_table(size_t old_size) + { + bool unordered = false; + m_encoder.insert_empty_rows(0, old_size, 0, unordered); + append_instruction(); + return true; + } + + bool add_search_index(size_t) + { + return true; // No-op + } + + bool remove_search_index(size_t) + { + return true; // No-op + } + + bool set_link_type(size_t, LinkType) + { + return true; // No-op + } + + bool insert_link_column(size_t col_idx, DataType, StringData, size_t target_table_idx, size_t backlink_col_ndx) + { + m_encoder.erase_link_column(col_idx, target_table_idx, backlink_col_ndx); + append_instruction(); + return true; + } + + bool erase_link_column(size_t col_idx, size_t target_table_idx, size_t backlink_col_idx) + { + DataType type = type_Link; // The real type of the column doesn't matter here, + // but the encoder asserts that it's actually a link type. + m_encoder.insert_link_column(col_idx, type, "", target_table_idx, backlink_col_idx); + append_instruction(); + return true; + } + + bool insert_column(size_t col_idx, DataType, StringData, bool) + { + m_encoder.erase_column(col_idx); + append_instruction(); + return true; + } + + bool erase_column(size_t col_idx) + { + m_encoder.insert_column(col_idx, DataType(), ""); + append_instruction(); + return true; + } + + bool rename_column(size_t, StringData) + { + return true; // No-op + } + + bool select_link_list(size_t col_ndx, size_t row_ndx, size_t link_target_group_level_ndx) + { + sync_linkview(); + m_encoder.select_link_list(col_ndx, row_ndx, link_target_group_level_ndx); + m_pending_lv_instr = get_inst(); + return true; + } + + bool link_list_set(size_t row, size_t value, size_t prior_size) + { + m_encoder.link_list_set(row, value, prior_size); + append_instruction(); + return true; + } + + bool link_list_insert(size_t link_ndx, size_t, size_t prior_size) + { + m_encoder.link_list_erase(link_ndx, prior_size + 1); + append_instruction(); + return true; + } + + bool link_list_move(size_t from_link_ndx, size_t to_link_ndx) + { + m_encoder.link_list_move(from_link_ndx, to_link_ndx); + append_instruction(); + return true; + } + + bool link_list_swap(size_t link1_ndx, size_t link2_ndx) + { + m_encoder.link_list_swap(link1_ndx, link2_ndx); + append_instruction(); + return true; + } + + bool link_list_erase(size_t link_ndx, size_t prior_size) + { + m_encoder.link_list_insert(link_ndx, 0, prior_size - 1); + append_instruction(); + return true; + } + + bool link_list_clear(size_t old_list_size) + { + // Append in reverse order because the reversed log is itself applied + // in reverse, and this way it generates all back-insertions rather than + // all front-insertions + for (size_t i = old_list_size; i > 0; --i) { + m_encoder.link_list_insert(i - 1, 0, old_list_size - i); + append_instruction(); + } + return true; + } + + bool nullify_link(size_t col_ndx, size_t row_ndx, size_t target_group_level_ndx) + { + size_t value = 0; + // FIXME: Is zero this right value to pass here, or should + // TransactReverser::nullify_link() also have taken a + // `target_group_level_ndx` argument. + m_encoder.set_link(col_ndx, row_ndx, value, target_group_level_ndx); + append_instruction(); + return true; + } + + bool link_list_nullify(size_t link_ndx, size_t prior_size) + { + m_encoder.link_list_insert(link_ndx, 0, prior_size - 1); + append_instruction(); + return true; + } + +private: + _impl::TransactLogBufferStream m_buffer; + _impl::TransactLogEncoder m_encoder{m_buffer}; + struct Instr { + size_t begin; + size_t end; + }; + std::vector m_instructions; + size_t current_instr_start = 0; + Instr m_pending_ts_instr{0, 0}; + Instr m_pending_ds_instr{0, 0}; + Instr m_pending_lv_instr{0, 0}; + + Instr get_inst() + { + Instr instr; + instr.begin = current_instr_start; + current_instr_start = transact_log_size(); + instr.end = current_instr_start; + return instr; + } + + size_t transact_log_size() const + { + REALM_ASSERT_3(m_encoder.write_position(), >=, m_buffer.transact_log_data()); + return m_encoder.write_position() - m_buffer.transact_log_data(); + } + + void append_instruction() + { + m_instructions.push_back(get_inst()); + } + + void append_instruction(Instr instr) + { + m_instructions.push_back(instr); + } + + void sync_select(Instr& pending_instr) + { + if (pending_instr.begin != pending_instr.end) { + append_instruction(pending_instr); + pending_instr = {0, 0}; + } + } + + void sync_linkview() + { + sync_select(m_pending_lv_instr); + } + + void sync_descriptor() + { + sync_linkview(); + sync_select(m_pending_ds_instr); + } + + void sync_table() + { + sync_descriptor(); + sync_select(m_pending_ts_instr); + } + + friend class ReversedNoCopyInputStream; +}; + + +class ReversedNoCopyInputStream : public NoCopyInputStream { +public: + ReversedNoCopyInputStream(TransactReverser& reverser) + : m_instr_order(reverser.m_instructions) + { + // push any pending select_table or select_descriptor into the buffer + reverser.sync_table(); + + m_buffer = reverser.m_buffer.transact_log_data(); + m_current = m_instr_order.size(); + } + + bool next_block(const char*& begin, const char*& end) override + { + if (m_current != 0) { + m_current--; + begin = m_buffer + m_instr_order[m_current].begin; + end = m_buffer + m_instr_order[m_current].end; + return (end > begin); + } + return false; + } + +private: + const char* m_buffer; + std::vector& m_instr_order; + size_t m_current; +}; + +} // namespace _impl +} // namespace realm + +#endif // REALM_IMPL_TRANSACT_LOG_HPP