233 lines
6.4 KiB
Solidity

// SPDX-License-Identifier: MIT
// heavily inspired by: https://bitbucket.org/rhitchens2/soliditystoragepatterns/src/master/GeneralizedCollection.sol
pragma solidity ^0.8.8;
import "./Debug.sol"; // DELETE ME
library Mappings {
type KeyId is bytes32;
type ValueId is bytes32;
// first entity is called a "One"
struct Key {
// needed to delete a "One"
uint256 _oneListPointer;
// One has many "Many"
ValueId[] _valueIds;
mapping(ValueId => uint256) _valueIdsIndex; // valueId => row of local _valueIds
// more app data
}
// other entity is called a "Many"
struct Value {
// needed to delete a "Many"
uint256 _valueIdsIndex;
// many has exactly one "One"
KeyId _keyId;
// add app fields
}
struct Mapping {
mapping(KeyId => Key) _keys;
KeyId[] _keyIds;
mapping(ValueId => Value) _values;
ValueId[] _valueIds;
}
function keyCount(Mapping storage db)
internal
view
returns(uint256)
{
return db._keyIds.length;
}
function getManyCount(Mapping storage db) internal view returns(uint256) {
return db._valueIds.length;
}
function getManyCount(Mapping storage db, KeyId keyId)
internal
view
returns(uint256 manyCount)
{
require(keyExists(db, keyId), "key does not exist");
return _getValueIds(db, keyId).length;
}
function keyExists(Mapping storage db, KeyId keyId)
internal
view
returns(bool)
{
if(keyCount(db) == 0) return false;
return equals(db._keyIds[db._keys[keyId]._oneListPointer], keyId);
}
function valueExists(Mapping storage db, ValueId valueId)
internal
view
returns(bool)
{
if(getManyCount(db) == 0) return false;
uint256 row = db._values[valueId]._valueIdsIndex;
bool retVal = equals(db._valueIds[row], valueId);
return retVal;
}
function _getValueIds(Mapping storage db,
KeyId keyId)
internal
view
returns(ValueId[] storage)
{
require(keyExists(db, keyId), "key does not exist");
return db._keys[keyId]._valueIds;
}
function getValueIds(Mapping storage db,
KeyId keyId)
internal
view
returns(ValueId[] storage)
{
require(keyExists(db, keyId), "key does not exist");
return _getValueIds(db, keyId);
}
// Insert
function insertKey(Mapping storage db, KeyId keyId)
internal
returns(bool)
{
require(!keyExists(db, keyId), "key already exists"); // duplicate key prohibited
db._keyIds.push(keyId);
db._keys[keyId]._oneListPointer = keyCount(db) - 1;
return true;
}
function insertValue(Mapping storage db, KeyId keyId, ValueId valueId)
internal
returns(bool)
{
require(keyExists(db, keyId), "key does not exist");
require(!valueExists(db, valueId), "value already exists"); // duplicate key prohibited
Value storage value = db._values[valueId];
db._valueIds.push(valueId);
value._valueIdsIndex = getManyCount(db) - 1;
value._keyId = keyId; // each many has exactly one "One", so this is mandatory
// We also maintain a list of "Many" that refer to the "One", so ...
Key storage key = db._keys[keyId];
key._valueIds.push(valueId);
key._valueIdsIndex[valueId] = key._valueIds.length - 1;
return true;
}
function insert(Mapping storage db, KeyId keyId, ValueId valueId)
internal
returns(bool success)
{
if (!keyExists(db, keyId)) {
success = insertKey(db, keyId);
if (!success) {
return false;
}
}
if (!valueExists(db, valueId)) {
success = insertValue(db, keyId, valueId);
}
return success;
}
// Delete
function deleteKey(Mapping storage db, KeyId keyId)
internal
returns(bool)
{
require(keyExists(db, keyId), "key does not exist");
require(_getValueIds(db, keyId).length == 0, "references manys"); // this would break referential integrity
uint256 rowToDelete = db._keys[keyId]._oneListPointer;
KeyId keyToMove = db._keyIds[keyCount(db)-1];
db._keyIds[rowToDelete] = keyToMove;
db._keys[keyToMove]._oneListPointer = rowToDelete;
db._keyIds.pop();
delete db._keys[keyId];
return true;
}
function deleteValue(Mapping storage db, ValueId valueId)
internal
returns(bool)
{
require(valueExists(db, valueId), "value does not exist"); // non-existant key
// delete from the Many table
uint256 toDeleteIndex = db._values[valueId]._valueIdsIndex;
uint256 lastIndex = getManyCount(db) - 1;
if (lastIndex != toDeleteIndex) {
ValueId lastValue = db._valueIds[lastIndex];
// Move the last value to the index where the value to delete is
db._valueIds[toDeleteIndex] = lastValue;
// Update the index for the moved value
db._values[lastValue]._valueIdsIndex = toDeleteIndex; // Replace lastvalue's index to valueIndex
}
db._valueIds.pop();
KeyId keyId = db._values[valueId]._keyId;
Key storage oneRow = db._keys[keyId];
toDeleteIndex = oneRow._valueIdsIndex[valueId];
lastIndex = oneRow._valueIds.length - 1;
if (lastIndex != toDeleteIndex) {
ValueId lastValue = oneRow._valueIds[lastIndex];
// Move the last value to the index where the value to delete is
oneRow._valueIds[toDeleteIndex] = lastValue;
// Update the index for the moved value
oneRow._valueIdsIndex[lastValue] = toDeleteIndex; // Replace lastvalue's index to valueIndex
}
oneRow._valueIds.pop();
delete oneRow._valueIdsIndex[valueId];
delete db._values[valueId];
return true;
}
function clearValues(Mapping storage db, KeyId keyId)
internal
returns(bool)
{
require(keyExists(db, keyId), "key does not exist"); // non-existant key
Debug._printTable(db, "[clearValues] BEFORE clearing");
// delete db._valueIds;
delete db._keys[keyId]._valueIds;
bool result = deleteKey(db, keyId);
Debug._printTable(db, "[clearValues] AFTER clearing");
return result;
}
function equals(KeyId a, KeyId b) internal pure returns (bool) {
return KeyId.unwrap(a) == KeyId.unwrap(b);
}
function equals(ValueId a, ValueId b) internal pure returns (bool) {
return ValueId.unwrap(a) == ValueId.unwrap(b);
}
function toKeyId(address addr) internal pure returns (KeyId) {
return KeyId.wrap(bytes32(uint(uint160(addr))));
}
// Useful in the case where a valueId is a foreign key
function toKeyId(ValueId valueId) internal pure returns (KeyId) {
return KeyId.wrap(ValueId.unwrap(valueId));
}
}