--- /dev/null
+/*************************************************************************
+ *
+ * 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 <stdexcept>
+
+#include <realm/string_data.hpp>
+#include <realm/data_type.hpp>
+#include <realm/binary_data.hpp>
+#include <realm/olddatetime.hpp>
+#include <realm/mixed.hpp>
+#include <realm/util/buffer.hpp>
+#include <realm/util/string_buffer.hpp>
+#include <realm/impl/input_stream.hpp>
+
+#include <realm/group.hpp>
+#include <realm/descriptor.hpp>
+#include <realm/link_view.hpp>
+
+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<char> 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<IntegerColumnIterator, IntegerColumnIterator>;
+ using UnsignedList = std::tuple<const size_t*, const size_t*>;
+
+ // 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 <class T>
+ size_t max_size(T);
+
+ size_t max_size_list()
+ {
+ return 0;
+ }
+
+ template <class T, class... Args>
+ size_t max_size_list(T val, Args... args)
+ {
+ return max_size(val) + max_size_list(args...);
+ }
+
+ template <class T>
+ char* encode(char* ptr, T value);
+
+ char* encode_list(char* ptr)
+ {
+ advance(ptr);
+ return ptr;
+ }
+
+ template <class T, class... Args>
+ char* encode_list(char* ptr, T value, Args... args)
+ {
+ return encode_list(encode(ptr, value), args...);
+ }
+
+ template <class... L>
+ void append_simple_instr(L... numbers);
+
+ template <class... L>
+ void append_mixed_instr(Instruction instr, const Mixed& value, L... numbers);
+
+ template <class T>
+ 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<size_t> 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<const LinkView*> 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 <class InstructionHandler>
+ void parse(InputStream&, InstructionHandler&);
+
+ template <class InstructionHandler>
+ void parse(NoCopyInputStream&, InstructionHandler&);
+
+private:
+ util::Buffer<char> 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<size_t> m_path;
+
+ REALM_NORETURN void parser_error() const;
+
+ template <class InstructionHandler>
+ void parse_one(InstructionHandler&);
+ bool has_next() noexcept;
+
+ template <class T>
+ 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 <class T>
+char* TransactLogEncoder::encode_int(char* ptr, T value)
+{
+ static_assert(std::numeric_limits<T>::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<T>::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<uchar*>(ptr) = uchar((1U << bits_per_byte) | unsigned(value & ((1U << bits_per_byte) - 1)));
+ ++ptr;
+ value >>= bits_per_byte;
+ }
+ *reinterpret_cast<uchar*>(ptr) = uchar(negative ? (1U << (bits_per_byte - 1)) | unsigned(value) : value);
+ return ++ptr;
+}
+
+template <class T>
+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>(char* ptr, char value)
+{
+ // Write the char as-is without encoding.
+ *ptr++ = value;
+ return ptr;
+}
+
+template <>
+inline char* TransactLogEncoder::encode<Instruction>(char* ptr, Instruction inst)
+{
+ return encode<char>(ptr, inst);
+}
+
+template <>
+inline char* TransactLogEncoder::encode<bool>(char* ptr, bool value)
+{
+ return encode<char>(ptr, value);
+}
+
+template <>
+inline char* TransactLogEncoder::encode<float>(char* ptr, float value)
+{
+ static_assert(std::numeric_limits<float>::is_iec559 &&
+ sizeof(float) * std::numeric_limits<unsigned char>::digits == 32,
+ "Unsupported 'float' representation");
+ const char* val_ptr = reinterpret_cast<char*>(&value);
+ return realm::safe_copy_n(val_ptr, sizeof value, ptr);
+}
+
+template <>
+inline char* TransactLogEncoder::encode<double>(char* ptr, double value)
+{
+ static_assert(std::numeric_limits<double>::is_iec559 &&
+ sizeof(double) * std::numeric_limits<unsigned char>::digits == 64,
+ "Unsupported 'double' representation");
+ const char* val_ptr = reinterpret_cast<char*>(&value);
+ return realm::safe_copy_n(val_ptr, sizeof value, ptr);
+}
+
+template <>
+inline char* TransactLogEncoder::encode<DataType>(char* ptr, DataType type)
+{
+ return encode<char>(ptr, type);
+}
+
+template <>
+inline char* TransactLogEncoder::encode<StringData>(char* ptr, StringData s)
+{
+ ptr = encode_int(ptr, s.size());
+ return std::copy_n(s.data(), s.size(), ptr);
+}
+
+template <>
+inline char* TransactLogEncoder::encode<TransactLogEncoder::IntegerList>(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<TransactLogEncoder::UnsignedList>(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 <class T>
+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<TransactLogEncoder::IntegerList>(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<TransactLogEncoder::UnsignedList>(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 <class... L>
+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 <class... L>
+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 <class InstructionHandler>
+void TransactLogParser::parse(NoCopyInputStream& in, InstructionHandler& handler)
+{
+ m_input = ∈
+ m_input_begin = m_input_end = nullptr;
+
+ while (has_next())
+ parse_one(handler); // Throws
+}
+
+template <class InstructionHandler>
+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 <class InstructionHandler>
+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<int>(); // Throws
+ size_t col_ndx = read_int<size_t>(); // Throws
+ size_t row_ndx = read_int<size_t>(); // Throws
+ size_t prior_num_rows = 0;
+ if (REALM_UNLIKELY(instr == instr_SetUnique))
+ prior_num_rows = read_int<size_t>(); // 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<int64_t>(); // 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<int_fast64_t>(); // Throws
+ if (!handler.set_olddatetime(col_ndx, row_ndx, value, instr)) // Throws
+ parser_error();
+ return;
+ }
+ case type_Timestamp: {
+ int64_t seconds = read_int<int64_t>(); // Throws
+ int32_t nanoseconds = read_int<int32_t>(); // 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<size_t>(); // 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<size_t>(); // 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<size_t>(); // Throws
+ size_t row_ndx = read_int<size_t>(); // Throws
+ int_fast64_t value = read_int<int64_t>(); // Throws
+ if (!handler.add_int(col_ndx, row_ndx, value)) // Throws
+ parser_error();
+ return;
+ }
+ case instr_NullifyLink: {
+ size_t col_ndx = read_int<size_t>(); // Throws
+ size_t row_ndx = read_int<size_t>(); // Throws
+ size_t target_group_level_ndx = read_int<size_t>(); // 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<size_t>(); // Throws
+ size_t row_ndx = read_int<size_t>(); // Throws
+ size_t pos = read_int<size_t>(); // 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<size_t>(); // Throws
+ size_t row_ndx = read_int<size_t>(); // Throws
+ size_t pos = read_int<size_t>(); // Throws
+ size_t size = read_int<size_t>(); // Throws
+ if (!handler.erase_substring(col_ndx, row_ndx, pos, size)) // Throws
+ parser_error();
+ return;
+ }
+ case instr_InsertEmptyRows: {
+ size_t row_ndx = read_int<size_t>(); // Throws
+ size_t num_rows_to_insert = read_int<size_t>(); // Throws
+ size_t prior_num_rows = read_int<size_t>(); // 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<size_t>(); // Throws
+ size_t prior_num_rows = read_int<size_t>(); // Throws
+ size_t key_col_ndx = read_int<size_t>(); // Throws
+ int64_t key = read_int<int64_t>(); // 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<size_t>(); // Throws
+ size_t num_rows_to_erase = read_int<size_t>(); // Throws
+ size_t prior_num_rows = read_int<size_t>(); // 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<size_t>(); // Throws
+ size_t row_ndx_2 = read_int<size_t>(); // Throws
+ if (!handler.swap_rows(row_ndx_1, row_ndx_2)) // Throws
+ parser_error();
+ return;
+ }
+ case instr_MoveRow: {
+ size_t from_ndx = read_int<size_t>(); // Throws
+ size_t to_ndx = read_int<size_t>(); // Throws
+ if (!handler.move_row(from_ndx, to_ndx)) // Throws
+ parser_error();
+ return;
+ }
+ case instr_MergeRows: {
+ size_t row_ndx = read_int<size_t>(); // Throws
+ size_t new_row_ndx = read_int<size_t>(); // Throws
+ if (!handler.merge_rows(row_ndx, new_row_ndx)) // Throws
+ parser_error();
+ return;
+ }
+ case instr_SelectTable: {
+ int levels = read_int<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<size_t>(); // Throws
+ for (int i = 0; i != levels; ++i) {
+ size_t col_ndx = read_int<size_t>(); // Throws
+ size_t row_ndx = read_int<size_t>(); // 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<size_t>(); // Throws
+ if (!handler.clear_table(old_size)) // Throws
+ parser_error();
+ return;
+ }
+ case instr_LinkListSet: {
+ size_t link_ndx = read_int<size_t>(); // Throws
+ size_t value = read_int<size_t>(); // Throws
+ size_t prior_size = read_int<size_t>(); // 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<size_t>(); // Throws
+ for (size_t i = 0; i < size; i++) {
+ size_t link = read_int<size_t>(); // Throws
+ if (!handler.link_list_set(i, link, size)) // Throws
+ parser_error();
+ }
+ return;
+ }
+ case instr_LinkListInsert: {
+ size_t link_ndx = read_int<size_t>(); // Throws
+ size_t value = read_int<size_t>(); // Throws
+ size_t prior_size = read_int<size_t>(); // 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<size_t>(); // Throws
+ size_t to_link_ndx = read_int<size_t>(); // 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<size_t>(); // Throws
+ size_t link2_ndx = read_int<size_t>(); // Throws
+ if (!handler.link_list_swap(link1_ndx, link2_ndx)) // Throws
+ parser_error();
+ return;
+ }
+ case instr_LinkListErase: {
+ size_t link_ndx = read_int<size_t>(); // Throws
+ size_t prior_size = read_int<size_t>(); // Throws
+ if (!handler.link_list_erase(link_ndx, prior_size)) // Throws
+ parser_error();
+ return;
+ }
+ case instr_LinkListNullify: {
+ size_t link_ndx = read_int<size_t>(); // Throws
+ size_t prior_size = read_int<size_t>(); // Throws
+ if (!handler.link_list_nullify(link_ndx, prior_size)) // Throws
+ parser_error();
+ return;
+ }
+ case instr_LinkListClear: {
+ size_t old_list_size = read_int<size_t>(); // Throws
+ if (!handler.link_list_clear(old_list_size)) // Throws
+ parser_error();
+ return;
+ }
+ case instr_SelectLinkList: {
+ size_t col_ndx = read_int<size_t>(); // Throws
+ size_t row_ndx = read_int<size_t>(); // Throws
+ size_t target_group_level_ndx = read_int<size_t>(); // 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<size_t>(); // Throws
+ if (!handler.add_search_index(col_ndx)) // Throws
+ parser_error();
+ return;
+ }
+ case instr_RemoveSearchIndex: {
+ size_t col_ndx = read_int<size_t>(); // Throws
+ if (!handler.remove_search_index(col_ndx)) // Throws
+ parser_error();
+ return;
+ }
+ case instr_SetLinkType: {
+ size_t col_ndx = read_int<size_t>(); // Throws
+ int link_type = read_int<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<size_t>(); // Throws
+ int type = read_int<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<size_t>(); // Throws
+ int type = read_int<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<size_t>(); // Throws
+ size_t backlink_col_ndx = read_int<size_t>(); // 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<size_t>(); // Throws
+ if (!handler.erase_column(col_ndx)) // Throws
+ parser_error();
+ return;
+ }
+ case instr_EraseLinkColumn: {
+ size_t col_ndx = read_int<size_t>(); // Throws
+ size_t link_target_table_ndx = read_int<size_t>(); // Throws
+ size_t backlink_col_ndx = read_int<size_t>(); // 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<size_t>(); // 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<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<size_t>(); // Throws
+ path[i] = col_ndx;
+ }
+ if (!handler.select_descriptor(levels, path)) // Throws
+ parser_error();
+ return;
+ }
+ case instr_InsertGroupLevelTable: {
+ size_t table_ndx = read_int<size_t>(); // Throws
+ size_t num_tables = read_int<size_t>(); // 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<size_t>(); // Throws
+ size_t prior_num_tables = read_int<size_t>(); // 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<size_t>(); // 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 <class T>
+T TransactLogParser::read_int()
+{
+ T value = 0;
+ int part = 0;
+ const int max_bytes = (std::numeric_limits<T>::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<unsigned char>(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<char>();
+}
+
+
+inline float TransactLogParser::read_float()
+{
+ static_assert(std::numeric_limits<float>::is_iec559 &&
+ sizeof(float) * std::numeric_limits<unsigned char>::digits == 32,
+ "Unsupported 'float' representation");
+ float value;
+ read_bytes(reinterpret_cast<char*>(&value), sizeof value); // Throws
+ return value;
+}
+
+
+inline double TransactLogParser::read_double()
+{
+ static_assert(std::numeric_limits<double>::is_iec559 &&
+ sizeof(double) * std::numeric_limits<unsigned char>::digits == 64,
+ "Unsupported 'double' representation");
+ double value;
+ read_bytes(reinterpret_cast<char*>(&value), sizeof value); // Throws
+ return value;
+}
+
+
+inline StringData TransactLogParser::read_string(util::StringBuffer& buf)
+{
+ size_t size = read_int<size_t>(); // 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<int64_t>(); // Throws
+ int32_t nanoseconds = read_int<int32_t>(); // Throws
+ return Timestamp(seconds, nanoseconds);
+}
+
+
+inline BinaryData TransactLogParser::read_binary(util::StringBuffer& buf)
+{
+ size_t size = read_int<size_t>(); // Throws
+
+ return read_buffer(buf, size);
+}
+
+
+inline void TransactLogParser::read_mixed(Mixed* mixed)
+{
+ DataType type = DataType(read_int<int>()); // Throws
+ switch (type) {
+ case type_Int: {
+ int_fast64_t value = read_int<int64_t>(); // 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<int_fast64_t>(); // 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<Instr> 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<TransactReverser::Instr>& m_instr_order;
+ size_t m_current;
+};
+
+} // namespace _impl
+} // namespace realm
+
+#endif // REALM_IMPL_TRANSACT_LOG_HPP