Commit 9b20a942 authored by Kenton Varda's avatar Kenton Varda

Support findOrCreate() (with only a single lookup).

This is a very common pattern in practice -- and annoyingly difficult with STL maps.

This required some refactoring so than index.insert() could be called before the row was actually constructed, based on the search parameters.

It also required some awful hacks to support putting the creation function at the end of the argument list to findOrCreate(), with a variable-width arg list before it.
parent 25a25704
...@@ -43,8 +43,17 @@ KJ_TEST("HashMap") { ...@@ -43,8 +43,17 @@ KJ_TEST("HashMap") {
KJ_EXPECT(KJ_ASSERT_NONNULL(map.find("foo"_kj)) == 321); KJ_EXPECT(KJ_ASSERT_NONNULL(map.find("foo"_kj)) == 321);
KJ_EXPECT(
map.findOrCreate("foo"_kj,
[]() -> HashMap<String, int>::Entry { KJ_FAIL_ASSERT("shouldn't have been called"); })
== 321);
KJ_EXPECT(map.findOrCreate("baz"_kj,
[](){ return HashMap<String, int>::Entry { kj::str("baz"), 654 }; }) == 654);
KJ_EXPECT(KJ_ASSERT_NONNULL(map.find("baz"_kj)) == 654);
KJ_EXPECT(map.erase("bar"_kj)); KJ_EXPECT(map.erase("bar"_kj));
KJ_EXPECT(!map.erase("baz"_kj)); KJ_EXPECT(map.erase("baz"_kj));
KJ_EXPECT(!map.erase("qux"_kj));
KJ_EXPECT(KJ_ASSERT_NONNULL(map.find("foo"_kj)) == 321); KJ_EXPECT(KJ_ASSERT_NONNULL(map.find("foo"_kj)) == 321);
KJ_EXPECT(map.size() == 1); KJ_EXPECT(map.size() == 1);
...@@ -75,8 +84,17 @@ KJ_TEST("TreeMap") { ...@@ -75,8 +84,17 @@ KJ_TEST("TreeMap") {
KJ_EXPECT(KJ_ASSERT_NONNULL(map.find("foo"_kj)) == 321); KJ_EXPECT(KJ_ASSERT_NONNULL(map.find("foo"_kj)) == 321);
KJ_EXPECT(
map.findOrCreate("foo"_kj,
[]() -> TreeMap<String, int>::Entry { KJ_FAIL_ASSERT("shouldn't have been called"); })
== 321);
KJ_EXPECT(map.findOrCreate("baz"_kj,
[](){ return TreeMap<String, int>::Entry { kj::str("baz"), 654 }; }) == 654);
KJ_EXPECT(KJ_ASSERT_NONNULL(map.find("baz"_kj)) == 654);
KJ_EXPECT(map.erase("bar"_kj)); KJ_EXPECT(map.erase("bar"_kj));
KJ_EXPECT(!map.erase("baz"_kj)); KJ_EXPECT(map.erase("baz"_kj));
KJ_EXPECT(!map.erase("qux"_kj));
KJ_EXPECT(KJ_ASSERT_NONNULL(map.find("foo"_kj)) == 321); KJ_EXPECT(KJ_ASSERT_NONNULL(map.find("foo"_kj)) == 321);
KJ_EXPECT(map.size() == 1); KJ_EXPECT(map.size() == 1);
......
...@@ -82,6 +82,11 @@ public: ...@@ -82,6 +82,11 @@ public:
// //
// Note that the default hasher for String accepts StringPtr. // Note that the default hasher for String accepts StringPtr.
template <typename KeyLike, typename Func>
Value& findOrCreate(KeyLike&& key, Func&& createEntry);
// Like find() but if the key isn't present then call createEntry() to create the corresponding
// entry and insert it. createEntry() must return type `Entry`.
template <typename KeyLike> template <typename KeyLike>
bool erase(KeyLike&& key); bool erase(KeyLike&& key);
// Erase the entry with the matching key. // Erase the entry with the matching key.
...@@ -172,9 +177,12 @@ public: ...@@ -172,9 +177,12 @@ public:
template <typename KeyLike> template <typename KeyLike>
kj::Maybe<const Value&> find(KeyLike&& key) const; kj::Maybe<const Value&> find(KeyLike&& key) const;
// Search for a matching key. The input does not have to be of type `Key`; it merely has to // Search for a matching key. The input does not have to be of type `Key`; it merely has to
// be something that the Hasher accepts. // be something that can be compared against `Key`.
//
// Note that the default hasher for String accepts StringPtr. template <typename KeyLike, typename Func>
Value& findOrCreate(KeyLike&& key, Func&& createEntry);
// Like find() but if the key isn't present then call createEntry() to create the corresponding
// entry and insert it. createEntry() must return type `Entry`.
template <typename K1, typename K2> template <typename K1, typename K2>
auto range(K1&& k1, K2&& k2); auto range(K1&& k1, K2&& k2);
...@@ -342,6 +350,12 @@ kj::Maybe<const Value&> HashMap<Key, Value>::find(KeyLike&& key) const { ...@@ -342,6 +350,12 @@ kj::Maybe<const Value&> HashMap<Key, Value>::find(KeyLike&& key) const {
return table.find(key).map([](const Entry& e) -> const Value& { return e.value; }); return table.find(key).map([](const Entry& e) -> const Value& { return e.value; });
} }
template <typename Key, typename Value>
template <typename KeyLike, typename Func>
Value& HashMap<Key, Value>::findOrCreate(KeyLike&& key, Func&& createEntry) {
return table.findOrCreate(key, kj::fwd<Func>(createEntry)).value;
}
template <typename Key, typename Value> template <typename Key, typename Value>
template <typename KeyLike> template <typename KeyLike>
bool HashMap<Key, Value>::erase(KeyLike&& key) { bool HashMap<Key, Value>::erase(KeyLike&& key) {
...@@ -428,6 +442,12 @@ kj::Maybe<const Value&> TreeMap<Key, Value>::find(KeyLike&& key) const { ...@@ -428,6 +442,12 @@ kj::Maybe<const Value&> TreeMap<Key, Value>::find(KeyLike&& key) const {
return table.find(key).map([](const Entry& e) -> const Value& { return e.value; }); return table.find(key).map([](const Entry& e) -> const Value& { return e.value; });
} }
template <typename Key, typename Value>
template <typename KeyLike, typename Func>
Value& TreeMap<Key, Value>::findOrCreate(KeyLike&& key, Func&& createEntry) {
return table.findOrCreate(key, kj::fwd<Func>(createEntry)).value;
}
template <typename Key, typename Value> template <typename Key, typename Value>
template <typename K1, typename K2> template <typename K1, typename K2>
auto TreeMap<Key, Value>::range(K1&& k1, K2&& k2) { auto TreeMap<Key, Value>::range(K1&& k1, K2&& k2) {
......
...@@ -148,6 +148,37 @@ KJ_TEST("simple table") { ...@@ -148,6 +148,37 @@ KJ_TEST("simple table") {
KJ_EXPECT(*iter++ == "corge"); KJ_EXPECT(*iter++ == "corge");
KJ_EXPECT(iter == table.end()); KJ_EXPECT(iter == table.end());
} }
auto& graultRow = table.begin()[1];
kj::StringPtr origGrault = graultRow;
KJ_EXPECT(&table.findOrCreate("grault",
[&]() -> kj::StringPtr { KJ_FAIL_ASSERT("shouldn't have called this"); }) == &graultRow);
KJ_EXPECT(graultRow.begin() == origGrault.begin());
KJ_EXPECT(&KJ_ASSERT_NONNULL(table.find("grault")) == &graultRow);
KJ_EXPECT(table.find("waldo") == nullptr);
KJ_EXPECT(table.size() == 4);
kj::String searchWaldo = kj::str("waldo");
kj::String insertWaldo = kj::str("waldo");
auto& waldo = table.findOrCreate(searchWaldo,
[&]() -> kj::StringPtr { return insertWaldo; });
KJ_EXPECT(waldo == "waldo");
KJ_EXPECT(waldo.begin() == insertWaldo.begin());
KJ_EXPECT(KJ_ASSERT_NONNULL(table.find("grault")) == "grault");
KJ_EXPECT(&KJ_ASSERT_NONNULL(table.find("waldo")) == &waldo);
KJ_EXPECT(table.size() == 5);
{
auto iter = table.begin();
KJ_EXPECT(*iter++ == "garply");
KJ_EXPECT(*iter++ == "grault");
KJ_EXPECT(*iter++ == "qux");
KJ_EXPECT(*iter++ == "corge");
KJ_EXPECT(*iter++ == "waldo");
KJ_EXPECT(iter == table.end());
}
} }
class BadHasher { class BadHasher {
...@@ -300,6 +331,47 @@ KJ_TEST("double-index table") { ...@@ -300,6 +331,47 @@ KJ_TEST("double-index table") {
KJ_EXPECT(table.size() == 2); KJ_EXPECT(table.size() == 2);
KJ_EXPECT(KJ_ASSERT_NONNULL(table.find<0>("foo")) == (SiPair {"foo", 123})); KJ_EXPECT(KJ_ASSERT_NONNULL(table.find<0>("foo")) == (SiPair {"foo", 123}));
KJ_EXPECT(KJ_ASSERT_NONNULL(table.find<1>(123)) == (SiPair {"foo", 123})); KJ_EXPECT(KJ_ASSERT_NONNULL(table.find<1>(123)) == (SiPair {"foo", 123}));
KJ_EXPECT(
table.findOrCreate<0>("foo",
[]() -> SiPair { KJ_FAIL_ASSERT("shouldn't have called this"); })
== (SiPair {"foo", 123}));
KJ_EXPECT(table.size() == 2);
KJ_EXPECT_THROW_MESSAGE("inserted row already exists in table",
table.findOrCreate<0>("corge", []() -> SiPair { return {"corge", 123}; }));
KJ_EXPECT(table.size() == 2);
KJ_EXPECT(KJ_ASSERT_NONNULL(table.find<0>("foo")) == (SiPair {"foo", 123}));
KJ_EXPECT(KJ_ASSERT_NONNULL(table.find<1>(123)) == (SiPair {"foo", 123}));
KJ_EXPECT(KJ_ASSERT_NONNULL(table.find<0>("bar")) == (SiPair {"bar", 456}));
KJ_EXPECT(KJ_ASSERT_NONNULL(table.find<1>(456)) == (SiPair {"bar", 456}));
KJ_EXPECT(table.find<0>("corge") == nullptr);
KJ_EXPECT(
table.findOrCreate<0>("corge", []() -> SiPair { return {"corge", 789}; })
== (SiPair {"corge", 789}));
KJ_EXPECT(table.size() == 3);
KJ_EXPECT(KJ_ASSERT_NONNULL(table.find<0>("foo")) == (SiPair {"foo", 123}));
KJ_EXPECT(KJ_ASSERT_NONNULL(table.find<1>(123)) == (SiPair {"foo", 123}));
KJ_EXPECT(KJ_ASSERT_NONNULL(table.find<0>("bar")) == (SiPair {"bar", 456}));
KJ_EXPECT(KJ_ASSERT_NONNULL(table.find<1>(456)) == (SiPair {"bar", 456}));
KJ_EXPECT(KJ_ASSERT_NONNULL(table.find<0>("corge")) == (SiPair {"corge", 789}));
KJ_EXPECT(KJ_ASSERT_NONNULL(table.find<1>(789)) == (SiPair {"corge", 789}));
KJ_EXPECT(
table.findOrCreate<1>(234, []() -> SiPair { return {"grault", 234}; })
== (SiPair {"grault", 234}));
KJ_EXPECT(table.size() == 4);
KJ_EXPECT(KJ_ASSERT_NONNULL(table.find<0>("foo")) == (SiPair {"foo", 123}));
KJ_EXPECT(KJ_ASSERT_NONNULL(table.find<1>(123)) == (SiPair {"foo", 123}));
KJ_EXPECT(KJ_ASSERT_NONNULL(table.find<0>("bar")) == (SiPair {"bar", 456}));
KJ_EXPECT(KJ_ASSERT_NONNULL(table.find<1>(456)) == (SiPair {"bar", 456}));
KJ_EXPECT(KJ_ASSERT_NONNULL(table.find<0>("corge")) == (SiPair {"corge", 789}));
KJ_EXPECT(KJ_ASSERT_NONNULL(table.find<1>(789)) == (SiPair {"corge", 789}));
KJ_EXPECT(KJ_ASSERT_NONNULL(table.find<0>("grault")) == (SiPair {"grault", 234}));
KJ_EXPECT(KJ_ASSERT_NONNULL(table.find<1>(234)) == (SiPair {"grault", 234}));
} }
class UintHasher { class UintHasher {
...@@ -667,6 +739,37 @@ KJ_TEST("simple tree table") { ...@@ -667,6 +739,37 @@ KJ_TEST("simple tree table") {
KJ_EXPECT(*iter++ == "garply"); KJ_EXPECT(*iter++ == "garply");
KJ_EXPECT(iter == range.end()); KJ_EXPECT(iter == range.end());
} }
auto& graultRow = table.begin()[1];
kj::StringPtr origGrault = graultRow;
KJ_EXPECT(&table.findOrCreate("grault",
[&]() -> kj::StringPtr { KJ_FAIL_ASSERT("shouldn't have called this"); }) == &graultRow);
KJ_EXPECT(graultRow.begin() == origGrault.begin());
KJ_EXPECT(&KJ_ASSERT_NONNULL(table.find("grault")) == &graultRow);
KJ_EXPECT(table.find("waldo") == nullptr);
KJ_EXPECT(table.size() == 4);
kj::String searchWaldo = kj::str("waldo");
kj::String insertWaldo = kj::str("waldo");
auto& waldo = table.findOrCreate(searchWaldo,
[&]() -> kj::StringPtr { return insertWaldo; });
KJ_EXPECT(waldo == "waldo");
KJ_EXPECT(waldo.begin() == insertWaldo.begin());
KJ_EXPECT(KJ_ASSERT_NONNULL(table.find("grault")) == "grault");
KJ_EXPECT(&KJ_ASSERT_NONNULL(table.find("waldo")) == &waldo);
KJ_EXPECT(table.size() == 5);
{
auto iter = table.begin();
KJ_EXPECT(*iter++ == "garply");
KJ_EXPECT(*iter++ == "grault");
KJ_EXPECT(*iter++ == "qux");
KJ_EXPECT(*iter++ == "corge");
KJ_EXPECT(*iter++ == "waldo");
KJ_EXPECT(iter == table.end());
}
} }
class UintCompare { class UintCompare {
......
...@@ -73,36 +73,56 @@ class Table { ...@@ -73,36 +73,56 @@ class Table {
// template <typename Key> // template <typename Key>
// class Index { // class Index {
// public: // public:
// kj::Maybe<size_t> insert(kj::ArrayPtr<const Row> table, size_t pos);
// // Called to indicate that table[pos] is a newly-added value that needs to be indexed.
// // If this index disallows duplicates and some other matching row already exists, then
// // insert() returns the index of that row -- in this case, the table will roll back the
// // insertion.
// //
// // Insert may throw an exception, in which case the table will roll back insertion.
//
// void reserve(size_t size); // void reserve(size_t size);
// // Called when Table::reserve() is called. // // Called when Table::reserve() is called.
// //
// void erase(kj::ArrayPtr<const Row> table, size_t pos); // // In all function calls below, `SearchPrams` refers to whatever parameters the index
// // Called to indicate that table[pos] is about to be removed, so should be de-indexed. // // supports for looking up a row in the table. At the very least, the table row type
// // itself must be supported. However, most indexes will also want to support some sort
// // of "key" type that is not a whole row.
//
// template <typename... SearchParams>
// kj::Maybe<size_t> insert(kj::ArrayPtr<const Row> table, size_t pos, SearchParams&&...);
// // Called to indicate that we're about to insert a new row which will match the given
// // search parameters, and will be located at the given position. If this index disallows
// // duplicates and some other matching row already exists, then insert() returns the index
// // of that row without modifying the index. If the row does not exist, then insert()
// // updates the index to note that the new row is located at `pos`. Note that `table[pos]`
// // may not be valid yet at the time of this call; the index must go on the search params
// // alone.
// // // //
// // erase() called immediately after insert() must not throw an exception, as it may be // // Insert may throw an exception, in which case the table will roll back insertion.
// // called during unwind. //
// template <typename... SearchParams>
// void erase(kj::ArrayPtr<const Row> table, size_t pos, SearchParams&&...);
// // Called to indicate that the index must remove references to row number `pos`. The
// // index must not attempt to access table[pos] directly -- in fact, `pos` may be equal to
// // `table.size()`, i.e., may be out-of-bounds (this happens when rolling back a failed
// // insertion). Instead, the index can use the search params to search for the row -- they
// // will either be the same as the params passed to insert(), or will be a single value of
// // type `Row&`.
// //
// // erase() called immediately after a successful insert() must not throw an exception, as
// // it may be called during unwind.
// //
// void move(kj::ArrayPtr<const Row> table, size_t oldPos, size_t newPos); // template <typename... SearchParams>
// // Called when the value at table[oldPos] is about to be moved to table[newPos]. // void move(kj::ArrayPtr<const Row> table, size_t oldPos, size_t newPos, SearchParams&&...);
// // Called when a row is about to be moved from `oldPos` to `newPos` in the table. The
// // index should update it to the new location. Neither `table[oldPos]` nor `table[newPos]`
// // is valid during the call -- use the search params to find the row. Before this call
// // `oldPos` is indexed and `newPos` is not -- after the call, the opposite is true.
// //
// // This should never throw; if it does the table may be corrupted. // // This should never throw; if it does the table may be corrupted.
// //
// class Iterator; // Behaves like a C++ iterator over size_t values. // class Iterator; // Behaves like a C++ iterator over size_t values.
// class Iterable; // Has begin() and end() methods returning iterators. // class Iterable; // Has begin() and end() methods returning iterators.
// //
// template <typename... Params> // template <typename... SearchParams>
// Maybe<size_t> find(kj::ArrayPtr<const Row> table, Params&&...) const; // Maybe<size_t> find(kj::ArrayPtr<const Row> table, SearchParams&&...) const;
// // Optional. Implements Table::find<Index>(...). // // Optional. Implements Table::find<Index>(...).
// //
// template <typename... Params> // template <typename... SearchParams>
// Iterable range(kj::ArrayPtr<const Row> table, Params&&...) const; // Iterable range(kj::ArrayPtr<const Row> table, SearchParams&&...) const;
// // Optional. Implements Table::range<Index>(...). // // Optional. Implements Table::range<Index>(...).
// //
// Iterator begin() const; // Iterator begin() const;
...@@ -155,6 +175,15 @@ public: ...@@ -155,6 +175,15 @@ public:
// Using the given index, search for a matching row. What parameters are accepted depends on the // Using the given index, search for a matching row. What parameters are accepted depends on the
// index. Not all indexes support this method -- "multimap" indexes may support only range(). // index. Not all indexes support this method -- "multimap" indexes may support only range().
template <typename Index, typename... Params, typename Func>
Row& findOrCreate(Params&&... params, Func&& createFunc);
// Like find(), but if the row doesn't exist, call a function to create it. createFunc() must
// return `Row` or something that implicitly converts to `Row`.
//
// NOTE: C++ doesn't actually properly suppoprt inferring types of a parameter pack at the
// beginning of an argument list, but we define a hack to support it below. Don't worry about
// it.
template <typename Index, typename... Params> template <typename Index, typename... Params>
auto range(Params&&... params); auto range(Params&&... params);
template <typename Index, typename... Params> template <typename Index, typename... Params>
...@@ -199,6 +228,8 @@ public: ...@@ -199,6 +228,8 @@ public:
kj::Maybe<Row&> find(Params&&... params); kj::Maybe<Row&> find(Params&&... params);
template <size_t index = 0, typename... Params> template <size_t index = 0, typename... Params>
kj::Maybe<const Row&> find(Params&&... params) const; kj::Maybe<const Row&> find(Params&&... params) const;
template <size_t index = 0, typename... Params, typename Func>
Row& findOrCreate(Params&&... params, Func&& createFunc);
template <size_t index = 0, typename... Params> template <size_t index = 0, typename... Params>
auto range(Params&&... params); auto range(Params&&... params);
template <size_t index = 0, typename... Params> template <size_t index = 0, typename... Params>
...@@ -221,12 +252,25 @@ public: ...@@ -221,12 +252,25 @@ public:
// Checks the integrity of indexes, throwing an exception if there are any problems. This is // Checks the integrity of indexes, throwing an exception if there are any problems. This is
// intended to be called within the unit test for an index. // intended to be called within the unit test for an index.
template <typename Index, typename First, typename... Rest>
Row& findOrCreate(First&& first, Rest&&... rest);
template <size_t index = 0, typename First, typename... Rest>
Row& findOrCreate(First&& first, Rest&&... rest);
// HACK: A parameter pack can only be inferred if it lives at the end of the argument list, so
// the findOrCreate() definitions from earlier won't actually work. These ones will, but we
// have to do some annoying things inside to regroup the arguments.
private: private:
Vector<Row> rows; Vector<Row> rows;
Tuple<Indexes...> indexes; Tuple<Indexes...> indexes;
template <size_t index = 0, bool final = (index >= sizeof...(Indexes))> template <size_t index = 0, bool final = (index >= sizeof...(Indexes))>
class Impl; class Impl;
template <typename Func, typename... Params>
class FindOrCreateImpl;
template <typename ParamsTuple, typename... Params>
struct FindOrCreateHack;
void eraseImpl(size_t pos); void eraseImpl(size_t pos);
template <typename Collection> template <typename Collection>
...@@ -408,26 +452,29 @@ public: ...@@ -408,26 +452,29 @@ public:
Impl<index + 1>::clear(table); Impl<index + 1>::clear(table);
} }
static kj::Maybe<size_t> insert(Table<Row, Indexes...>& table, size_t pos) { static kj::Maybe<size_t> insert(Table<Row, Indexes...>& table, size_t pos, Row& row, uint skip) {
KJ_IF_MAYBE(existing, get<index>(table.indexes).insert(table.rows.asPtr(), pos)) { if (skip == index) {
return Impl<index + 1>::insert(table, pos, row, skip);
}
KJ_IF_MAYBE(existing, get<index>(table.indexes).insert(table.rows.asPtr(), pos, row)) {
return *existing; return *existing;
} }
bool success = false; bool success = false;
KJ_DEFER(if (!success) { get<index>(table.indexes).erase(table.rows.asPtr(), pos); }); KJ_DEFER(if (!success) { get<index>(table.indexes).erase(table.rows.asPtr(), pos, row); });
auto result = Impl<index + 1>::insert(table, pos); auto result = Impl<index + 1>::insert(table, pos, row, skip);
success = result == nullptr; success = result == nullptr;
return result; return result;
} }
static void erase(Table<Row, Indexes...>& table, size_t pos) { static void erase(Table<Row, Indexes...>& table, size_t pos, Row& row) {
get<index>(table.indexes).erase(table.rows.asPtr(), pos); get<index>(table.indexes).erase(table.rows.asPtr(), pos, row);
Impl<index + 1>::erase(table, pos); Impl<index + 1>::erase(table, pos, row);
} }
static void move(Table<Row, Indexes...>& table, size_t oldPos, size_t newPos) { static void move(Table<Row, Indexes...>& table, size_t oldPos, size_t newPos, Row& row) {
get<index>(table.indexes).move(table.rows.asPtr(), oldPos, newPos); get<index>(table.indexes).move(table.rows.asPtr(), oldPos, newPos, row);
Impl<index + 1>::move(table, oldPos, newPos); Impl<index + 1>::move(table, oldPos, newPos, row);
} }
}; };
...@@ -437,9 +484,11 @@ class Table<Row, Indexes...>::Impl<index, true> { ...@@ -437,9 +484,11 @@ class Table<Row, Indexes...>::Impl<index, true> {
public: public:
static void reserve(Table<Row, Indexes...>& table, size_t size) {} static void reserve(Table<Row, Indexes...>& table, size_t size) {}
static void clear(Table<Row, Indexes...>& table) {} static void clear(Table<Row, Indexes...>& table) {}
static kj::Maybe<size_t> insert(Table<Row, Indexes...>& table, size_t pos) { return nullptr; } static kj::Maybe<size_t> insert(Table<Row, Indexes...>& table, size_t pos, Row& row, uint skip) {
static void erase(Table<Row, Indexes...>& table, size_t pos) {} return nullptr;
static void move(Table<Row, Indexes...>& table, size_t oldPos, size_t newPos) {} }
static void erase(Table<Row, Indexes...>& table, size_t pos, Row& row) {}
static void move(Table<Row, Indexes...>& table, size_t oldPos, size_t newPos, Row& row) {}
}; };
template <typename Row, typename... Indexes> template <typename Row, typename... Indexes>
...@@ -488,15 +537,10 @@ const Row* Table<Row, Indexes...>::end() const { ...@@ -488,15 +537,10 @@ const Row* Table<Row, Indexes...>::end() const {
template <typename Row, typename... Indexes> template <typename Row, typename... Indexes>
Row& Table<Row, Indexes...>::insert(Row&& row) { Row& Table<Row, Indexes...>::insert(Row&& row) {
size_t pos = rows.size(); KJ_IF_MAYBE(existing, Impl<>::insert(*this, rows.size(), row, kj::maxValue)) {
Row& rowRef = rows.add(kj::mv(row));
bool success = false;
KJ_DEFER({ if (!success) rows.removeLast(); });
KJ_IF_MAYBE(existing, Impl<>::insert(*this, pos)) {
_::throwDuplicateTableRow(); _::throwDuplicateTableRow();
} else { } else {
success = true; return rows.add(kj::mv(row));
return rowRef;
} }
} }
template <typename Row, typename... Indexes> template <typename Row, typename... Indexes>
...@@ -525,14 +569,11 @@ void Table<Row, Indexes...>::insertAll(Collection& collection) { ...@@ -525,14 +569,11 @@ void Table<Row, Indexes...>::insertAll(Collection& collection) {
template <typename Row, typename... Indexes> template <typename Row, typename... Indexes>
template <typename UpdateFunc> template <typename UpdateFunc>
Row& Table<Row, Indexes...>::upsert(Row&& row, UpdateFunc&& update) { Row& Table<Row, Indexes...>::upsert(Row&& row, UpdateFunc&& update) {
size_t pos = rows.size(); KJ_IF_MAYBE(existing, Impl<>::insert(*this, rows.size(), row, kj::maxValue)) {
Row& rowRef = rows.add(kj::mv(row)); update(rows[*existing], kj::mv(row));
KJ_IF_MAYBE(existing, Impl<>::insert(*this, pos)) {
update(rows[*existing], kj::mv(rowRef));
rows.removeLast();
return rows[*existing]; return rows[*existing];
} else { } else {
return rowRef; return rows.add(kj::mv(row));
} }
} }
template <typename Row, typename... Indexes> template <typename Row, typename... Indexes>
...@@ -570,6 +611,59 @@ kj::Maybe<const Row&> Table<Row, Indexes...>::find(Params&&... params) const { ...@@ -570,6 +611,59 @@ kj::Maybe<const Row&> Table<Row, Indexes...>::find(Params&&... params) const {
} }
} }
template <typename Row, typename... Indexes>
template <typename... Params, typename Func>
class Table<Row, Indexes...>::FindOrCreateImpl<Func, Params...> {
public:
template <size_t index>
static Row& apply(Table<Row, Indexes...>& table, Params&&... params, Func&& createFunc) {
auto pos = table.rows.size();
KJ_IF_MAYBE(existing, get<index>(table.indexes).insert(table.rows.asPtr(), pos, params...)) {
return table.rows[*existing];
} else {
bool success = false;
auto& newRow = table.rows.add(createFunc());
KJ_DEFER({
if (!success) {
table.rows.removeLast();
get<index>(table.indexes).erase(table.rows.asPtr(), pos, params...);
}
});
if (Impl<>::insert(table, pos, newRow, index) == nullptr) {
success = true;
} else {
_::throwDuplicateTableRow();
}
return newRow;
}
}
};
template <typename Row, typename... Indexes>
template <typename... T, typename U, typename V, typename... W>
struct Table<Row, Indexes...>::FindOrCreateHack<_::Tuple<T...>, U, V, W...>
: public FindOrCreateHack<_::Tuple<T..., U>, V, W...> {};
template <typename Row, typename... Indexes>
template <typename... T, typename U>
struct Table<Row, Indexes...>::FindOrCreateHack<_::Tuple<T...>, U>
: public FindOrCreateImpl<U, T...> {};
// This awful hack works around C++'s lack of support for parameter packs anywhere other than at
// the end of an argument list. We accumulate all of the types except for the last one into a
// Tuple, then forward to FindOrCreateImpl with the last parameter as the Func.
template <typename Row, typename... Indexes>
template <typename Index, typename First, typename... Rest>
Row& Table<Row, Indexes...>::findOrCreate(First&& first, Rest&&... rest) {
return findOrCreate<indexOfType<Index, Tuple<Indexes...>>()>(
kj::fwd<First>(first), kj::fwd<Rest>(rest)...);
}
template <typename Row, typename... Indexes>
template <size_t index, typename First, typename... Rest>
Row& Table<Row, Indexes...>::findOrCreate(First&& first, Rest&&... rest) {
return FindOrCreateHack<_::Tuple<>, First, Rest...>::template apply<index>(
*this, kj::fwd<First>(first), kj::fwd<Rest>(rest)...);
}
template <typename Row, typename... Indexes> template <typename Row, typename... Indexes>
template <typename Index, typename... Params> template <typename Index, typename... Params>
auto Table<Row, Indexes...>::range(Params&&... params) { auto Table<Row, Indexes...>::range(Params&&... params) {
...@@ -655,10 +749,10 @@ void Table<Row, Indexes...>::erase(Row& row) { ...@@ -655,10 +749,10 @@ void Table<Row, Indexes...>::erase(Row& row) {
} }
template <typename Row, typename... Indexes> template <typename Row, typename... Indexes>
void Table<Row, Indexes...>::eraseImpl(size_t pos) { void Table<Row, Indexes...>::eraseImpl(size_t pos) {
Impl<>::erase(*this, pos); Impl<>::erase(*this, pos, rows[pos]);
size_t back = rows.size() - 1; size_t back = rows.size() - 1;
if (pos != back) { if (pos != back) {
Impl<>::move(*this, back, pos); Impl<>::move(*this, back, pos, rows[back]);
rows[pos] = kj::mv(rows[back]); rows[pos] = kj::mv(rows[back]);
} }
rows.removeLast(); rows.removeLast();
...@@ -778,14 +872,14 @@ public: ...@@ -778,14 +872,14 @@ public:
memset(buckets.begin(), 0, buckets.asBytes().size()); memset(buckets.begin(), 0, buckets.asBytes().size());
} }
template <typename Row> template <typename Row, typename... Params>
kj::Maybe<size_t> insert(kj::ArrayPtr<Row> table, size_t pos) { kj::Maybe<size_t> insert(kj::ArrayPtr<Row> table, size_t pos, Params&&... params) {
if (buckets.size() * 2 < (table.size() + erasedCount) * 3) { if (buckets.size() * 2 < (table.size() + 1 + erasedCount) * 3) {
// Load factor is more than 2/3, let's rehash. // Load factor is more than 2/3, let's rehash.
rehash(kj::max(buckets.size() * 2, table.size() * 2)); rehash(kj::max(buckets.size() * 2, (table.size() + 1) * 2));
} }
uint hashCode = cb.hashCode(table[pos]); uint hashCode = cb.hashCode(params...);
Maybe<_::HashBucket&> erasedSlot; Maybe<_::HashBucket&> erasedSlot;
for (uint i = _::chooseBucket(hashCode, buckets.size());; i = _::probeHash(buckets, i)) { for (uint i = _::chooseBucket(hashCode, buckets.size());; i = _::probeHash(buckets, i)) {
auto& bucket = buckets[i]; auto& bucket = buckets[i];
...@@ -805,16 +899,16 @@ public: ...@@ -805,16 +899,16 @@ public:
erasedSlot = bucket; erasedSlot = bucket;
} }
} else if (bucket.hash == hashCode && } else if (bucket.hash == hashCode &&
cb.matches(bucket.getRow(table), table[pos])) { cb.matches(bucket.getRow(table), params...)) {
// duplicate row // duplicate row
return size_t(bucket.getPos()); return size_t(bucket.getPos());
} }
} }
} }
template <typename Row> template <typename Row, typename... Params>
void erase(kj::ArrayPtr<Row> table, size_t pos) { void erase(kj::ArrayPtr<Row> table, size_t pos, Params&&... params) {
uint hashCode = cb.hashCode(table[pos]); uint hashCode = cb.hashCode(params...);
for (uint i = _::chooseBucket(hashCode, buckets.size());; i = _::probeHash(buckets, i)) { for (uint i = _::chooseBucket(hashCode, buckets.size());; i = _::probeHash(buckets, i)) {
auto& bucket = buckets[i]; auto& bucket = buckets[i];
if (bucket.isPos(pos)) { if (bucket.isPos(pos)) {
...@@ -830,9 +924,9 @@ public: ...@@ -830,9 +924,9 @@ public:
} }
} }
template <typename Row> template <typename Row, typename... Params>
void move(kj::ArrayPtr<Row> table, size_t oldPos, size_t newPos) { void move(kj::ArrayPtr<Row> table, size_t oldPos, size_t newPos, Params&&... params) {
uint hashCode = cb.hashCode(table[oldPos]); uint hashCode = cb.hashCode(params...);
for (uint i = _::chooseBucket(hashCode, buckets.size());; i = _::probeHash(buckets, i)) { for (uint i = _::chooseBucket(hashCode, buckets.size());; i = _::probeHash(buckets, i)) {
auto& bucket = buckets[i]; auto& bucket = buckets[i];
if (bucket.isPos(oldPos)) { if (bucket.isPos(oldPos)) {
...@@ -1317,12 +1411,11 @@ public: ...@@ -1317,12 +1411,11 @@ public:
inline auto begin() const { return impl.begin(); } inline auto begin() const { return impl.begin(); }
inline auto end() const { return impl.end(); } inline auto end() const { return impl.end(); }
template <typename Row> template <typename Row, typename... Params>
kj::Maybe<size_t> insert(kj::ArrayPtr<Row> table, size_t pos) { kj::Maybe<size_t> insert(kj::ArrayPtr<Row> table, size_t pos, Params&&... params) {
auto& newRow = table[pos]; auto iter = impl.insert(searchKey(table, params...));
auto iter = impl.insert(searchKey(table, newRow));
if (!iter.isEnd() && cb.matches(table[*iter], newRow)) { if (!iter.isEnd() && cb.matches(table[*iter], params...)) {
return *iter; return *iter;
} else { } else {
iter.insert(impl, pos); iter.insert(impl, pos);
...@@ -1330,16 +1423,14 @@ public: ...@@ -1330,16 +1423,14 @@ public:
} }
} }
template <typename Row> template <typename Row, typename... Params>
void erase(kj::ArrayPtr<Row> table, size_t pos) { void erase(kj::ArrayPtr<Row> table, size_t pos, Params&&... params) {
auto& row = table[pos]; impl.erase(pos, searchKey(table, params...));
impl.erase(pos, searchKey(table, row));
} }
template <typename Row> template <typename Row, typename... Params>
void move(kj::ArrayPtr<Row> table, size_t oldPos, size_t newPos) { void move(kj::ArrayPtr<Row> table, size_t oldPos, size_t newPos, Params&&... params) {
auto& row = table[oldPos]; impl.renumber(oldPos, newPos, searchKey(table, params...));
impl.renumber(oldPos, newPos, searchKey(table, row));
} }
template <typename Row, typename... Params> template <typename Row, typename... Params>
...@@ -1452,17 +1543,17 @@ public: ...@@ -1452,17 +1543,17 @@ public:
inline Iterator end() const { return Iterator(links, 0); } inline Iterator end() const { return Iterator(links, 0); }
template <typename Row> template <typename Row>
kj::Maybe<size_t> insert(kj::ArrayPtr<Row> table, size_t pos) { kj::Maybe<size_t> insert(kj::ArrayPtr<Row> table, size_t pos, const Row& row) {
return insertImpl(pos); return insertImpl(pos);
} }
template <typename Row> template <typename Row>
void erase(kj::ArrayPtr<Row> table, size_t pos) { void erase(kj::ArrayPtr<Row> table, size_t pos, const Row& row) {
eraseImpl(pos); eraseImpl(pos);
} }
template <typename Row> template <typename Row>
void move(kj::ArrayPtr<Row> table, size_t oldPos, size_t newPos) { void move(kj::ArrayPtr<Row> table, size_t oldPos, size_t newPos, const Row& row) {
return moveImpl(oldPos, newPos); return moveImpl(oldPos, newPos);
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment