1 /*************************************************************************
3 * Copyright 2016 Realm Inc.
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
17 **************************************************************************/
19 #ifndef REALM_ARRAY_BINARY_HPP
20 #define REALM_ARRAY_BINARY_HPP
22 #include <realm/binary_data.hpp>
23 #include <realm/array_blob.hpp>
24 #include <realm/array_integer.hpp>
25 #include <realm/exceptions.hpp>
31 ---------------------------------------------------------------------------------------
32 ArrayBinary stores binary elements using two ArrayInteger and one ArrayBlob. The ArrayBlob can only store one
33 single concecutive array of bytes (contrary to its 'Array' name that misleadingly indicates it could store multiple
36 Assume we have the strings "a", "", "abc", null, "ab". Then the three arrays will contain:
38 ArrayInteger m_offsets 1, 1, 5, 5, 6
39 ArrayBlob m_blob aabcab
40 ArrayInteger m_nulls 0, 0, 0, 1, 0 // 1 indicates null, 0 indicates non-null
42 So for each element the ArrayInteger, the ArrayInteger points into the ArrayBlob at the position of the first
43 byte of the next element.
45 m_nulls is always present (except for old database files; see below), so any ArrayBinary is always nullable!
46 The nullable property (such as throwing exception upon set(null) on non-nullable column, etc) is handled on
49 DATABASE FILE VERSION CHANGES
50 ---------------------------------------------------------------------------------------
51 Old database files do not have any m_nulls array. To be backwardscompatible, many methods will have tests like
52 `if(Array::size() == 3)` and have a backwards compatible code paths for these (e.g. avoid writing to m_nulls
53 in set(), etc). This way no file format upgrade is needed to support nulls for BinaryData.
56 class ArrayBinary : public Array {
58 explicit ArrayBinary(Allocator&) noexcept;
59 ~ArrayBinary() noexcept override
63 // Disable copying, this is not allowed.
64 ArrayBinary& operator=(const ArrayBinary&) = delete;
65 ArrayBinary(const ArrayBinary&) = delete;
67 /// Create a new empty binary array and attach this accessor to
68 /// it. This does not modify the parent reference information of
71 /// Note that the caller assumes ownership of the allocated
72 /// underlying node. It is not owned by the accessor.
75 // Old database files will not have the m_nulls array, so we need code paths for
76 // backwards compatibility for these cases.
77 bool legacy_array_type() const noexcept;
80 /// Overriding functions of Array
81 void init_from_ref(ref_type) noexcept;
82 void init_from_mem(MemRef) noexcept;
83 void init_from_parent() noexcept;
86 bool is_empty() const noexcept;
87 size_t size() const noexcept;
89 BinaryData get(size_t ndx) const noexcept;
90 size_t read(size_t ndx, size_t pos, char* buffer, size_t max_size) const noexcept;
92 void add(BinaryData value, bool add_zero_term = false);
93 void set(size_t ndx, BinaryData value, bool add_zero_term = false);
94 void insert(size_t ndx, BinaryData value, bool add_zero_term = false);
95 void erase(size_t ndx);
96 void truncate(size_t new_size);
100 /// Get the specified element without the cost of constructing an
101 /// array instance. If an array instance is already available, or
102 /// you need to get multiple values, then this method will be
104 static BinaryData get(const char* header, size_t ndx, Allocator&) noexcept;
106 ref_type bptree_leaf_insert(size_t ndx, BinaryData, bool add_zero_term, TreeInsertBase& state);
108 static size_t get_size_from_header(const char*, Allocator&) noexcept;
110 /// Construct a binary array of the specified size and return just
111 /// the reference to the underlying memory. All elements will be
112 /// initialized to the binary value `defaults`, which can be either
113 /// null or zero-length non-null (value with size > 0 is not allowed as
114 /// initialization value).
115 static MemRef create_array(size_t size, Allocator&, BinaryData defaults);
117 /// Construct a copy of the specified slice of this binary array
118 /// using the specified target allocator.
119 MemRef slice(size_t offset, size_t slice_size, Allocator& target_alloc) const;
122 void to_dot(std::ostream&, bool is_strings, StringData title = StringData()) const;
124 bool update_from_parent(size_t old_baseline) noexcept;
127 ArrayInteger m_offsets;
129 ArrayInteger m_nulls;
135 inline ArrayBinary::ArrayBinary(Allocator& allocator) noexcept
137 , m_offsets(allocator)
141 m_offsets.set_parent(this, 0);
142 m_blob.set_parent(this, 1);
143 m_nulls.set_parent(this, 2);
146 inline void ArrayBinary::create()
148 size_t init_size = 0;
149 BinaryData defaults = BinaryData{}; // This init value is ignored because size = 0
150 MemRef mem = create_array(init_size, get_alloc(), defaults); // Throws
154 inline void ArrayBinary::init_from_ref(ref_type ref) noexcept
157 char* header = get_alloc().translate(ref);
158 init_from_mem(MemRef(header, ref, m_alloc));
161 inline void ArrayBinary::init_from_parent() noexcept
163 ref_type ref = get_ref_from_parent();
167 inline bool ArrayBinary::is_empty() const noexcept
169 return m_offsets.is_empty();
172 // Old database files will not have the m_nulls array, so we need code paths for
173 // backwards compatibility for these cases. We can test if m_nulls exists by looking
174 // at number of references in this ArrayBinary.
175 inline bool ArrayBinary::legacy_array_type() const noexcept
177 if (Array::size() == 3)
178 return false; // New database file
179 else if (Array::size() == 2)
180 return true; // Old database file
182 REALM_ASSERT(false); // Should never happen
186 inline size_t ArrayBinary::size() const noexcept
188 return m_offsets.size();
191 inline BinaryData ArrayBinary::get(size_t ndx) const noexcept
193 REALM_ASSERT_3(ndx, <, m_offsets.size());
195 if (!legacy_array_type() && m_nulls.get(ndx)) {
199 size_t begin = ndx ? to_size_t(m_offsets.get(ndx - 1)) : 0;
200 size_t end = to_size_t(m_offsets.get(ndx));
202 BinaryData bd = BinaryData(m_blob.get(begin), end - begin);
203 // Old database file (non-nullable column should never return null)
204 REALM_ASSERT(!bd.is_null());
209 inline void ArrayBinary::truncate(size_t new_size)
211 REALM_ASSERT_3(new_size, <, m_offsets.size());
213 size_t blob_size = new_size ? to_size_t(m_offsets.get(new_size - 1)) : 0;
215 m_offsets.truncate(new_size);
216 m_blob.truncate(blob_size);
217 if (!legacy_array_type())
218 m_nulls.truncate(new_size);
221 inline void ArrayBinary::clear()
225 if (!legacy_array_type())
229 inline void ArrayBinary::destroy()
233 if (!legacy_array_type())
238 inline size_t ArrayBinary::get_size_from_header(const char* header, Allocator& alloc) noexcept
240 ref_type offsets_ref = to_ref(Array::get(header, 0));
241 const char* offsets_header = alloc.translate(offsets_ref);
242 return Array::get_size_from_header(offsets_header);
245 inline bool ArrayBinary::update_from_parent(size_t old_baseline) noexcept
247 bool res = Array::update_from_parent(old_baseline);
249 m_blob.update_from_parent(old_baseline);
250 m_offsets.update_from_parent(old_baseline);
251 if (!legacy_array_type())
252 m_nulls.update_from_parent(old_baseline);
259 #endif // REALM_ARRAY_BINARY_HPP