annotate CSP2/CSP2_env/env-d9b9114564458d9d-741b3de822f2aaca6c6caa4325c4afce/include/kj/table.h @ 69:33d812a61356

planemo upload commit 2e9511a184a1ca667c7be0c6321a36dc4e3d116d
author jpayne
date Tue, 18 Mar 2025 17:55:14 -0400
parents
children
rev   line source
jpayne@69 1 // Copyright (c) 2018 Kenton Varda and contributors
jpayne@69 2 // Licensed under the MIT License:
jpayne@69 3 //
jpayne@69 4 // Permission is hereby granted, free of charge, to any person obtaining a copy
jpayne@69 5 // of this software and associated documentation files (the "Software"), to deal
jpayne@69 6 // in the Software without restriction, including without limitation the rights
jpayne@69 7 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
jpayne@69 8 // copies of the Software, and to permit persons to whom the Software is
jpayne@69 9 // furnished to do so, subject to the following conditions:
jpayne@69 10 //
jpayne@69 11 // The above copyright notice and this permission notice shall be included in
jpayne@69 12 // all copies or substantial portions of the Software.
jpayne@69 13 //
jpayne@69 14 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
jpayne@69 15 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
jpayne@69 16 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
jpayne@69 17 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
jpayne@69 18 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
jpayne@69 19 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
jpayne@69 20 // THE SOFTWARE.
jpayne@69 21
jpayne@69 22 #pragma once
jpayne@69 23
jpayne@69 24 #include "common.h"
jpayne@69 25 #include "tuple.h"
jpayne@69 26 #include "vector.h"
jpayne@69 27 #include "function.h"
jpayne@69 28
jpayne@69 29 #if _MSC_VER
jpayne@69 30 // Need _ReadWriteBarrier
jpayne@69 31 #if _MSC_VER < 1910
jpayne@69 32 #include <intrin.h>
jpayne@69 33 #else
jpayne@69 34 #include <intrin0.h>
jpayne@69 35 #endif
jpayne@69 36 #endif
jpayne@69 37
jpayne@69 38 #if KJ_DEBUG_TABLE_IMPL
jpayne@69 39 #include "debug.h"
jpayne@69 40 #define KJ_TABLE_IREQUIRE KJ_REQUIRE
jpayne@69 41 #define KJ_TABLE_IASSERT KJ_ASSERT
jpayne@69 42 #else
jpayne@69 43 #define KJ_TABLE_IREQUIRE KJ_IREQUIRE
jpayne@69 44 #define KJ_TABLE_IASSERT KJ_IASSERT
jpayne@69 45 #endif
jpayne@69 46
jpayne@69 47 KJ_BEGIN_HEADER
jpayne@69 48
jpayne@69 49 namespace kj {
jpayne@69 50
jpayne@69 51 class String;
jpayne@69 52
jpayne@69 53 namespace _ { // private
jpayne@69 54
jpayne@69 55 template <typename Row>
jpayne@69 56 class TableMapping;
jpayne@69 57 template <typename Row, typename Inner>
jpayne@69 58 using TableIterable = MappedIterable<Inner, TableMapping<Row>>;
jpayne@69 59 template <typename Row, typename Inner>
jpayne@69 60 using TableIterator = MappedIterator<Inner, TableMapping<Row>>;
jpayne@69 61
jpayne@69 62 } // namespace _ (private)
jpayne@69 63
jpayne@69 64 template <typename Row, typename... Indexes>
jpayne@69 65 class Table {
jpayne@69 66 // A table with one or more indexes. This is the KJ alternative to map, set, unordered_map, and
jpayne@69 67 // unordered_set.
jpayne@69 68 //
jpayne@69 69 // Unlike a traditional map, which explicitly stores key/value pairs, a Table simply stores
jpayne@69 70 // "rows" of arbitrary type, and then lets the application specify how these should be indexed.
jpayne@69 71 // Rows could be indexed on a specific struct field, or they could be indexed based on a computed
jpayne@69 72 // property. An index could be hash-based or tree-based. Multiple indexes are supported, making
jpayne@69 73 // it easy to construct a "bimap".
jpayne@69 74 //
jpayne@69 75 // The table has deterministic iteration order based on the sequence of insertions and deletions.
jpayne@69 76 // In the case of only insertions, the iteration order is the order of insertion. If deletions
jpayne@69 77 // occur, then the current last row is moved to occupy the deleted slot. This determinism is
jpayne@69 78 // intended to be reliable for the purpose of testing, etc.
jpayne@69 79 //
jpayne@69 80 // Each index is a class that looks like:
jpayne@69 81 //
jpayne@69 82 // class Index {
jpayne@69 83 // public:
jpayne@69 84 // void reserve(size_t size);
jpayne@69 85 // // Called when Table::reserve() is called.
jpayne@69 86 //
jpayne@69 87 // SearchParam& keyForRow(const Row& row) const;
jpayne@69 88 // // Given a row, return a value appropriate to pass as SearchParams to the other functions.
jpayne@69 89 //
jpayne@69 90 // // In all function calls below, `SearchPrams` refers to whatever parameters the index
jpayne@69 91 // // supports for looking up a row in the table.
jpayne@69 92 //
jpayne@69 93 // template <typename... SearchParams>
jpayne@69 94 // kj::Maybe<size_t> insert(kj::ArrayPtr<const Row> table, size_t pos, SearchParams&&...);
jpayne@69 95 // // Called to indicate that we're about to insert a new row which will match the given
jpayne@69 96 // // search parameters, and will be located at the given position. If this index disallows
jpayne@69 97 // // duplicates and some other matching row already exists, then insert() returns the index
jpayne@69 98 // // of that row without modifying the index. If the row does not exist, then insert()
jpayne@69 99 // // updates the index to note that the new row is located at `pos`. Note that `table[pos]`
jpayne@69 100 // // may not be valid yet at the time of this call; the index must go on the search params
jpayne@69 101 // // alone.
jpayne@69 102 // //
jpayne@69 103 // // Insert may throw an exception, in which case the table will roll back insertion.
jpayne@69 104 //
jpayne@69 105 // template <typename... SearchParams>
jpayne@69 106 // void erase(kj::ArrayPtr<const Row> table, size_t pos, SearchParams&&...);
jpayne@69 107 // // Called to indicate that the index must remove references to row number `pos`. The
jpayne@69 108 // // index must not attempt to access table[pos] directly -- in fact, `pos` may be equal to
jpayne@69 109 // // `table.size()`, i.e., may be out-of-bounds (this happens when rolling back a failed
jpayne@69 110 // // insertion). Instead, the index can use the search params to search for the row -- they
jpayne@69 111 // // will either be the same as the params passed to insert(), or will be a single value of
jpayne@69 112 // // type `Row&`.
jpayne@69 113 // //
jpayne@69 114 // // erase() called immediately after a successful insert() must not throw an exception, as
jpayne@69 115 // // it may be called during unwind.
jpayne@69 116 //
jpayne@69 117 // template <typename... SearchParams>
jpayne@69 118 // void move(kj::ArrayPtr<const Row> table, size_t oldPos, size_t newPos, SearchParams&&...);
jpayne@69 119 // // Called when a row is about to be moved from `oldPos` to `newPos` in the table. The
jpayne@69 120 // // index should update it to the new location. Neither `table[oldPos]` nor `table[newPos]`
jpayne@69 121 // // is valid during the call -- use the search params to find the row. Before this call
jpayne@69 122 // // `oldPos` is indexed and `newPos` is not -- after the call, the opposite is true.
jpayne@69 123 // //
jpayne@69 124 // // This should never throw; if it does the table may be corrupted.
jpayne@69 125 //
jpayne@69 126 // class Iterator; // Behaves like a C++ iterator over size_t values.
jpayne@69 127 // class Iterable; // Has begin() and end() methods returning iterators.
jpayne@69 128 //
jpayne@69 129 // template <typename... SearchParams>
jpayne@69 130 // Maybe<size_t> find(kj::ArrayPtr<const Row> table, SearchParams&&...) const;
jpayne@69 131 // // Optional. Implements Table::find<Index>(...).
jpayne@69 132 //
jpayne@69 133 // template <typename... SearchParams>
jpayne@69 134 // Iterator seek(kj::ArrayPtr<const Row> table, SearchParams&&...) const;
jpayne@69 135 // // Optional. Implements Table::seek<Index>() and Table::range<Index>(...).
jpayne@69 136 //
jpayne@69 137 // Iterator begin() const;
jpayne@69 138 // Iterator end() const;
jpayne@69 139 // // Optional. Implements Table::ordered<Index>().
jpayne@69 140 // };
jpayne@69 141
jpayne@69 142 public:
jpayne@69 143 Table();
jpayne@69 144 Table(Indexes&&... indexes);
jpayne@69 145
jpayne@69 146 void reserve(size_t size);
jpayne@69 147 // Pre-allocates space for a table of the given size. Normally a Table grows by re-allocating
jpayne@69 148 // its backing array whenever more space is needed. Reserving in advance avoids redundantly
jpayne@69 149 // re-allocating as the table grows.
jpayne@69 150
jpayne@69 151 size_t size() const;
jpayne@69 152 size_t capacity() const;
jpayne@69 153
jpayne@69 154 void clear();
jpayne@69 155
jpayne@69 156 Row* begin();
jpayne@69 157 Row* end();
jpayne@69 158 const Row* begin() const;
jpayne@69 159 const Row* end() const;
jpayne@69 160
jpayne@69 161 Row& insert(Row&& row);
jpayne@69 162 Row& insert(const Row& row);
jpayne@69 163 // Inserts a new row. Throws an exception if this would violate the uniqueness constraints of any
jpayne@69 164 // of the indexes.
jpayne@69 165
jpayne@69 166 template <typename Collection>
jpayne@69 167 void insertAll(Collection&& collection);
jpayne@69 168 template <typename Collection>
jpayne@69 169 void insertAll(Collection& collection);
jpayne@69 170 // Given an iterable collection of Rows, inserts all of them into this table. If the input is
jpayne@69 171 // an rvalue, the rows will be moved rather than copied.
jpayne@69 172 //
jpayne@69 173 // If an insertion throws (e.g. because it violates a uniqueness constraint of some index),
jpayne@69 174 // subsequent insertions do not occur, but previous insertions remain inserted.
jpayne@69 175
jpayne@69 176 template <typename UpdateFunc>
jpayne@69 177 Row& upsert(Row&& row, UpdateFunc&& update);
jpayne@69 178 template <typename UpdateFunc>
jpayne@69 179 Row& upsert(const Row& row, UpdateFunc&& update);
jpayne@69 180 // Tries to insert a new row. However, if a duplicate already exists (according to some index),
jpayne@69 181 // then update(Row& existingRow, Row&& newRow) is called to modify the existing row.
jpayne@69 182
jpayne@69 183 template <typename Index, typename... Params>
jpayne@69 184 kj::Maybe<Row&> find(Params&&... params);
jpayne@69 185 template <typename Index, typename... Params>
jpayne@69 186 kj::Maybe<const Row&> find(Params&&... params) const;
jpayne@69 187 // Using the given index, search for a matching row. What parameters are accepted depends on the
jpayne@69 188 // index. Not all indexes support this method -- "multimap" indexes may support only range().
jpayne@69 189
jpayne@69 190 template <typename Index, typename... Params, typename Func>
jpayne@69 191 Row& findOrCreate(Params&&... params, Func&& createFunc);
jpayne@69 192 // Like find(), but if the row doesn't exist, call a function to create it. createFunc() must
jpayne@69 193 // return `Row` or something that implicitly converts to `Row`.
jpayne@69 194 //
jpayne@69 195 // NOTE: C++ doesn't actually properly support inferring types of a parameter pack at the
jpayne@69 196 // beginning of an argument list, but we define a hack to support it below. Don't worry about
jpayne@69 197 // it.
jpayne@69 198
jpayne@69 199 template <typename Index, typename BeginKey, typename EndKey>
jpayne@69 200 auto range(BeginKey&& begin, EndKey&& end);
jpayne@69 201 template <typename Index, typename BeginKey, typename EndKey>
jpayne@69 202 auto range(BeginKey&& begin, EndKey&& end) const;
jpayne@69 203 // Using the given index, look up a range of values, returning an iterable. What parameters are
jpayne@69 204 // accepted depends on the index. Not all indexes support this method (in particular, unordered
jpayne@69 205 // indexes normally don't).
jpayne@69 206
jpayne@69 207 template <typename Index>
jpayne@69 208 _::TableIterable<Row, Index&> ordered();
jpayne@69 209 template <typename Index>
jpayne@69 210 _::TableIterable<const Row, const Index&> ordered() const;
jpayne@69 211 // Returns an iterable over the whole table ordered using the given index. Not all indexes
jpayne@69 212 // support this method.
jpayne@69 213
jpayne@69 214 template <typename Index, typename... Params>
jpayne@69 215 auto seek(Params&&... params);
jpayne@69 216 template <typename Index, typename... Params>
jpayne@69 217 auto seek(Params&&... params) const;
jpayne@69 218 // Takes same parameters as find(), but returns an iterator at the position where the search
jpayne@69 219 // key should go. That is, this returns an iterator that points to the matching entry or, if
jpayne@69 220 // there is no matching entry, points at the next entry after the key, in order. Or, if there
jpayne@69 221 // is no such entry, the returned iterator is the same as ordered().end().
jpayne@69 222 //
jpayne@69 223 // seek() is only supported by indexes that support ordered(). It returns the same kind of
jpayne@69 224 // iterator that ordered() uses.
jpayne@69 225
jpayne@69 226 template <typename Index, typename... Params>
jpayne@69 227 bool eraseMatch(Params&&... params);
jpayne@69 228 // Erase the row that would be matched by `find<Index>(params)`. Returns true if there was a
jpayne@69 229 // match.
jpayne@69 230
jpayne@69 231 template <typename Index, typename BeginKey, typename EndKey>
jpayne@69 232 size_t eraseRange(BeginKey&& begin, EndKey&& end);
jpayne@69 233 // Erase the row that would be matched by `range<Index>(params)`. Returns the number of
jpayne@69 234 // elements erased.
jpayne@69 235
jpayne@69 236 void erase(Row& row);
jpayne@69 237 // Erase the given row.
jpayne@69 238 //
jpayne@69 239 // WARNING: This invalidates all iterators, so you can't iterate over rows and erase them this
jpayne@69 240 // way. Use `eraseAll()` for that.
jpayne@69 241
jpayne@69 242 Row release(Row& row);
jpayne@69 243 // Remove the given row from the table and return it in one operation.
jpayne@69 244 //
jpayne@69 245 // WARNING: This invalidates all iterators, so you can't iterate over rows and release them this
jpayne@69 246 // way.
jpayne@69 247
jpayne@69 248 template <typename Predicate, typename = decltype(instance<Predicate>()(instance<Row&>()))>
jpayne@69 249 size_t eraseAll(Predicate&& predicate);
jpayne@69 250 // Erase all rows for which predicate(row) returns true. This scans over the entire table.
jpayne@69 251
jpayne@69 252 template <typename Collection, typename = decltype(instance<Collection>().begin()), bool = true>
jpayne@69 253 size_t eraseAll(Collection&& collection);
jpayne@69 254 // Erase all rows in the given iterable collection of rows. This carefully marks rows for
jpayne@69 255 // deletion in a first pass then deletes them in a second.
jpayne@69 256
jpayne@69 257 template <size_t index = 0, typename... Params>
jpayne@69 258 kj::Maybe<Row&> find(Params&&... params);
jpayne@69 259 template <size_t index = 0, typename... Params>
jpayne@69 260 kj::Maybe<const Row&> find(Params&&... params) const;
jpayne@69 261 template <size_t index = 0, typename... Params, typename Func>
jpayne@69 262 Row& findOrCreate(Params&&... params, Func&& createFunc);
jpayne@69 263 template <size_t index = 0, typename BeginKey, typename EndKey>
jpayne@69 264 auto range(BeginKey&& begin, EndKey&& end);
jpayne@69 265 template <size_t index = 0, typename BeginKey, typename EndKey>
jpayne@69 266 auto range(BeginKey&& begin, EndKey&& end) const;
jpayne@69 267 template <size_t index = 0>
jpayne@69 268 _::TableIterable<Row, TypeOfIndex<index, Tuple<Indexes...>>&> ordered();
jpayne@69 269 template <size_t index = 0>
jpayne@69 270 _::TableIterable<const Row, const TypeOfIndex<index, Tuple<Indexes...>>&> ordered() const;
jpayne@69 271 template <size_t index = 0, typename... Params>
jpayne@69 272 auto seek(Params&&... params);
jpayne@69 273 template <size_t index = 0, typename... Params>
jpayne@69 274 auto seek(Params&&... params) const;
jpayne@69 275 template <size_t index = 0, typename... Params>
jpayne@69 276 bool eraseMatch(Params&&... params);
jpayne@69 277 template <size_t index = 0, typename BeginKey, typename EndKey>
jpayne@69 278 size_t eraseRange(BeginKey&& begin, EndKey&& end);
jpayne@69 279 // Methods which take an index type as a template parameter can also take an index number. This
jpayne@69 280 // is useful particularly when you have multiple indexes of the same type but different runtime
jpayne@69 281 // properties. Additionally, you can omit the template parameter altogether to use the first
jpayne@69 282 // index.
jpayne@69 283
jpayne@69 284 template <size_t index = 0>
jpayne@69 285 void verify();
jpayne@69 286 // Checks the integrity of indexes, throwing an exception if there are any problems. This is
jpayne@69 287 // intended to be called within the unit test for an index.
jpayne@69 288
jpayne@69 289 template <typename Index, typename First, typename... Rest>
jpayne@69 290 Row& findOrCreate(First&& first, Rest&&... rest);
jpayne@69 291 template <size_t index = 0, typename First, typename... Rest>
jpayne@69 292 Row& findOrCreate(First&& first, Rest&&... rest);
jpayne@69 293 // HACK: A parameter pack can only be inferred if it lives at the end of the argument list, so
jpayne@69 294 // the findOrCreate() definitions from earlier won't actually work. These ones will, but we
jpayne@69 295 // have to do some annoying things inside to regroup the arguments.
jpayne@69 296
jpayne@69 297 private:
jpayne@69 298 Vector<Row> rows;
jpayne@69 299 Tuple<Indexes...> indexes;
jpayne@69 300
jpayne@69 301 template <size_t index = 0, bool final = (index >= sizeof...(Indexes))>
jpayne@69 302 class Impl;
jpayne@69 303 template <typename Func, typename... Params>
jpayne@69 304 class FindOrCreateImpl;
jpayne@69 305
jpayne@69 306 template <typename ParamsTuple, typename... Params>
jpayne@69 307 struct FindOrCreateHack;
jpayne@69 308
jpayne@69 309 void eraseImpl(size_t pos);
jpayne@69 310 template <typename Collection>
jpayne@69 311 size_t eraseAllImpl(Collection&& collection);
jpayne@69 312 };
jpayne@69 313
jpayne@69 314 template <typename Callbacks>
jpayne@69 315 class HashIndex;
jpayne@69 316 // A Table index based on a hash table.
jpayne@69 317 //
jpayne@69 318 // This implementation:
jpayne@69 319 // * Is based on linear probing, not chaining. It is important to use a high-quality hash function.
jpayne@69 320 // Use the KJ hashing library if possible.
jpayne@69 321 // * Is limited to tables of 2^30 rows or less, mainly to allow for tighter packing with 32-bit
jpayne@69 322 // integers instead of 64-bit.
jpayne@69 323 // * Caches hash codes so that each table row need only be hashed once, and never checks equality
jpayne@69 324 // unless hash codes have already been determined to be equal.
jpayne@69 325 //
jpayne@69 326 // The `Callbacks` type defines how to compute hash codes and equality. It should be defined like:
jpayne@69 327 //
jpayne@69 328 // class Callbacks {
jpayne@69 329 // public:
jpayne@69 330 // // In this interface, `SearchParams...` means whatever parameters you want to support in
jpayne@69 331 // // a call to table.find(...). By overloading the calls to support various inputs, you can
jpayne@69 332 // // affect what table.find(...) accepts.
jpayne@69 333 //
jpayne@69 334 // SearchParam& keyForRow(const Row& row);
jpayne@69 335 // // Given a row of the table, return the SearchParams that might be passed to the other
jpayne@69 336 // // methods to match this row.
jpayne@69 337 //
jpayne@69 338 // bool matches(const Row&, SearchParams&&...) const;
jpayne@69 339 // // Returns true if the row on the left matches the search params on the right.
jpayne@69 340 //
jpayne@69 341 // uint hashCode(SearchParams&&...) const;
jpayne@69 342 // // Computes the hash code of the given search params. Matching rows (as determined by
jpayne@69 343 // // matches()) must have the same hash code. Non-matching rows should have different hash
jpayne@69 344 // // codes, to the maximum extent possible. Non-matching rows with the same hash code hurt
jpayne@69 345 // // performance.
jpayne@69 346 // };
jpayne@69 347 //
jpayne@69 348 // If your `Callbacks` type has dynamic state, you may pass its constructor parameters as the
jpayne@69 349 // constructor parameters to `HashIndex`.
jpayne@69 350
jpayne@69 351 template <typename Callbacks>
jpayne@69 352 class TreeIndex;
jpayne@69 353 // A Table index based on a B-tree.
jpayne@69 354 //
jpayne@69 355 // This allows sorted iteration over rows.
jpayne@69 356 //
jpayne@69 357 // The `Callbacks` type defines how to compare rows. It should be defined like:
jpayne@69 358 //
jpayne@69 359 // class Callbacks {
jpayne@69 360 // public:
jpayne@69 361 // // In this interface, `SearchParams...` means whatever parameters you want to support in
jpayne@69 362 // // a call to table.find(...). By overloading the calls to support various inputs, you can
jpayne@69 363 // // affect what table.find(...) accepts.
jpayne@69 364 //
jpayne@69 365 // SearchParam& keyForRow(const Row& row);
jpayne@69 366 // // Given a row of the table, return the SearchParams that might be passed to the other
jpayne@69 367 // // methods to match this row.
jpayne@69 368 //
jpayne@69 369 // bool isBefore(const Row&, SearchParams&&...) const;
jpayne@69 370 // // Returns true if the row on the left comes before the search params on the right.
jpayne@69 371 //
jpayne@69 372 // bool matches(const Row&, SearchParams&&...) const;
jpayne@69 373 // // Returns true if the row "matches" the search params.
jpayne@69 374 // };
jpayne@69 375
jpayne@69 376 // =======================================================================================
jpayne@69 377 // inline implementation details
jpayne@69 378
jpayne@69 379 namespace _ { // private
jpayne@69 380
jpayne@69 381 KJ_NORETURN(void throwDuplicateTableRow());
jpayne@69 382
jpayne@69 383 template <typename Dst, typename Src, typename = decltype(instance<Src>().size())>
jpayne@69 384 inline void tryReserveSize(Dst& dst, Src&& src) { dst.reserve(dst.size() + src.size()); }
jpayne@69 385 template <typename... Params>
jpayne@69 386 inline void tryReserveSize(Params&&...) {}
jpayne@69 387 // If `src` has a `.size()` method, call dst.reserve(dst.size() + src.size()).
jpayne@69 388 // Otherwise, do nothing.
jpayne@69 389
jpayne@69 390 template <typename Row>
jpayne@69 391 class TableMapping {
jpayne@69 392 public:
jpayne@69 393 TableMapping(Row* table): table(table) {}
jpayne@69 394 Row& map(size_t i) const { return table[i]; }
jpayne@69 395
jpayne@69 396 private:
jpayne@69 397 Row* table;
jpayne@69 398 };
jpayne@69 399
jpayne@69 400 template <typename Row>
jpayne@69 401 class TableUnmapping {
jpayne@69 402 public:
jpayne@69 403 TableUnmapping(Row* table): table(table) {}
jpayne@69 404 size_t map(Row& row) const { return &row - table; }
jpayne@69 405 size_t map(Row* row) const { return row - table; }
jpayne@69 406
jpayne@69 407 private:
jpayne@69 408 Row* table;
jpayne@69 409 };
jpayne@69 410
jpayne@69 411 template <typename Iterator>
jpayne@69 412 class IterRange {
jpayne@69 413 public:
jpayne@69 414 inline IterRange(Iterator b, Iterator e): b(b), e(e) {}
jpayne@69 415
jpayne@69 416 inline Iterator begin() const { return b; }
jpayne@69 417 inline Iterator end() const { return e; }
jpayne@69 418 private:
jpayne@69 419 Iterator b;
jpayne@69 420 Iterator e;
jpayne@69 421 };
jpayne@69 422
jpayne@69 423 template <typename Iterator>
jpayne@69 424 inline IterRange<Decay<Iterator>> iterRange(Iterator b, Iterator e) {
jpayne@69 425 return { b, e };
jpayne@69 426 }
jpayne@69 427
jpayne@69 428 } // namespace _ (private)
jpayne@69 429
jpayne@69 430 template <typename Row, typename... Indexes>
jpayne@69 431 template <size_t index>
jpayne@69 432 class Table<Row, Indexes...>::Impl<index, false> {
jpayne@69 433 public:
jpayne@69 434 static void reserve(Table<Row, Indexes...>& table, size_t size) {
jpayne@69 435 get<index>(table.indexes).reserve(size);
jpayne@69 436 Impl<index + 1>::reserve(table, size);
jpayne@69 437 }
jpayne@69 438
jpayne@69 439 static void clear(Table<Row, Indexes...>& table) {
jpayne@69 440 get<index>(table.indexes).clear();
jpayne@69 441 Impl<index + 1>::clear(table);
jpayne@69 442 }
jpayne@69 443
jpayne@69 444 static kj::Maybe<size_t> insert(Table<Row, Indexes...>& table, size_t pos, Row& row, uint skip) {
jpayne@69 445 if (skip == index) {
jpayne@69 446 return Impl<index + 1>::insert(table, pos, row, skip);
jpayne@69 447 }
jpayne@69 448 auto& indexObj = get<index>(table.indexes);
jpayne@69 449 KJ_IF_MAYBE(existing, indexObj.insert(table.rows.asPtr(), pos, indexObj.keyForRow(row))) {
jpayne@69 450 return *existing;
jpayne@69 451 }
jpayne@69 452
jpayne@69 453 bool success = false;
jpayne@69 454 KJ_DEFER(if (!success) {
jpayne@69 455 indexObj.erase(table.rows.asPtr(), pos, indexObj.keyForRow(row));
jpayne@69 456 });
jpayne@69 457 auto result = Impl<index + 1>::insert(table, pos, row, skip);
jpayne@69 458 success = result == nullptr;
jpayne@69 459 return result;
jpayne@69 460 }
jpayne@69 461
jpayne@69 462 static void erase(Table<Row, Indexes...>& table, size_t pos, Row& row) {
jpayne@69 463 auto& indexObj = get<index>(table.indexes);
jpayne@69 464 indexObj.erase(table.rows.asPtr(), pos, indexObj.keyForRow(row));
jpayne@69 465 Impl<index + 1>::erase(table, pos, row);
jpayne@69 466 }
jpayne@69 467
jpayne@69 468 static void move(Table<Row, Indexes...>& table, size_t oldPos, size_t newPos, Row& row) {
jpayne@69 469 auto& indexObj = get<index>(table.indexes);
jpayne@69 470 indexObj.move(table.rows.asPtr(), oldPos, newPos, indexObj.keyForRow(row));
jpayne@69 471 Impl<index + 1>::move(table, oldPos, newPos, row);
jpayne@69 472 }
jpayne@69 473 };
jpayne@69 474
jpayne@69 475 template <typename Row, typename... Indexes>
jpayne@69 476 template <size_t index>
jpayne@69 477 class Table<Row, Indexes...>::Impl<index, true> {
jpayne@69 478 public:
jpayne@69 479 static void reserve(Table<Row, Indexes...>& table, size_t size) {}
jpayne@69 480 static void clear(Table<Row, Indexes...>& table) {}
jpayne@69 481 static kj::Maybe<size_t> insert(Table<Row, Indexes...>& table, size_t pos, Row& row, uint skip) {
jpayne@69 482 return nullptr;
jpayne@69 483 }
jpayne@69 484 static void erase(Table<Row, Indexes...>& table, size_t pos, Row& row) {}
jpayne@69 485 static void move(Table<Row, Indexes...>& table, size_t oldPos, size_t newPos, Row& row) {}
jpayne@69 486 };
jpayne@69 487
jpayne@69 488 template <typename Row, typename... Indexes>
jpayne@69 489 Table<Row, Indexes...>::Table() {}
jpayne@69 490
jpayne@69 491 template <typename Row, typename... Indexes>
jpayne@69 492 Table<Row, Indexes...>::Table(Indexes&&... indexes)
jpayne@69 493 : indexes(tuple(kj::fwd<Indexes&&>(indexes)...)) {}
jpayne@69 494
jpayne@69 495 template <typename Row, typename... Indexes>
jpayne@69 496 void Table<Row, Indexes...>::reserve(size_t size) {
jpayne@69 497 rows.reserve(size);
jpayne@69 498 Impl<>::reserve(*this, size);
jpayne@69 499 }
jpayne@69 500
jpayne@69 501 template <typename Row, typename... Indexes>
jpayne@69 502 size_t Table<Row, Indexes...>::size() const {
jpayne@69 503 return rows.size();
jpayne@69 504 }
jpayne@69 505 template <typename Row, typename... Indexes>
jpayne@69 506 void Table<Row, Indexes...>::clear() {
jpayne@69 507 Impl<>::clear(*this);
jpayne@69 508 rows.clear();
jpayne@69 509 }
jpayne@69 510 template <typename Row, typename... Indexes>
jpayne@69 511 size_t Table<Row, Indexes...>::capacity() const {
jpayne@69 512 return rows.capacity();
jpayne@69 513 }
jpayne@69 514
jpayne@69 515 template <typename Row, typename... Indexes>
jpayne@69 516 Row* Table<Row, Indexes...>::begin() {
jpayne@69 517 return rows.begin();
jpayne@69 518 }
jpayne@69 519 template <typename Row, typename... Indexes>
jpayne@69 520 Row* Table<Row, Indexes...>::end() {
jpayne@69 521 return rows.end();
jpayne@69 522 }
jpayne@69 523 template <typename Row, typename... Indexes>
jpayne@69 524 const Row* Table<Row, Indexes...>::begin() const {
jpayne@69 525 return rows.begin();
jpayne@69 526 }
jpayne@69 527 template <typename Row, typename... Indexes>
jpayne@69 528 const Row* Table<Row, Indexes...>::end() const {
jpayne@69 529 return rows.end();
jpayne@69 530 }
jpayne@69 531
jpayne@69 532 template <typename Row, typename... Indexes>
jpayne@69 533 Row& Table<Row, Indexes...>::insert(Row&& row) {
jpayne@69 534 KJ_IF_MAYBE(existing, Impl<>::insert(*this, rows.size(), row, kj::maxValue)) {
jpayne@69 535 _::throwDuplicateTableRow();
jpayne@69 536 } else {
jpayne@69 537 return rows.add(kj::mv(row));
jpayne@69 538 }
jpayne@69 539 }
jpayne@69 540 template <typename Row, typename... Indexes>
jpayne@69 541 Row& Table<Row, Indexes...>::insert(const Row& row) {
jpayne@69 542 return insert(kj::cp(row));
jpayne@69 543 }
jpayne@69 544
jpayne@69 545 template <typename Row, typename... Indexes>
jpayne@69 546 template <typename Collection>
jpayne@69 547 void Table<Row, Indexes...>::insertAll(Collection&& collection) {
jpayne@69 548 _::tryReserveSize(*this, collection);
jpayne@69 549 for (auto& row: collection) {
jpayne@69 550 insert(kj::mv(row));
jpayne@69 551 }
jpayne@69 552 }
jpayne@69 553
jpayne@69 554 template <typename Row, typename... Indexes>
jpayne@69 555 template <typename Collection>
jpayne@69 556 void Table<Row, Indexes...>::insertAll(Collection& collection) {
jpayne@69 557 _::tryReserveSize(*this, collection);
jpayne@69 558 for (auto& row: collection) {
jpayne@69 559 insert(row);
jpayne@69 560 }
jpayne@69 561 }
jpayne@69 562
jpayne@69 563 template <typename Row, typename... Indexes>
jpayne@69 564 template <typename UpdateFunc>
jpayne@69 565 Row& Table<Row, Indexes...>::upsert(Row&& row, UpdateFunc&& update) {
jpayne@69 566 KJ_IF_MAYBE(existing, Impl<>::insert(*this, rows.size(), row, kj::maxValue)) {
jpayne@69 567 update(rows[*existing], kj::mv(row));
jpayne@69 568 return rows[*existing];
jpayne@69 569 } else {
jpayne@69 570 return rows.add(kj::mv(row));
jpayne@69 571 }
jpayne@69 572 }
jpayne@69 573 template <typename Row, typename... Indexes>
jpayne@69 574 template <typename UpdateFunc>
jpayne@69 575 Row& Table<Row, Indexes...>::upsert(const Row& row, UpdateFunc&& update) {
jpayne@69 576 return upsert(kj::cp(row), kj::fwd<UpdateFunc>(update));
jpayne@69 577 }
jpayne@69 578
jpayne@69 579 template <typename Row, typename... Indexes>
jpayne@69 580 template <typename Index, typename... Params>
jpayne@69 581 kj::Maybe<Row&> Table<Row, Indexes...>::find(Params&&... params) {
jpayne@69 582 return find<indexOfType<Index, Tuple<Indexes...>>()>(kj::fwd<Params>(params)...);
jpayne@69 583 }
jpayne@69 584 template <typename Row, typename... Indexes>
jpayne@69 585 template <size_t index, typename... Params>
jpayne@69 586 kj::Maybe<Row&> Table<Row, Indexes...>::find(Params&&... params) {
jpayne@69 587 KJ_IF_MAYBE(pos, get<index>(indexes).find(rows.asPtr(), kj::fwd<Params>(params)...)) {
jpayne@69 588 return rows[*pos];
jpayne@69 589 } else {
jpayne@69 590 return nullptr;
jpayne@69 591 }
jpayne@69 592 }
jpayne@69 593 template <typename Row, typename... Indexes>
jpayne@69 594 template <typename Index, typename... Params>
jpayne@69 595 kj::Maybe<const Row&> Table<Row, Indexes...>::find(Params&&... params) const {
jpayne@69 596 return find<indexOfType<Index, Tuple<Indexes...>>()>(kj::fwd<Params>(params)...);
jpayne@69 597 }
jpayne@69 598 template <typename Row, typename... Indexes>
jpayne@69 599 template <size_t index, typename... Params>
jpayne@69 600 kj::Maybe<const Row&> Table<Row, Indexes...>::find(Params&&... params) const {
jpayne@69 601 KJ_IF_MAYBE(pos, get<index>(indexes).find(rows.asPtr(), kj::fwd<Params>(params)...)) {
jpayne@69 602 return rows[*pos];
jpayne@69 603 } else {
jpayne@69 604 return nullptr;
jpayne@69 605 }
jpayne@69 606 }
jpayne@69 607
jpayne@69 608 template <typename Row, typename... Indexes>
jpayne@69 609 template <typename Func, typename... Params>
jpayne@69 610 class Table<Row, Indexes...>::FindOrCreateImpl {
jpayne@69 611 public:
jpayne@69 612 template <size_t index>
jpayne@69 613 static Row& apply(Table<Row, Indexes...>& table, Params&&... params, Func&& createFunc) {
jpayne@69 614 auto pos = table.rows.size();
jpayne@69 615 KJ_IF_MAYBE(existing, get<index>(table.indexes).insert(table.rows.asPtr(), pos, params...)) {
jpayne@69 616 return table.rows[*existing];
jpayne@69 617 } else {
jpayne@69 618 bool success = false;
jpayne@69 619 KJ_DEFER({
jpayne@69 620 if (!success) {
jpayne@69 621 get<index>(table.indexes).erase(table.rows.asPtr(), pos, params...);
jpayne@69 622 }
jpayne@69 623 });
jpayne@69 624 auto& newRow = table.rows.add(createFunc());
jpayne@69 625 KJ_DEFER({
jpayne@69 626 if (!success) {
jpayne@69 627 table.rows.removeLast();
jpayne@69 628 }
jpayne@69 629 });
jpayne@69 630 if (Table<Row, Indexes...>::template Impl<>::insert(table, pos, newRow, index) == nullptr) {
jpayne@69 631 success = true;
jpayne@69 632 } else {
jpayne@69 633 _::throwDuplicateTableRow();
jpayne@69 634 }
jpayne@69 635 return newRow;
jpayne@69 636 }
jpayne@69 637 }
jpayne@69 638 };
jpayne@69 639
jpayne@69 640 template <typename Row, typename... Indexes>
jpayne@69 641 template <typename... T, typename U, typename V, typename... W>
jpayne@69 642 struct Table<Row, Indexes...>::FindOrCreateHack<_::Tuple<T...>, U, V, W...>
jpayne@69 643 : public FindOrCreateHack<_::Tuple<T..., U>, V, W...> {};
jpayne@69 644 template <typename Row, typename... Indexes>
jpayne@69 645 template <typename... T, typename U>
jpayne@69 646 struct Table<Row, Indexes...>::FindOrCreateHack<_::Tuple<T...>, U>
jpayne@69 647 : public FindOrCreateImpl<U, T...> {};
jpayne@69 648 // This awful hack works around C++'s lack of support for parameter packs anywhere other than at
jpayne@69 649 // the end of an argument list. We accumulate all of the types except for the last one into a
jpayne@69 650 // Tuple, then forward to FindOrCreateImpl with the last parameter as the Func.
jpayne@69 651
jpayne@69 652 template <typename Row, typename... Indexes>
jpayne@69 653 template <typename Index, typename First, typename... Rest>
jpayne@69 654 Row& Table<Row, Indexes...>::findOrCreate(First&& first, Rest&&... rest) {
jpayne@69 655 return findOrCreate<indexOfType<Index, Tuple<Indexes...>>()>(
jpayne@69 656 kj::fwd<First>(first), kj::fwd<Rest>(rest)...);
jpayne@69 657 }
jpayne@69 658 template <typename Row, typename... Indexes>
jpayne@69 659 template <size_t index, typename First, typename... Rest>
jpayne@69 660 Row& Table<Row, Indexes...>::findOrCreate(First&& first, Rest&&... rest) {
jpayne@69 661 return FindOrCreateHack<_::Tuple<>, First, Rest...>::template apply<index>(
jpayne@69 662 *this, kj::fwd<First>(first), kj::fwd<Rest>(rest)...);
jpayne@69 663 }
jpayne@69 664
jpayne@69 665 template <typename Row, typename... Indexes>
jpayne@69 666 template <typename Index, typename BeginKey, typename EndKey>
jpayne@69 667 auto Table<Row, Indexes...>::range(BeginKey&& begin, EndKey&& end) {
jpayne@69 668 return range<indexOfType<Index, Tuple<Indexes...>>()>(
jpayne@69 669 kj::fwd<BeginKey>(begin), kj::fwd<EndKey>(end));
jpayne@69 670 }
jpayne@69 671 template <typename Row, typename... Indexes>
jpayne@69 672 template <size_t index, typename BeginKey, typename EndKey>
jpayne@69 673 auto Table<Row, Indexes...>::range(BeginKey&& begin, EndKey&& end) {
jpayne@69 674 auto inner = _::iterRange(get<index>(indexes).seek(rows.asPtr(), kj::fwd<BeginKey>(begin)),
jpayne@69 675 get<index>(indexes).seek(rows.asPtr(), kj::fwd<EndKey>(end)));
jpayne@69 676 return _::TableIterable<Row, decltype(inner)>(kj::mv(inner), rows.begin());
jpayne@69 677 }
jpayne@69 678 template <typename Row, typename... Indexes>
jpayne@69 679 template <typename Index, typename BeginKey, typename EndKey>
jpayne@69 680 auto Table<Row, Indexes...>::range(BeginKey&& begin, EndKey&& end) const {
jpayne@69 681 return range<indexOfType<Index, Tuple<Indexes...>>()>(
jpayne@69 682 kj::fwd<BeginKey>(begin), kj::fwd<EndKey>(end));
jpayne@69 683 }
jpayne@69 684 template <typename Row, typename... Indexes>
jpayne@69 685 template <size_t index, typename BeginKey, typename EndKey>
jpayne@69 686 auto Table<Row, Indexes...>::range(BeginKey&& begin, EndKey&& end) const {
jpayne@69 687 auto inner = _::iterRange(get<index>(indexes).seek(rows.asPtr(), kj::fwd<BeginKey>(begin)),
jpayne@69 688 get<index>(indexes).seek(rows.asPtr(), kj::fwd<EndKey>(end)));
jpayne@69 689 return _::TableIterable<const Row, decltype(inner)>(kj::mv(inner), rows.begin());
jpayne@69 690 }
jpayne@69 691
jpayne@69 692 template <typename Row, typename... Indexes>
jpayne@69 693 template <typename Index>
jpayne@69 694 _::TableIterable<Row, Index&> Table<Row, Indexes...>::ordered() {
jpayne@69 695 return ordered<indexOfType<Index, Tuple<Indexes...>>()>();
jpayne@69 696 }
jpayne@69 697 template <typename Row, typename... Indexes>
jpayne@69 698 template <size_t index>
jpayne@69 699 _::TableIterable<Row, TypeOfIndex<index, Tuple<Indexes...>>&> Table<Row, Indexes...>::ordered() {
jpayne@69 700 return { get<index>(indexes), rows.begin() };
jpayne@69 701 }
jpayne@69 702 template <typename Row, typename... Indexes>
jpayne@69 703 template <typename Index>
jpayne@69 704 _::TableIterable<const Row, const Index&> Table<Row, Indexes...>::ordered() const {
jpayne@69 705 return ordered<indexOfType<Index, Tuple<Indexes...>>()>();
jpayne@69 706 }
jpayne@69 707 template <typename Row, typename... Indexes>
jpayne@69 708 template <size_t index>
jpayne@69 709 _::TableIterable<const Row, const TypeOfIndex<index, Tuple<Indexes...>>&>
jpayne@69 710 Table<Row, Indexes...>::ordered() const {
jpayne@69 711 return { get<index>(indexes), rows.begin() };
jpayne@69 712 }
jpayne@69 713
jpayne@69 714 template <typename Row, typename... Indexes>
jpayne@69 715 template <typename Index, typename... Params>
jpayne@69 716 auto Table<Row, Indexes...>::seek(Params&&... params) {
jpayne@69 717 return seek<indexOfType<Index, Tuple<Indexes...>>()>(kj::fwd<Params>(params)...);
jpayne@69 718 }
jpayne@69 719 template <typename Row, typename... Indexes>
jpayne@69 720 template <size_t index, typename... Params>
jpayne@69 721 auto Table<Row, Indexes...>::seek(Params&&... params) {
jpayne@69 722 auto inner = get<index>(indexes).seek(rows.asPtr(), kj::fwd<Params>(params)...);
jpayne@69 723 return _::TableIterator<Row, decltype(inner)>(kj::mv(inner), rows.begin());
jpayne@69 724 }
jpayne@69 725 template <typename Row, typename... Indexes>
jpayne@69 726 template <typename Index, typename... Params>
jpayne@69 727 auto Table<Row, Indexes...>::seek(Params&&... params) const {
jpayne@69 728 return seek<indexOfType<Index, Tuple<Indexes...>>()>(kj::fwd<Params>(params)...);
jpayne@69 729 }
jpayne@69 730 template <typename Row, typename... Indexes>
jpayne@69 731 template <size_t index, typename... Params>
jpayne@69 732 auto Table<Row, Indexes...>::seek(Params&&... params) const {
jpayne@69 733 auto inner = get<index>(indexes).seek(rows.asPtr(), kj::fwd<Params>(params)...);
jpayne@69 734 return _::TableIterator<Row, decltype(inner)>(kj::mv(inner), rows.begin());
jpayne@69 735 }
jpayne@69 736
jpayne@69 737 template <typename Row, typename... Indexes>
jpayne@69 738 template <typename Index, typename... Params>
jpayne@69 739 bool Table<Row, Indexes...>::eraseMatch(Params&&... params) {
jpayne@69 740 return eraseMatch<indexOfType<Index, Tuple<Indexes...>>()>(kj::fwd<Params>(params)...);
jpayne@69 741 }
jpayne@69 742 template <typename Row, typename... Indexes>
jpayne@69 743 template <size_t index, typename... Params>
jpayne@69 744 bool Table<Row, Indexes...>::eraseMatch(Params&&... params) {
jpayne@69 745 KJ_IF_MAYBE(pos, get<index>(indexes).find(rows.asPtr(), kj::fwd<Params>(params)...)) {
jpayne@69 746 eraseImpl(*pos);
jpayne@69 747 return true;
jpayne@69 748 } else {
jpayne@69 749 return false;
jpayne@69 750 }
jpayne@69 751 }
jpayne@69 752
jpayne@69 753 template <typename Row, typename... Indexes>
jpayne@69 754 template <typename Index, typename BeginKey, typename EndKey>
jpayne@69 755 size_t Table<Row, Indexes...>::eraseRange(BeginKey&& begin, EndKey&& end) {
jpayne@69 756 return eraseRange<indexOfType<Index, Tuple<Indexes...>>()>(
jpayne@69 757 kj::fwd<BeginKey>(begin), kj::fwd<EndKey>(end));
jpayne@69 758 }
jpayne@69 759 template <typename Row, typename... Indexes>
jpayne@69 760 template <size_t index, typename BeginKey, typename EndKey>
jpayne@69 761 size_t Table<Row, Indexes...>::eraseRange(BeginKey&& begin, EndKey&& end) {
jpayne@69 762 auto inner = _::iterRange(get<index>(indexes).seek(rows.asPtr(), kj::fwd<BeginKey>(begin)),
jpayne@69 763 get<index>(indexes).seek(rows.asPtr(), kj::fwd<EndKey>(end)));
jpayne@69 764 return eraseAllImpl(inner);
jpayne@69 765 }
jpayne@69 766
jpayne@69 767 template <typename Row, typename... Indexes>
jpayne@69 768 template <size_t index>
jpayne@69 769 void Table<Row, Indexes...>::verify() {
jpayne@69 770 get<index>(indexes).verify(rows.asPtr());
jpayne@69 771 }
jpayne@69 772
jpayne@69 773 template <typename Row, typename... Indexes>
jpayne@69 774 void Table<Row, Indexes...>::erase(Row& row) {
jpayne@69 775 KJ_TABLE_IREQUIRE(&row >= rows.begin() && &row < rows.end(), "row is not a member of this table");
jpayne@69 776 eraseImpl(&row - rows.begin());
jpayne@69 777 }
jpayne@69 778 template <typename Row, typename... Indexes>
jpayne@69 779 void Table<Row, Indexes...>::eraseImpl(size_t pos) {
jpayne@69 780 Impl<>::erase(*this, pos, rows[pos]);
jpayne@69 781 size_t back = rows.size() - 1;
jpayne@69 782 if (pos != back) {
jpayne@69 783 Impl<>::move(*this, back, pos, rows[back]);
jpayne@69 784 rows[pos] = kj::mv(rows[back]);
jpayne@69 785 }
jpayne@69 786 rows.removeLast();
jpayne@69 787 }
jpayne@69 788
jpayne@69 789 template <typename Row, typename... Indexes>
jpayne@69 790 Row Table<Row, Indexes...>::release(Row& row) {
jpayne@69 791 KJ_TABLE_IREQUIRE(&row >= rows.begin() && &row < rows.end(), "row is not a member of this table");
jpayne@69 792 size_t pos = &row - rows.begin();
jpayne@69 793 Impl<>::erase(*this, pos, row);
jpayne@69 794 Row result = kj::mv(row);
jpayne@69 795 size_t back = rows.size() - 1;
jpayne@69 796 if (pos != back) {
jpayne@69 797 Impl<>::move(*this, back, pos, rows[back]);
jpayne@69 798 row = kj::mv(rows[back]);
jpayne@69 799 }
jpayne@69 800 rows.removeLast();
jpayne@69 801 return result;
jpayne@69 802 }
jpayne@69 803
jpayne@69 804 template <typename Row, typename... Indexes>
jpayne@69 805 template <typename Predicate, typename>
jpayne@69 806 size_t Table<Row, Indexes...>::eraseAll(Predicate&& predicate) {
jpayne@69 807 size_t count = 0;
jpayne@69 808 for (size_t i = 0; i < rows.size();) {
jpayne@69 809 if (predicate(rows[i])) {
jpayne@69 810 eraseImpl(i);
jpayne@69 811 ++count;
jpayne@69 812 // eraseImpl() replaces the erased row with the last row, so don't increment i here; repeat
jpayne@69 813 // with the same i.
jpayne@69 814 } else {
jpayne@69 815 ++i;
jpayne@69 816 }
jpayne@69 817 }
jpayne@69 818 return count;
jpayne@69 819 }
jpayne@69 820
jpayne@69 821 template <typename Row, typename... Indexes>
jpayne@69 822 template <typename Collection, typename, bool>
jpayne@69 823 size_t Table<Row, Indexes...>::eraseAll(Collection&& collection) {
jpayne@69 824 return eraseAllImpl(MappedIterable<Collection&, _::TableUnmapping<Row>>(
jpayne@69 825 collection, rows.begin()));
jpayne@69 826 }
jpayne@69 827
jpayne@69 828 template <typename Row, typename... Indexes>
jpayne@69 829 template <typename Collection>
jpayne@69 830 size_t Table<Row, Indexes...>::eraseAllImpl(Collection&& collection) {
jpayne@69 831 // We need to transform the collection of row numbers into a sequence of erasures, accounting
jpayne@69 832 // for the fact that each erasure re-positions the last row into its slot.
jpayne@69 833 Vector<size_t> erased;
jpayne@69 834 _::tryReserveSize(erased, collection);
jpayne@69 835 for (size_t pos: collection) {
jpayne@69 836 while (pos >= rows.size() - erased.size()) {
jpayne@69 837 // Oops, the next item to be erased is already scheduled to be moved to a different location
jpayne@69 838 // due to a previous erasure. Figure out where it will be at this point.
jpayne@69 839 size_t erasureNumber = rows.size() - pos - 1;
jpayne@69 840 pos = erased[erasureNumber];
jpayne@69 841 }
jpayne@69 842 erased.add(pos);
jpayne@69 843 }
jpayne@69 844
jpayne@69 845 // Now we can execute the sequence of erasures.
jpayne@69 846 for (size_t pos: erased) {
jpayne@69 847 eraseImpl(pos);
jpayne@69 848 }
jpayne@69 849
jpayne@69 850 return erased.size();
jpayne@69 851 }
jpayne@69 852
jpayne@69 853 // -----------------------------------------------------------------------------
jpayne@69 854 // Hash table index
jpayne@69 855
jpayne@69 856 namespace _ { // private
jpayne@69 857
jpayne@69 858 void logHashTableInconsistency();
jpayne@69 859
jpayne@69 860 struct HashBucket {
jpayne@69 861 uint hash;
jpayne@69 862 uint value;
jpayne@69 863
jpayne@69 864 HashBucket() = default;
jpayne@69 865 HashBucket(uint hash, uint pos)
jpayne@69 866 : hash(hash), value(pos + 2) {}
jpayne@69 867
jpayne@69 868 inline bool isEmpty() const { return value == 0; }
jpayne@69 869 inline bool isErased() const { return value == 1; }
jpayne@69 870 inline bool isOccupied() const { return value >= 2; }
jpayne@69 871 template <typename Row>
jpayne@69 872 inline Row& getRow(ArrayPtr<Row> table) const { return table[getPos()]; }
jpayne@69 873 template <typename Row>
jpayne@69 874 inline const Row& getRow(ArrayPtr<const Row> table) const { return table[getPos()]; }
jpayne@69 875 inline bool isPos(uint pos) const { return pos + 2 == value; }
jpayne@69 876 inline uint getPos() const {
jpayne@69 877 KJ_TABLE_IASSERT(value >= 2);
jpayne@69 878 return value - 2;
jpayne@69 879 }
jpayne@69 880 inline void setEmpty() { value = 0; }
jpayne@69 881 inline void setErased() { value = 1; }
jpayne@69 882 inline void setPos(uint pos) { value = pos + 2; }
jpayne@69 883 };
jpayne@69 884
jpayne@69 885 inline size_t probeHash(const kj::Array<HashBucket>& buckets, size_t i) {
jpayne@69 886 // TODO(perf): Is linear probing OK or should we do something fancier?
jpayne@69 887 if (++i == buckets.size()) {
jpayne@69 888 return 0;
jpayne@69 889 } else {
jpayne@69 890 return i;
jpayne@69 891 }
jpayne@69 892 }
jpayne@69 893
jpayne@69 894 kj::Array<HashBucket> rehash(kj::ArrayPtr<const HashBucket> oldBuckets, size_t targetSize);
jpayne@69 895
jpayne@69 896 uint chooseBucket(uint hash, uint count);
jpayne@69 897
jpayne@69 898 } // namespace _ (private)
jpayne@69 899
jpayne@69 900 template <typename Callbacks>
jpayne@69 901 class HashIndex {
jpayne@69 902 public:
jpayne@69 903 HashIndex() = default;
jpayne@69 904 template <typename... Params>
jpayne@69 905 HashIndex(Params&&... params): cb(kj::fwd<Params>(params)...) {}
jpayne@69 906
jpayne@69 907 size_t capacity() {
jpayne@69 908 // This method is for testing.
jpayne@69 909 return buckets.size();
jpayne@69 910 }
jpayne@69 911
jpayne@69 912 void reserve(size_t size) {
jpayne@69 913 if (buckets.size() < size * 2) {
jpayne@69 914 rehash(size);
jpayne@69 915 }
jpayne@69 916 }
jpayne@69 917
jpayne@69 918 void clear() {
jpayne@69 919 erasedCount = 0;
jpayne@69 920 if (buckets.size() > 0) memset(buckets.begin(), 0, buckets.asBytes().size());
jpayne@69 921 }
jpayne@69 922
jpayne@69 923 template <typename Row>
jpayne@69 924 decltype(auto) keyForRow(Row&& row) const {
jpayne@69 925 return cb.keyForRow(kj::fwd<Row>(row));
jpayne@69 926 }
jpayne@69 927
jpayne@69 928 template <typename Row, typename... Params>
jpayne@69 929 kj::Maybe<size_t> insert(kj::ArrayPtr<Row> table, size_t pos, Params&&... params) {
jpayne@69 930 if (buckets.size() * 2 < (table.size() + 1 + erasedCount) * 3) {
jpayne@69 931 // Load factor is more than 2/3, let's rehash so that it's 1/3, i.e. double the buckets.
jpayne@69 932 // Note that rehashing also cleans up erased entries, so we may not actually be doubling if
jpayne@69 933 // there are a lot of erasures. Nevertheless, this gives us amortized constant time -- it
jpayne@69 934 // would take at least O(table.size()) more insertions (whether or not erasures occur)
jpayne@69 935 // before another rehash is needed.
jpayne@69 936 rehash((table.size() + 1) * 3);
jpayne@69 937 }
jpayne@69 938
jpayne@69 939 uint hashCode = cb.hashCode(params...);
jpayne@69 940 Maybe<_::HashBucket&> erasedSlot;
jpayne@69 941 for (uint i = _::chooseBucket(hashCode, buckets.size());; i = _::probeHash(buckets, i)) {
jpayne@69 942 auto& bucket = buckets[i];
jpayne@69 943 if (bucket.isEmpty()) {
jpayne@69 944 // no duplicates found
jpayne@69 945 KJ_IF_MAYBE(s, erasedSlot) {
jpayne@69 946 --erasedCount;
jpayne@69 947 *s = { hashCode, uint(pos) };
jpayne@69 948 } else {
jpayne@69 949 bucket = { hashCode, uint(pos) };
jpayne@69 950 }
jpayne@69 951 return nullptr;
jpayne@69 952 } else if (bucket.isErased()) {
jpayne@69 953 // We can fill in the erased slot. However, we have to keep searching to make sure there
jpayne@69 954 // are no duplicates before we do that.
jpayne@69 955 if (erasedSlot == nullptr) {
jpayne@69 956 erasedSlot = bucket;
jpayne@69 957 }
jpayne@69 958 } else if (bucket.hash == hashCode &&
jpayne@69 959 cb.matches(bucket.getRow(table), params...)) {
jpayne@69 960 // duplicate row
jpayne@69 961 return size_t(bucket.getPos());
jpayne@69 962 }
jpayne@69 963 }
jpayne@69 964 }
jpayne@69 965
jpayne@69 966 template <typename Row, typename... Params>
jpayne@69 967 void erase(kj::ArrayPtr<Row> table, size_t pos, Params&&... params) {
jpayne@69 968 uint hashCode = cb.hashCode(params...);
jpayne@69 969 for (uint i = _::chooseBucket(hashCode, buckets.size());; i = _::probeHash(buckets, i)) {
jpayne@69 970 auto& bucket = buckets[i];
jpayne@69 971 if (bucket.isPos(pos)) {
jpayne@69 972 // found it
jpayne@69 973 ++erasedCount;
jpayne@69 974 bucket.setErased();
jpayne@69 975 return;
jpayne@69 976 } else if (bucket.isEmpty()) {
jpayne@69 977 // can't find the bucket, something is very wrong
jpayne@69 978 _::logHashTableInconsistency();
jpayne@69 979 return;
jpayne@69 980 }
jpayne@69 981 }
jpayne@69 982 }
jpayne@69 983
jpayne@69 984 template <typename Row, typename... Params>
jpayne@69 985 void move(kj::ArrayPtr<Row> table, size_t oldPos, size_t newPos, Params&&... params) {
jpayne@69 986 uint hashCode = cb.hashCode(params...);
jpayne@69 987 for (uint i = _::chooseBucket(hashCode, buckets.size());; i = _::probeHash(buckets, i)) {
jpayne@69 988 auto& bucket = buckets[i];
jpayne@69 989 if (bucket.isPos(oldPos)) {
jpayne@69 990 // found it
jpayne@69 991 bucket.setPos(newPos);
jpayne@69 992 return;
jpayne@69 993 } else if (bucket.isEmpty()) {
jpayne@69 994 // can't find the bucket, something is very wrong
jpayne@69 995 _::logHashTableInconsistency();
jpayne@69 996 return;
jpayne@69 997 }
jpayne@69 998 }
jpayne@69 999 }
jpayne@69 1000
jpayne@69 1001 template <typename Row, typename... Params>
jpayne@69 1002 Maybe<size_t> find(kj::ArrayPtr<Row> table, Params&&... params) const {
jpayne@69 1003 if (buckets.size() == 0) return nullptr;
jpayne@69 1004
jpayne@69 1005 uint hashCode = cb.hashCode(params...);
jpayne@69 1006 for (uint i = _::chooseBucket(hashCode, buckets.size());; i = _::probeHash(buckets, i)) {
jpayne@69 1007 auto& bucket = buckets[i];
jpayne@69 1008 if (bucket.isEmpty()) {
jpayne@69 1009 // not found.
jpayne@69 1010 return nullptr;
jpayne@69 1011 } else if (bucket.isErased()) {
jpayne@69 1012 // skip, keep searching
jpayne@69 1013 } else if (bucket.hash == hashCode &&
jpayne@69 1014 cb.matches(bucket.getRow(table), params...)) {
jpayne@69 1015 // found
jpayne@69 1016 return size_t(bucket.getPos());
jpayne@69 1017 }
jpayne@69 1018 }
jpayne@69 1019 }
jpayne@69 1020
jpayne@69 1021 // No begin() nor end() because hash tables are not usefully ordered.
jpayne@69 1022
jpayne@69 1023 private:
jpayne@69 1024 Callbacks cb;
jpayne@69 1025 size_t erasedCount = 0;
jpayne@69 1026 Array<_::HashBucket> buckets;
jpayne@69 1027
jpayne@69 1028 void rehash(size_t targetSize) {
jpayne@69 1029 buckets = _::rehash(buckets, targetSize);
jpayne@69 1030 erasedCount = 0;
jpayne@69 1031 }
jpayne@69 1032 };
jpayne@69 1033
jpayne@69 1034 // -----------------------------------------------------------------------------
jpayne@69 1035 // BTree index
jpayne@69 1036
jpayne@69 1037 namespace _ { // private
jpayne@69 1038
jpayne@69 1039 KJ_ALWAYS_INLINE(void compilerBarrier());
jpayne@69 1040 void compilerBarrier() {
jpayne@69 1041 // Make sure that reads occurring before this call cannot be re-ordered to happen after
jpayne@69 1042 // writes that occur after this call. We need this in a couple places below to prevent C++
jpayne@69 1043 // strict aliasing rules from breaking things.
jpayne@69 1044 #if _MSC_VER
jpayne@69 1045 _ReadWriteBarrier();
jpayne@69 1046 #else
jpayne@69 1047 __asm__ __volatile__("": : :"memory");
jpayne@69 1048 #endif
jpayne@69 1049 }
jpayne@69 1050
jpayne@69 1051 template <typename T>
jpayne@69 1052 inline void acopy(T* to, T* from, size_t size) { memcpy(to, from, size * sizeof(T)); }
jpayne@69 1053 template <typename T>
jpayne@69 1054 inline void amove(T* to, T* from, size_t size) { memmove(to, from, size * sizeof(T)); }
jpayne@69 1055 template <typename T>
jpayne@69 1056 inline void azero(T* ptr, size_t size) { memset(ptr, 0, size * sizeof(T)); }
jpayne@69 1057 // memcpy/memmove/memset variants that count size in elements, not bytes.
jpayne@69 1058 //
jpayne@69 1059 // TODO(cleanup): These are generally useful, put them somewhere.
jpayne@69 1060
jpayne@69 1061 class BTreeImpl {
jpayne@69 1062 public:
jpayne@69 1063 class Iterator;
jpayne@69 1064 class MaybeUint;
jpayne@69 1065 struct NodeUnion;
jpayne@69 1066 struct Leaf;
jpayne@69 1067 struct Parent;
jpayne@69 1068 struct Freelisted;
jpayne@69 1069
jpayne@69 1070 class SearchKey {
jpayne@69 1071 // Passed to methods that need to search the tree. This class allows most of the B-tree
jpayne@69 1072 // implementation to be kept out of templates, avoiding code bloat, at the cost of some
jpayne@69 1073 // performance trade-off. In order to lessen the performance cost of virtual calls, we design
jpayne@69 1074 // this interface so that it only needs to be called once per tree node, rather than once per
jpayne@69 1075 // comparison.
jpayne@69 1076
jpayne@69 1077 public:
jpayne@69 1078 virtual uint search(const Parent& parent) const = 0;
jpayne@69 1079 virtual uint search(const Leaf& leaf) const = 0;
jpayne@69 1080 // Binary search for the first key/row in the parent/leaf that is equal to or comes after the
jpayne@69 1081 // search key.
jpayne@69 1082
jpayne@69 1083 virtual bool isAfter(uint rowIndex) const = 0;
jpayne@69 1084 // Returns true if the key comes after the value in the given row.
jpayne@69 1085 };
jpayne@69 1086
jpayne@69 1087 BTreeImpl();
jpayne@69 1088 ~BTreeImpl() noexcept(false);
jpayne@69 1089
jpayne@69 1090 KJ_DISALLOW_COPY(BTreeImpl);
jpayne@69 1091 BTreeImpl(BTreeImpl&& other);
jpayne@69 1092 BTreeImpl& operator=(BTreeImpl&& other);
jpayne@69 1093
jpayne@69 1094 void logInconsistency() const;
jpayne@69 1095
jpayne@69 1096 void reserve(size_t size);
jpayne@69 1097
jpayne@69 1098 void clear();
jpayne@69 1099
jpayne@69 1100 Iterator begin() const;
jpayne@69 1101 Iterator end() const;
jpayne@69 1102
jpayne@69 1103 Iterator search(const SearchKey& searchKey) const;
jpayne@69 1104 // Find the "first" row (in sorted order) for which searchKey.isAfter(rowNumber) returns true.
jpayne@69 1105
jpayne@69 1106 Iterator insert(const SearchKey& searchKey);
jpayne@69 1107 // Like search() but ensures that there is room in the leaf node to insert a new row.
jpayne@69 1108
jpayne@69 1109 void erase(uint row, const SearchKey& searchKey);
jpayne@69 1110 // Erase the given row number from the tree. searchKey.isAfter() returns true for the given row
jpayne@69 1111 // and all rows after it.
jpayne@69 1112
jpayne@69 1113 void renumber(uint oldRow, uint newRow, const SearchKey& searchKey);
jpayne@69 1114 // Renumber the given row from oldRow to newRow. searchKey.isAfter() returns true for oldRow and
jpayne@69 1115 // all rows after it. (It will not be called on newRow.)
jpayne@69 1116
jpayne@69 1117 void verify(size_t size, FunctionParam<bool(uint, uint)>);
jpayne@69 1118
jpayne@69 1119 private:
jpayne@69 1120 NodeUnion* tree; // allocated with aligned_alloc aligned to cache lines
jpayne@69 1121 uint treeCapacity;
jpayne@69 1122 uint height; // height of *parent* tree -- does not include the leaf level
jpayne@69 1123 uint freelistHead;
jpayne@69 1124 uint freelistSize;
jpayne@69 1125 uint beginLeaf;
jpayne@69 1126 uint endLeaf;
jpayne@69 1127 void growTree(uint minCapacity = 0);
jpayne@69 1128
jpayne@69 1129 template <typename T>
jpayne@69 1130 struct AllocResult;
jpayne@69 1131
jpayne@69 1132 template <typename T>
jpayne@69 1133 inline AllocResult<T> alloc();
jpayne@69 1134 inline void free(uint pos);
jpayne@69 1135
jpayne@69 1136 inline uint split(Parent& src, uint srcPos, Parent& dst, uint dstPos);
jpayne@69 1137 inline uint split(Leaf& dst, uint dstPos, Leaf& src, uint srcPos);
jpayne@69 1138 inline void merge(Parent& dst, uint dstPos, uint pivot, Parent& src);
jpayne@69 1139 inline void merge(Leaf& dst, uint dstPos, uint pivot, Leaf& src);
jpayne@69 1140 inline void move(Parent& dst, uint dstPos, Parent& src);
jpayne@69 1141 inline void move(Leaf& dst, uint dstPos, Leaf& src);
jpayne@69 1142 inline void rotateLeft(
jpayne@69 1143 Parent& left, Parent& right, Parent& parent, uint indexInParent, MaybeUint*& fixup);
jpayne@69 1144 inline void rotateLeft(
jpayne@69 1145 Leaf& left, Leaf& right, Parent& parent, uint indexInParent, MaybeUint*& fixup);
jpayne@69 1146 inline void rotateRight(Parent& left, Parent& right, Parent& parent, uint indexInParent);
jpayne@69 1147 inline void rotateRight(Leaf& left, Leaf& right, Parent& parent, uint indexInParent);
jpayne@69 1148
jpayne@69 1149 template <typename Node>
jpayne@69 1150 inline Node& insertHelper(const SearchKey& searchKey,
jpayne@69 1151 Node& node, Parent* parent, uint indexInParent, uint pos);
jpayne@69 1152
jpayne@69 1153 template <typename Node>
jpayne@69 1154 inline Node& eraseHelper(
jpayne@69 1155 Node& node, Parent* parent, uint indexInParent, uint pos, MaybeUint*& fixup);
jpayne@69 1156
jpayne@69 1157 size_t verifyNode(size_t size, FunctionParam<bool(uint, uint)>&,
jpayne@69 1158 uint pos, uint height, MaybeUint maxRow);
jpayne@69 1159
jpayne@69 1160 static const NodeUnion EMPTY_NODE;
jpayne@69 1161 };
jpayne@69 1162
jpayne@69 1163 class BTreeImpl::MaybeUint {
jpayne@69 1164 // A nullable uint, using the value zero to mean null and shifting all other values up by 1.
jpayne@69 1165 public:
jpayne@69 1166 MaybeUint() = default;
jpayne@69 1167 inline MaybeUint(uint i): i(i - 1) {}
jpayne@69 1168 inline MaybeUint(decltype(nullptr)): i(0) {}
jpayne@69 1169
jpayne@69 1170 inline bool operator==(decltype(nullptr)) const { return i == 0; }
jpayne@69 1171 inline bool operator==(uint j) const { return i == j + 1; }
jpayne@69 1172 inline bool operator==(const MaybeUint& other) const { return i == other.i; }
jpayne@69 1173 inline bool operator!=(decltype(nullptr)) const { return i != 0; }
jpayne@69 1174 inline bool operator!=(uint j) const { return i != j + 1; }
jpayne@69 1175 inline bool operator!=(const MaybeUint& other) const { return i != other.i; }
jpayne@69 1176
jpayne@69 1177 inline MaybeUint& operator=(decltype(nullptr)) { i = 0; return *this; }
jpayne@69 1178 inline MaybeUint& operator=(uint j) { i = j + 1; return *this; }
jpayne@69 1179
jpayne@69 1180 inline uint operator*() const { KJ_TABLE_IREQUIRE(i != 0); return i - 1; }
jpayne@69 1181
jpayne@69 1182 template <typename Func>
jpayne@69 1183 inline bool check(Func& func) const { return i != 0 && func(i - 1); }
jpayne@69 1184 // Equivalent to *this != nullptr && func(**this)
jpayne@69 1185
jpayne@69 1186 kj::String toString() const;
jpayne@69 1187
jpayne@69 1188 private:
jpayne@69 1189 uint i;
jpayne@69 1190 };
jpayne@69 1191
jpayne@69 1192 struct BTreeImpl::Leaf {
jpayne@69 1193 uint next;
jpayne@69 1194 uint prev;
jpayne@69 1195 // Pointers to next and previous nodes at the same level, used for fast iteration.
jpayne@69 1196
jpayne@69 1197 static constexpr size_t NROWS = 14;
jpayne@69 1198 MaybeUint rows[NROWS];
jpayne@69 1199 // Pointers to table rows, offset by 1 so that 0 is an empty value.
jpayne@69 1200
jpayne@69 1201 inline bool isFull() const;
jpayne@69 1202 inline bool isMostlyFull() const;
jpayne@69 1203 inline bool isHalfFull() const;
jpayne@69 1204
jpayne@69 1205 inline void insert(uint i, uint newRow) {
jpayne@69 1206 KJ_TABLE_IREQUIRE(rows[Leaf::NROWS - 1] == nullptr); // check not full
jpayne@69 1207
jpayne@69 1208 amove(rows + i + 1, rows + i, Leaf::NROWS - (i + 1));
jpayne@69 1209 rows[i] = newRow;
jpayne@69 1210 }
jpayne@69 1211
jpayne@69 1212 inline void erase(uint i) {
jpayne@69 1213 KJ_TABLE_IREQUIRE(rows[0] != nullptr); // check not empty
jpayne@69 1214
jpayne@69 1215 amove(rows + i, rows + i + 1, Leaf::NROWS - (i + 1));
jpayne@69 1216 rows[Leaf::NROWS - 1] = nullptr;
jpayne@69 1217 }
jpayne@69 1218
jpayne@69 1219 inline uint size() const {
jpayne@69 1220 static_assert(Leaf::NROWS == 14, "logic here needs updating");
jpayne@69 1221
jpayne@69 1222 // Binary search for first empty element in `rows`, or return 14 if no empty elements. We do
jpayne@69 1223 // this in a branch-free manner. Since there are 15 possible results (0 through 14, inclusive),
jpayne@69 1224 // this isn't a perfectly balanced binary search. We carefully choose the split points so that
jpayne@69 1225 // there's no way we'll try to dereference row[14] or later (which would be a buffer overflow).
jpayne@69 1226 uint i = (rows[6] != nullptr) * 7;
jpayne@69 1227 i += (rows[i + 3] != nullptr) * 4;
jpayne@69 1228 i += (rows[i + 1] != nullptr) * 2;
jpayne@69 1229 i += (rows[i ] != nullptr);
jpayne@69 1230 return i;
jpayne@69 1231 }
jpayne@69 1232
jpayne@69 1233 template <typename Func>
jpayne@69 1234 inline uint binarySearch(Func& predicate) const {
jpayne@69 1235 // Binary search to find first row for which predicate(row) is false.
jpayne@69 1236
jpayne@69 1237 static_assert(Leaf::NROWS == 14, "logic here needs updating");
jpayne@69 1238
jpayne@69 1239 // See comments in size().
jpayne@69 1240 uint i = (rows[6].check(predicate)) * 7;
jpayne@69 1241 i += (rows[i + 3].check(predicate)) * 4;
jpayne@69 1242 i += (rows[i + 1].check(predicate)) * 2;
jpayne@69 1243 if (i != 6) { // don't redundantly check row 6
jpayne@69 1244 i += (rows[i ].check(predicate));
jpayne@69 1245 }
jpayne@69 1246 return i;
jpayne@69 1247 }
jpayne@69 1248 };
jpayne@69 1249
jpayne@69 1250 struct BTreeImpl::Parent {
jpayne@69 1251 uint unused;
jpayne@69 1252 // Not used. May be arbitrarily non-zero due to overlap with Freelisted::nextOffset.
jpayne@69 1253
jpayne@69 1254 static constexpr size_t NKEYS = 7;
jpayne@69 1255 MaybeUint keys[NKEYS];
jpayne@69 1256 // Pointers to table rows, offset by 1 so that 0 is an empty value.
jpayne@69 1257 //
jpayne@69 1258 // Each keys[i] specifies the table row which is the "last" row found under children[i].
jpayne@69 1259 //
jpayne@69 1260 // Note that `keys` has size 7 but `children` has size 8. `children[8]`'s "last row" is not
jpayne@69 1261 // recorded here, because the Parent's Parent records it instead. (Or maybe the Parent's Parent's
jpayne@69 1262 // Parent, if this Parent is `children[8]` of its own Parent. And so on.)
jpayne@69 1263
jpayne@69 1264 static constexpr size_t NCHILDREN = NKEYS + 1;
jpayne@69 1265 uint children[NCHILDREN];
jpayne@69 1266 // Pointers to children. Not offset because the root is always at position 0, and a pointer
jpayne@69 1267 // to the root would be nonsensical.
jpayne@69 1268
jpayne@69 1269 inline bool isFull() const;
jpayne@69 1270 inline bool isMostlyFull() const;
jpayne@69 1271 inline bool isHalfFull() const;
jpayne@69 1272 inline void initRoot(uint key, uint leftChild, uint rightChild);
jpayne@69 1273 inline void insertAfter(uint i, uint splitKey, uint child);
jpayne@69 1274 inline void eraseAfter(uint i);
jpayne@69 1275
jpayne@69 1276 inline uint keyCount() const {
jpayne@69 1277 static_assert(Parent::NKEYS == 7, "logic here needs updating");
jpayne@69 1278
jpayne@69 1279 // Binary search for first empty element in `keys`, or return 7 if no empty elements. We do
jpayne@69 1280 // this in a branch-free manner. Since there are 8 possible results (0 through 7, inclusive),
jpayne@69 1281 // this is a perfectly balanced binary search.
jpayne@69 1282 uint i = (keys[3] != nullptr) * 4;
jpayne@69 1283 i += (keys[i + 1] != nullptr) * 2;
jpayne@69 1284 i += (keys[i ] != nullptr);
jpayne@69 1285 return i;
jpayne@69 1286 }
jpayne@69 1287
jpayne@69 1288 template <typename Func>
jpayne@69 1289 inline uint binarySearch(Func& predicate) const {
jpayne@69 1290 // Binary search to find first key for which predicate(key) is false.
jpayne@69 1291
jpayne@69 1292 static_assert(Parent::NKEYS == 7, "logic here needs updating");
jpayne@69 1293
jpayne@69 1294 // See comments in size().
jpayne@69 1295 uint i = (keys[3].check(predicate)) * 4;
jpayne@69 1296 i += (keys[i + 1].check(predicate)) * 2;
jpayne@69 1297 i += (keys[i ].check(predicate));
jpayne@69 1298 return i;
jpayne@69 1299 }
jpayne@69 1300 };
jpayne@69 1301
jpayne@69 1302 struct BTreeImpl::Freelisted {
jpayne@69 1303 int nextOffset;
jpayne@69 1304 // The next node in the freelist is at: this + 1 + nextOffset
jpayne@69 1305 //
jpayne@69 1306 // Hence, newly-allocated space can initialize this to zero.
jpayne@69 1307
jpayne@69 1308 uint zero[15];
jpayne@69 1309 // Freelisted entries are always zero'd.
jpayne@69 1310 };
jpayne@69 1311
jpayne@69 1312 struct BTreeImpl::NodeUnion {
jpayne@69 1313 union {
jpayne@69 1314 Freelisted freelist;
jpayne@69 1315 // If this node is in the freelist.
jpayne@69 1316
jpayne@69 1317 Leaf leaf;
jpayne@69 1318 // If this node is a leaf.
jpayne@69 1319
jpayne@69 1320 Parent parent;
jpayne@69 1321 // If this node is not a leaf.
jpayne@69 1322 };
jpayne@69 1323
jpayne@69 1324 inline operator Leaf&() { return leaf; }
jpayne@69 1325 inline operator Parent&() { return parent; }
jpayne@69 1326 inline operator const Leaf&() const { return leaf; }
jpayne@69 1327 inline operator const Parent&() const { return parent; }
jpayne@69 1328 };
jpayne@69 1329
jpayne@69 1330 static_assert(sizeof(BTreeImpl::Parent) == 64,
jpayne@69 1331 "BTreeImpl::Parent should be optimized to fit a cache line");
jpayne@69 1332 static_assert(sizeof(BTreeImpl::Leaf) == 64,
jpayne@69 1333 "BTreeImpl::Leaf should be optimized to fit a cache line");
jpayne@69 1334 static_assert(sizeof(BTreeImpl::Freelisted) == 64,
jpayne@69 1335 "BTreeImpl::Freelisted should be optimized to fit a cache line");
jpayne@69 1336 static_assert(sizeof(BTreeImpl::NodeUnion) == 64,
jpayne@69 1337 "BTreeImpl::NodeUnion should be optimized to fit a cache line");
jpayne@69 1338
jpayne@69 1339 bool BTreeImpl::Leaf::isFull() const {
jpayne@69 1340 return rows[Leaf::NROWS - 1] != nullptr;
jpayne@69 1341 }
jpayne@69 1342 bool BTreeImpl::Leaf::isMostlyFull() const {
jpayne@69 1343 return rows[Leaf::NROWS / 2] != nullptr;
jpayne@69 1344 }
jpayne@69 1345 bool BTreeImpl::Leaf::isHalfFull() const {
jpayne@69 1346 KJ_TABLE_IASSERT(rows[Leaf::NROWS / 2 - 1] != nullptr);
jpayne@69 1347 return rows[Leaf::NROWS / 2] == nullptr;
jpayne@69 1348 }
jpayne@69 1349
jpayne@69 1350 bool BTreeImpl::Parent::isFull() const {
jpayne@69 1351 return keys[Parent::NKEYS - 1] != nullptr;
jpayne@69 1352 }
jpayne@69 1353 bool BTreeImpl::Parent::isMostlyFull() const {
jpayne@69 1354 return keys[Parent::NKEYS / 2] != nullptr;
jpayne@69 1355 }
jpayne@69 1356 bool BTreeImpl::Parent::isHalfFull() const {
jpayne@69 1357 KJ_TABLE_IASSERT(keys[Parent::NKEYS / 2 - 1] != nullptr);
jpayne@69 1358 return keys[Parent::NKEYS / 2] == nullptr;
jpayne@69 1359 }
jpayne@69 1360
jpayne@69 1361 class BTreeImpl::Iterator {
jpayne@69 1362 public:
jpayne@69 1363 Iterator(const NodeUnion* tree, const Leaf* leaf, uint row)
jpayne@69 1364 : tree(tree), leaf(leaf), row(row) {}
jpayne@69 1365
jpayne@69 1366 size_t operator*() const {
jpayne@69 1367 KJ_TABLE_IREQUIRE(row < Leaf::NROWS && leaf->rows[row] != nullptr,
jpayne@69 1368 "tried to dereference end() iterator");
jpayne@69 1369 return *leaf->rows[row];
jpayne@69 1370 }
jpayne@69 1371
jpayne@69 1372 inline Iterator& operator++() {
jpayne@69 1373 KJ_TABLE_IREQUIRE(leaf->rows[row] != nullptr, "B-tree iterator overflow");
jpayne@69 1374 ++row;
jpayne@69 1375 if (row >= Leaf::NROWS || leaf->rows[row] == nullptr) {
jpayne@69 1376 if (leaf->next == 0) {
jpayne@69 1377 // at end; stay on current leaf
jpayne@69 1378 } else {
jpayne@69 1379 leaf = &tree[leaf->next].leaf;
jpayne@69 1380 row = 0;
jpayne@69 1381 }
jpayne@69 1382 }
jpayne@69 1383 return *this;
jpayne@69 1384 }
jpayne@69 1385 inline Iterator operator++(int) {
jpayne@69 1386 Iterator other = *this;
jpayne@69 1387 ++*this;
jpayne@69 1388 return other;
jpayne@69 1389 }
jpayne@69 1390
jpayne@69 1391 inline Iterator& operator--() {
jpayne@69 1392 if (row == 0) {
jpayne@69 1393 KJ_TABLE_IREQUIRE(leaf->prev != 0, "B-tree iterator underflow");
jpayne@69 1394 leaf = &tree[leaf->prev].leaf;
jpayne@69 1395 row = leaf->size() - 1;
jpayne@69 1396 } else {
jpayne@69 1397 --row;
jpayne@69 1398 }
jpayne@69 1399 return *this;
jpayne@69 1400 }
jpayne@69 1401 inline Iterator operator--(int) {
jpayne@69 1402 Iterator other = *this;
jpayne@69 1403 --*this;
jpayne@69 1404 return other;
jpayne@69 1405 }
jpayne@69 1406
jpayne@69 1407 inline bool operator==(const Iterator& other) const {
jpayne@69 1408 return leaf == other.leaf && row == other.row;
jpayne@69 1409 }
jpayne@69 1410 inline bool operator!=(const Iterator& other) const {
jpayne@69 1411 return leaf != other.leaf || row != other.row;
jpayne@69 1412 }
jpayne@69 1413
jpayne@69 1414 bool isEnd() {
jpayne@69 1415 return row == Leaf::NROWS || leaf->rows[row] == nullptr;
jpayne@69 1416 }
jpayne@69 1417
jpayne@69 1418 void insert(BTreeImpl& impl, uint newRow) {
jpayne@69 1419 KJ_TABLE_IASSERT(impl.tree == tree);
jpayne@69 1420 const_cast<Leaf*>(leaf)->insert(row, newRow);
jpayne@69 1421 }
jpayne@69 1422
jpayne@69 1423 void erase(BTreeImpl& impl) {
jpayne@69 1424 KJ_TABLE_IASSERT(impl.tree == tree);
jpayne@69 1425 const_cast<Leaf*>(leaf)->erase(row);
jpayne@69 1426 }
jpayne@69 1427
jpayne@69 1428 void replace(BTreeImpl& impl, uint newRow) {
jpayne@69 1429 KJ_TABLE_IASSERT(impl.tree == tree);
jpayne@69 1430 const_cast<Leaf*>(leaf)->rows[row] = newRow;
jpayne@69 1431 }
jpayne@69 1432
jpayne@69 1433 private:
jpayne@69 1434 const NodeUnion* tree;
jpayne@69 1435 const Leaf* leaf;
jpayne@69 1436 uint row;
jpayne@69 1437 };
jpayne@69 1438
jpayne@69 1439 inline BTreeImpl::Iterator BTreeImpl::begin() const {
jpayne@69 1440 return { tree, &tree[beginLeaf].leaf, 0 };
jpayne@69 1441 }
jpayne@69 1442 inline BTreeImpl::Iterator BTreeImpl::end() const {
jpayne@69 1443 auto& leaf = tree[endLeaf].leaf;
jpayne@69 1444 return { tree, &leaf, leaf.size() };
jpayne@69 1445 }
jpayne@69 1446
jpayne@69 1447 } // namespace _ (private)
jpayne@69 1448
jpayne@69 1449 template <typename Callbacks>
jpayne@69 1450 class TreeIndex {
jpayne@69 1451 public:
jpayne@69 1452 TreeIndex() = default;
jpayne@69 1453 template <typename... Params>
jpayne@69 1454 TreeIndex(Params&&... params): cb(kj::fwd<Params>(params)...) {}
jpayne@69 1455
jpayne@69 1456 template <typename Row>
jpayne@69 1457 void verify(kj::ArrayPtr<Row> table) {
jpayne@69 1458 impl.verify(table.size(), [&](uint i, uint j) {
jpayne@69 1459 return cb.isBefore(table[i], table[j]);
jpayne@69 1460 });
jpayne@69 1461 }
jpayne@69 1462
jpayne@69 1463 inline void reserve(size_t size) { impl.reserve(size); }
jpayne@69 1464 inline void clear() { impl.clear(); }
jpayne@69 1465 inline auto begin() const { return impl.begin(); }
jpayne@69 1466 inline auto end() const { return impl.end(); }
jpayne@69 1467
jpayne@69 1468 template <typename Row>
jpayne@69 1469 decltype(auto) keyForRow(Row&& row) const {
jpayne@69 1470 return cb.keyForRow(kj::fwd<Row>(row));
jpayne@69 1471 }
jpayne@69 1472
jpayne@69 1473 template <typename Row, typename... Params>
jpayne@69 1474 kj::Maybe<size_t> insert(kj::ArrayPtr<Row> table, size_t pos, Params&&... params) {
jpayne@69 1475 auto iter = impl.insert(searchKey(table, params...));
jpayne@69 1476
jpayne@69 1477 if (!iter.isEnd() && cb.matches(table[*iter], params...)) {
jpayne@69 1478 return *iter;
jpayne@69 1479 } else {
jpayne@69 1480 iter.insert(impl, pos);
jpayne@69 1481 return nullptr;
jpayne@69 1482 }
jpayne@69 1483 }
jpayne@69 1484
jpayne@69 1485 template <typename Row, typename... Params>
jpayne@69 1486 void erase(kj::ArrayPtr<Row> table, size_t pos, Params&&... params) {
jpayne@69 1487 impl.erase(pos, searchKeyForErase(table, pos, params...));
jpayne@69 1488 }
jpayne@69 1489
jpayne@69 1490 template <typename Row, typename... Params>
jpayne@69 1491 void move(kj::ArrayPtr<Row> table, size_t oldPos, size_t newPos, Params&&... params) {
jpayne@69 1492 impl.renumber(oldPos, newPos, searchKey(table, params...));
jpayne@69 1493 }
jpayne@69 1494
jpayne@69 1495 template <typename Row, typename... Params>
jpayne@69 1496 Maybe<size_t> find(kj::ArrayPtr<Row> table, Params&&... params) const {
jpayne@69 1497 auto iter = impl.search(searchKey(table, params...));
jpayne@69 1498
jpayne@69 1499 if (!iter.isEnd() && cb.matches(table[*iter], params...)) {
jpayne@69 1500 return size_t(*iter);
jpayne@69 1501 } else {
jpayne@69 1502 return nullptr;
jpayne@69 1503 }
jpayne@69 1504 }
jpayne@69 1505
jpayne@69 1506 template <typename Row, typename... Params>
jpayne@69 1507 _::BTreeImpl::Iterator seek(kj::ArrayPtr<Row> table, Params&&... params) const {
jpayne@69 1508 return impl.search(searchKey(table, params...));
jpayne@69 1509 }
jpayne@69 1510
jpayne@69 1511 private:
jpayne@69 1512 Callbacks cb;
jpayne@69 1513 _::BTreeImpl impl;
jpayne@69 1514
jpayne@69 1515 template <typename Predicate>
jpayne@69 1516 class SearchKeyImpl: public _::BTreeImpl::SearchKey {
jpayne@69 1517 public:
jpayne@69 1518 SearchKeyImpl(Predicate&& predicate)
jpayne@69 1519 : predicate(kj::mv(predicate)) {}
jpayne@69 1520
jpayne@69 1521 uint search(const _::BTreeImpl::Parent& parent) const override {
jpayne@69 1522 return parent.binarySearch(predicate);
jpayne@69 1523 }
jpayne@69 1524 uint search(const _::BTreeImpl::Leaf& leaf) const override {
jpayne@69 1525 return leaf.binarySearch(predicate);
jpayne@69 1526 }
jpayne@69 1527 bool isAfter(uint rowIndex) const override {
jpayne@69 1528 return predicate(rowIndex);
jpayne@69 1529 }
jpayne@69 1530
jpayne@69 1531 private:
jpayne@69 1532 Predicate predicate;
jpayne@69 1533 };
jpayne@69 1534
jpayne@69 1535 template <typename Row, typename... Params>
jpayne@69 1536 inline auto searchKey(kj::ArrayPtr<Row>& table, Params&... params) const {
jpayne@69 1537 auto predicate = [&](uint i) { return cb.isBefore(table[i], params...); };
jpayne@69 1538 return SearchKeyImpl<decltype(predicate)>(kj::mv(predicate));
jpayne@69 1539 }
jpayne@69 1540
jpayne@69 1541 template <typename Row, typename... Params>
jpayne@69 1542 inline auto searchKeyForErase(kj::ArrayPtr<Row>& table, uint pos, Params&... params) const {
jpayne@69 1543 // When erasing, the table entry for the erased row may already be invalid, so we must avoid
jpayne@69 1544 // accessing it.
jpayne@69 1545 auto predicate = [&,pos](uint i) {
jpayne@69 1546 return i != pos && cb.isBefore(table[i], params...);
jpayne@69 1547 };
jpayne@69 1548 return SearchKeyImpl<decltype(predicate)>(kj::mv(predicate));
jpayne@69 1549 }
jpayne@69 1550 };
jpayne@69 1551
jpayne@69 1552 // -----------------------------------------------------------------------------
jpayne@69 1553 // Insertion order index
jpayne@69 1554
jpayne@69 1555 class InsertionOrderIndex {
jpayne@69 1556 // Table index which allows iterating over elements in order of insertion. This index cannot
jpayne@69 1557 // be used for Table::find(), but can be used for Table::ordered().
jpayne@69 1558
jpayne@69 1559 struct Link;
jpayne@69 1560 public:
jpayne@69 1561 InsertionOrderIndex();
jpayne@69 1562 InsertionOrderIndex(const InsertionOrderIndex&) = delete;
jpayne@69 1563 InsertionOrderIndex& operator=(const InsertionOrderIndex&) = delete;
jpayne@69 1564 InsertionOrderIndex(InsertionOrderIndex&& other);
jpayne@69 1565 InsertionOrderIndex& operator=(InsertionOrderIndex&& other);
jpayne@69 1566 ~InsertionOrderIndex() noexcept(false);
jpayne@69 1567
jpayne@69 1568 class Iterator {
jpayne@69 1569 public:
jpayne@69 1570 Iterator(const Link* links, uint pos)
jpayne@69 1571 : links(links), pos(pos) {}
jpayne@69 1572
jpayne@69 1573 inline size_t operator*() const {
jpayne@69 1574 KJ_TABLE_IREQUIRE(pos != 0, "can't dereference end() iterator");
jpayne@69 1575 return pos - 1;
jpayne@69 1576 };
jpayne@69 1577
jpayne@69 1578 inline Iterator& operator++() {
jpayne@69 1579 pos = links[pos].next;
jpayne@69 1580 return *this;
jpayne@69 1581 }
jpayne@69 1582 inline Iterator operator++(int) {
jpayne@69 1583 Iterator result = *this;
jpayne@69 1584 ++*this;
jpayne@69 1585 return result;
jpayne@69 1586 }
jpayne@69 1587 inline Iterator& operator--() {
jpayne@69 1588 pos = links[pos].prev;
jpayne@69 1589 return *this;
jpayne@69 1590 }
jpayne@69 1591 inline Iterator operator--(int) {
jpayne@69 1592 Iterator result = *this;
jpayne@69 1593 --*this;
jpayne@69 1594 return result;
jpayne@69 1595 }
jpayne@69 1596
jpayne@69 1597 inline bool operator==(const Iterator& other) const {
jpayne@69 1598 return pos == other.pos;
jpayne@69 1599 }
jpayne@69 1600 inline bool operator!=(const Iterator& other) const {
jpayne@69 1601 return pos != other.pos;
jpayne@69 1602 }
jpayne@69 1603
jpayne@69 1604 private:
jpayne@69 1605 const Link* links;
jpayne@69 1606 uint pos;
jpayne@69 1607 };
jpayne@69 1608
jpayne@69 1609 template <typename Row>
jpayne@69 1610 Row& keyForRow(Row& row) const { return row; }
jpayne@69 1611
jpayne@69 1612 void reserve(size_t size);
jpayne@69 1613 void clear();
jpayne@69 1614 inline Iterator begin() const { return Iterator(links, links[0].next); }
jpayne@69 1615 inline Iterator end() const { return Iterator(links, 0); }
jpayne@69 1616
jpayne@69 1617 template <typename Row>
jpayne@69 1618 kj::Maybe<size_t> insert(kj::ArrayPtr<Row> table, size_t pos, const Row& row) {
jpayne@69 1619 return insertImpl(pos);
jpayne@69 1620 }
jpayne@69 1621
jpayne@69 1622 template <typename Row>
jpayne@69 1623 void erase(kj::ArrayPtr<Row> table, size_t pos, const Row& row) {
jpayne@69 1624 eraseImpl(pos);
jpayne@69 1625 }
jpayne@69 1626
jpayne@69 1627 template <typename Row>
jpayne@69 1628 void move(kj::ArrayPtr<Row> table, size_t oldPos, size_t newPos, const Row& row) {
jpayne@69 1629 return moveImpl(oldPos, newPos);
jpayne@69 1630 }
jpayne@69 1631
jpayne@69 1632 private:
jpayne@69 1633 struct Link {
jpayne@69 1634 uint next;
jpayne@69 1635 uint prev;
jpayne@69 1636 };
jpayne@69 1637
jpayne@69 1638 uint capacity;
jpayne@69 1639 Link* links;
jpayne@69 1640 // links[0] is special: links[0].next points to the first link, links[0].prev points to the last.
jpayne@69 1641 // links[n+1] corresponds to row n.
jpayne@69 1642
jpayne@69 1643 kj::Maybe<size_t> insertImpl(size_t pos);
jpayne@69 1644 void eraseImpl(size_t pos);
jpayne@69 1645 void moveImpl(size_t oldPos, size_t newPos);
jpayne@69 1646
jpayne@69 1647 static const Link EMPTY_LINK;
jpayne@69 1648 };
jpayne@69 1649
jpayne@69 1650 } // namespace kj
jpayne@69 1651
jpayne@69 1652 KJ_END_HEADER