mirror of
https://github.com/logos-storage/logos-storage-contracts-eth.git
synced 2026-01-17 20:53:08 +00:00
233 lines
6.4 KiB
Solidity
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));
|
|
}
|
|
}
|