diff --git a/examples/minimal/packages/contracts/types/ethers-contracts/IWorld.ts b/examples/minimal/packages/contracts/types/ethers-contracts/IWorld.ts index 386ad0b860..c929beb5a8 100644 --- a/examples/minimal/packages/contracts/types/ethers-contracts/IWorld.ts +++ b/examples/minimal/packages/contracts/types/ethers-contracts/IWorld.ts @@ -36,6 +36,18 @@ export type StringStructStruct = { value: PromiseOrValue }; export type StringStructStructOutput = [string] & { value: string }; +export type MoreStructStruct = { + selectionType: PromiseOrValue; + fieldIndex: PromiseOrValue; + value: PromiseOrValue; +}; + +export type MoreStructStructOutput = [number, number, string] & { + selectionType: number; + fieldIndex: number; + value: string; +}; + export interface IWorldInterface extends utils.Interface { functions: { "call(bytes16,bytes16,bytes)": FunctionFragment; @@ -57,6 +69,7 @@ export interface IWorldInterface extends utils.Interface { "installModule(address,bytes)": FunctionFragment; "installRootModule(address,bytes)": FunctionFragment; "isStore()": FunctionFragment; + "moreStruct(bytes32,uint8[],(uint8,uint8,bytes)[])": FunctionFragment; "pickUp(uint32,uint32)": FunctionFragment; "popFromField(bytes16,bytes16,bytes32[],uint8,uint256)": FunctionFragment; "popFromField(bytes32,bytes32[],uint8,uint256)": FunctionFragment; @@ -108,6 +121,7 @@ export interface IWorldInterface extends utils.Interface { | "installModule" | "installRootModule" | "isStore" + | "moreStruct" | "pickUp" | "popFromField(bytes16,bytes16,bytes32[],uint8,uint256)" | "popFromField(bytes32,bytes32[],uint8,uint256)" @@ -249,6 +263,14 @@ export interface IWorldInterface extends utils.Interface { values: [PromiseOrValue, PromiseOrValue] ): string; encodeFunctionData(functionFragment: "isStore", values?: undefined): string; + encodeFunctionData( + functionFragment: "moreStruct", + values: [ + PromiseOrValue, + PromiseOrValue[], + MoreStructStruct[] + ] + ): string; encodeFunctionData( functionFragment: "pickUp", values: [PromiseOrValue, PromiseOrValue] @@ -527,6 +549,7 @@ export interface IWorldInterface extends utils.Interface { data: BytesLike ): Result; decodeFunctionResult(functionFragment: "isStore", data: BytesLike): Result; + decodeFunctionResult(functionFragment: "moreStruct", data: BytesLike): Result; decodeFunctionResult(functionFragment: "pickUp", data: BytesLike): Result; decodeFunctionResult( functionFragment: "popFromField(bytes16,bytes16,bytes32[],uint8,uint256)", @@ -849,6 +872,13 @@ export interface IWorld extends BaseContract { isStore(overrides?: CallOverrides): Promise<[void]>; + moreStruct( + tableId: PromiseOrValue, + projectionFieldIndices: PromiseOrValue[], + arg2: MoreStructStruct[], + overrides?: Overrides & { from?: PromiseOrValue } + ): Promise; + pickUp( item: PromiseOrValue, itemVariant: PromiseOrValue, @@ -1171,6 +1201,13 @@ export interface IWorld extends BaseContract { isStore(overrides?: CallOverrides): Promise; + moreStruct( + tableId: PromiseOrValue, + projectionFieldIndices: PromiseOrValue[], + arg2: MoreStructStruct[], + overrides?: Overrides & { from?: PromiseOrValue } + ): Promise; + pickUp( item: PromiseOrValue, itemVariant: PromiseOrValue, @@ -1491,6 +1528,13 @@ export interface IWorld extends BaseContract { isStore(overrides?: CallOverrides): Promise; + moreStruct( + tableId: PromiseOrValue, + projectionFieldIndices: PromiseOrValue[], + arg2: MoreStructStruct[], + overrides?: CallOverrides + ): Promise; + pickUp( item: PromiseOrValue, itemVariant: PromiseOrValue, @@ -1858,6 +1902,13 @@ export interface IWorld extends BaseContract { isStore(overrides?: CallOverrides): Promise; + moreStruct( + tableId: PromiseOrValue, + projectionFieldIndices: PromiseOrValue[], + arg2: MoreStructStruct[], + overrides?: Overrides & { from?: PromiseOrValue } + ): Promise; + pickUp( item: PromiseOrValue, itemVariant: PromiseOrValue, @@ -2181,6 +2232,13 @@ export interface IWorld extends BaseContract { isStore(overrides?: CallOverrides): Promise; + moreStruct( + tableId: PromiseOrValue, + projectionFieldIndices: PromiseOrValue[], + arg2: MoreStructStruct[], + overrides?: Overrides & { from?: PromiseOrValue } + ): Promise; + pickUp( item: PromiseOrValue, itemVariant: PromiseOrValue, diff --git a/examples/minimal/packages/contracts/types/ethers-contracts/factories/IWorld__factory.ts b/examples/minimal/packages/contracts/types/ethers-contracts/factories/IWorld__factory.ts index 65d99da0a2..3fdac1717b 100644 --- a/examples/minimal/packages/contracts/types/ethers-contracts/factories/IWorld__factory.ts +++ b/examples/minimal/packages/contracts/types/ethers-contracts/factories/IWorld__factory.ts @@ -739,6 +739,46 @@ const _abi = [ stateMutability: "view", type: "function", }, + { + inputs: [ + { + internalType: "bytes32", + name: "tableId", + type: "bytes32", + }, + { + internalType: "uint8[]", + name: "projectionFieldIndices", + type: "uint8[]", + }, + { + components: [ + { + internalType: "enum SelectionType", + name: "selectionType", + type: "uint8", + }, + { + internalType: "uint8", + name: "fieldIndex", + type: "uint8", + }, + { + internalType: "bytes", + name: "value", + type: "bytes", + }, + ], + internalType: "struct MoreStruct[]", + name: "", + type: "tuple[]", + }, + ], + name: "moreStruct", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, { inputs: [ { diff --git a/packages/world/mud.config.ts b/packages/world/mud.config.ts index 177cffeeea..57d9aba08b 100644 --- a/packages/world/mud.config.ts +++ b/packages/world/mud.config.ts @@ -144,6 +144,11 @@ export default mudConfig({ schema: "address[]", tableIdArgument: true, }, + Customer: { + directory: "../test/tables", + schema: { balance: "uint32", name: "string" }, + tableIdArgument: true, + }, }, enums: { Resource: ["NONE", "NAMESPACE", "TABLE", "SYSTEM"], diff --git a/packages/world/src/Tables.sol b/packages/world/src/Tables.sol index b6b1f4bd1a..0f46301efc 100644 --- a/packages/world/src/Tables.sol +++ b/packages/world/src/Tables.sol @@ -17,3 +17,4 @@ import { UsedKeysIndex, UsedKeysIndexTableId } from "./modules/keysintable/table import { UniqueEntity } from "./modules/uniqueentity/tables/UniqueEntity.sol"; import { Bool } from "./../test/tables/Bool.sol"; import { AddressArray } from "./../test/tables/AddressArray.sol"; +import { Customer, CustomerData } from "./../test/tables/Customer.sol"; diff --git a/packages/world/src/interfaces/IBaseWorld.sol b/packages/world/src/interfaces/IBaseWorld.sol index e05669f33a..792ef08972 100644 --- a/packages/world/src/interfaces/IBaseWorld.sol +++ b/packages/world/src/interfaces/IBaseWorld.sol @@ -11,6 +11,7 @@ import { IAccessManagementSystem } from "./IAccessManagementSystem.sol"; import { IEphemeralRecordSystem } from "./IEphemeralRecordSystem.sol"; import { IModuleInstallationSystem } from "./IModuleInstallationSystem.sol"; import { IWorldRegistrationSystem } from "./IWorldRegistrationSystem.sol"; +import { IQuerySystem } from "./IQuerySystem.sol"; /** * The IBaseWorld interface includes all systems dynamically added to the World @@ -23,7 +24,8 @@ interface IBaseWorld is IAccessManagementSystem, IEphemeralRecordSystem, IModuleInstallationSystem, - IWorldRegistrationSystem + IWorldRegistrationSystem, + IQuerySystem { } diff --git a/packages/world/src/interfaces/IQuerySystem.sol b/packages/world/src/interfaces/IQuerySystem.sol new file mode 100644 index 0000000000..56be9f4a01 --- /dev/null +++ b/packages/world/src/interfaces/IQuerySystem.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.0; + +/* Autogenerated file. Do not edit manually. */ + +import { IStore } from "@latticexyz/store/src/IStore.sol"; +import { SelectionFragment, Record } from "./../modules/query/systems/structs.sol"; + +interface IQuerySystem { + function query( + IStore store, + bytes32 tableId, + uint8[] memory projectionFieldIndices, + SelectionFragment[] memory fragments + ) external view returns (Record[] memory records); +} diff --git a/packages/world/src/modules/query/systems/QuerySystem.sol b/packages/world/src/modules/query/systems/QuerySystem.sol new file mode 100644 index 0000000000..6cb647325b --- /dev/null +++ b/packages/world/src/modules/query/systems/QuerySystem.sol @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.0; + +import { IStore } from "@latticexyz/store/src/IStore.sol"; +import { System } from "../../../System.sol"; +import { StoreSwitch } from "@latticexyz/store/src/StoreSwitch.sol"; +import { getKeysInTable } from "../../keysintable/getKeysInTable.sol"; +import { Record, SelectionFragment, SelectionType } from "./structs.sol"; + +function isEqual(bytes memory a, bytes memory b) pure returns (bool) { + return keccak256(a) == keccak256(b); +} + +function passesSelectionFragment(SelectionFragment memory fragment, bytes memory value) pure returns (bool) { + if (fragment.selectionType == SelectionType.Equal && !isEqual(fragment.value, value)) { + return false; + } + if (fragment.selectionType == SelectionType.NotEqual && isEqual(fragment.value, value)) { + return false; + } + + return true; +} + +// This implements SQL-style queries like: +// SELECT * FROM table WHERE name="" +contract QuerySystem is System { + function query( + IStore store, + bytes32 tableId, + uint8[] memory projectionFieldIndices, + SelectionFragment[] memory fragments + ) public view returns (Record[] memory records) { + bytes32[][] memory keys = getKeysInTable(store, tableId); + + records = new Record[](0); + + for (uint256 i; i < keys.length; i++) { + bool passes = true; + for (uint256 j; j < fragments.length; j++) { + bytes memory blob = store.getField(tableId, keys[i], fragments[j].fieldIndex); + + if (!passesSelectionFragment(fragments[j], blob)) { + passes = false; + break; + } + } + + if (passes) { + // Increase the length of array + uint256 length = records.length; + + Record[] memory newRecords = new Record[](length + 1); + for (uint256 k; k < length; k++) { + newRecords[k] = records[k]; + } + records = newRecords; + + records[length].key = keys[i]; + records[length].value = new bytes[](projectionFieldIndices.length); + + for (uint256 j; j < projectionFieldIndices.length; j++) { + records[length].value[j] = store.getField(tableId, keys[i], projectionFieldIndices[j]); + } + } + } + } +} diff --git a/packages/world/src/modules/query/systems/structs.sol b/packages/world/src/modules/query/systems/structs.sol new file mode 100644 index 0000000000..d392de09bb --- /dev/null +++ b/packages/world/src/modules/query/systems/structs.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.0; + +enum SelectionType { + Equal, + NotEqual +} + +struct SelectionFragment { + SelectionType selectionType; + uint8 fieldIndex; + bytes value; +} + +struct Record { + bytes32[] key; + bytes[] value; +} diff --git a/packages/world/src/modules/query/world/IQuerySystem.sol b/packages/world/src/modules/query/world/IQuerySystem.sol new file mode 100644 index 0000000000..2ab3aa1902 --- /dev/null +++ b/packages/world/src/modules/query/world/IQuerySystem.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.0; + +/* Autogenerated file. Do not edit manually. */ + +import { IStore } from "@latticexyz/store/src/IStore.sol"; +import { SelectionFragment, Record } from "../systems/structs.sol"; + +interface IQuerySystem { + function query_system_query( + IStore store, + bytes32 tableId, + uint8[] memory projectionFieldIndices, + SelectionFragment[] memory fragments + ) external view returns (Record[] memory records); +} diff --git a/packages/world/src/modules/query/world/IWorld.sol b/packages/world/src/modules/query/world/IWorld.sol new file mode 100644 index 0000000000..040508d324 --- /dev/null +++ b/packages/world/src/modules/query/world/IWorld.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.0; + +/* Autogenerated file. Do not edit manually. */ + +import { IBaseWorld } from "../../../interfaces/IBaseWorld.sol"; + +import { IQuerySystem } from "./IQuerySystem.sol"; + +/** + * The IWorld interface includes all systems dynamically added to the World + * during the deploy process. + */ +interface IWorld is IBaseWorld, IQuerySystem { + +} diff --git a/packages/world/test/sql.t.sol b/packages/world/test/sql.t.sol new file mode 100644 index 0000000000..6393bcbad7 --- /dev/null +++ b/packages/world/test/sql.t.sol @@ -0,0 +1,135 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.0; + +import "forge-std/Test.sol"; + +import { World } from "../src/World.sol"; +import { Customer } from "./tables/Customer.sol"; +import { IWorld } from "../src/modules/query/world/IWorld.sol"; +import { QuerySystem } from "../src/modules/query/systems/QuerySystem.sol"; +import { SelectionFragment, SelectionType, Record } from "../src/modules/query/systems/structs.sol"; +import { Schema, SchemaLib } from "@latticexyz/store/src/Schema.sol"; +import { SchemaType } from "@latticexyz/schema-type/src/solidity/SchemaType.sol"; +import { CoreModule } from "../src/modules/core/CoreModule.sol"; +import { KeysInTableModule } from "../src/modules/keysintable/KeysInTableModule.sol"; + +function isEqual(bytes memory a, bytes memory b) pure returns (bool) { + return keccak256(a) == keccak256(b); +} + +function isEqual(string memory a, string memory b) pure returns (bool) { + return keccak256(abi.encode(a)) == keccak256(abi.encode(b)); +} + +contract SqlTest is Test { + address worldAddress; + IWorld public world; + + Schema defaultKeySchema = SchemaLib.encode(SchemaType.BYTES32); + bytes32 tableId; + + QuerySystem private immutable querySystem = new QuerySystem(); + KeysInTableModule keysInTableModule = new KeysInTableModule(); + + function setUp() public { + worldAddress = address(new World()); + world = IWorld(worldAddress); + world.installRootModule(new CoreModule(), new bytes(0)); + + bytes16 namespace = "query"; + bytes16 name = "table"; + bytes16 systemName = "system"; + + tableId = world.registerTable(namespace, name, Customer.getSchema(), defaultKeySchema); + world.registerSystem(namespace, systemName, querySystem, true); + // Register system's functions + world.registerFunctionSelector(namespace, systemName, "query", "(address,bytes32,uint8[],(uint8,uint8,bytes)[])"); + + world.installRootModule(keysInTableModule, abi.encode(tableId)); + } + + function testQuery(bytes32 key, uint32 value, string memory name) public { + vm.assume(bytes(name).length > 0); + + vm.prank(worldAddress); + Customer.set(world, tableId, key, value, name); + uint32 balance = Customer.getBalance(world, tableId, key); + assertEq(balance, value); + + uint8[] memory projectionFieldIndices = new uint8[](2); + projectionFieldIndices[0] = 0; + projectionFieldIndices[1] = 1; + SelectionFragment[] memory fragments = new SelectionFragment[](0); + + Record[] memory records = world.query_system_query(world, tableId, projectionFieldIndices, fragments); + + assertEq(records.length, 1); + assertTrue(isEqual(records[0].value[0], abi.encodePacked(value))); + assertTrue(isEqual(records[0].value[1], abi.encodePacked(name))); + } + + function filter( + SelectionType selectionType, + uint8 fieldIndex, + bytes memory value, + bytes memory result0, + bytes memory result1 + ) internal { + uint8[] memory projectionFieldIndices = new uint8[](2); + projectionFieldIndices[0] = 0; + projectionFieldIndices[1] = 1; + + SelectionFragment[] memory fragments = new SelectionFragment[](1); + fragments[0] = SelectionFragment(selectionType, fieldIndex, value); + + Record[] memory records = world.query_system_query(world, tableId, projectionFieldIndices, fragments); + + assertEq(records.length, 1); + assertTrue(isEqual(records[0].value[0], result0)); + assertTrue(isEqual(records[0].value[1], result1)); + } + + function testQuerySelection( + bytes32 key1, + bytes32 key2, + uint32 value1, + uint32 value2, + string memory name1, + string memory name2 + ) public { + vm.assume(key1 != key2); + vm.assume(value1 != value2); + vm.assume(!isEqual(name1, name2)); + vm.assume(bytes(name1).length > 0); + vm.assume(bytes(name2).length > 0); + + vm.startPrank(worldAddress); + Customer.set(world, tableId, key1, value1, name1); + Customer.set(world, tableId, key2, value2, name2); + vm.stopPrank(); + + assertEq(Customer.getBalance(world, tableId, key1), value1); + assertEq(Customer.getBalance(world, tableId, key2), value2); + + uint8[] memory projectionFieldIndices = new uint8[](2); + projectionFieldIndices[0] = 0; + projectionFieldIndices[1] = 1; + SelectionFragment[] memory fragments = new SelectionFragment[](0); + + Record[] memory records = world.query_system_query(world, tableId, projectionFieldIndices, fragments); + + assertEq(records.length, 2); + + // FILTER BY BALANCE + filter(SelectionType.Equal, 0, abi.encodePacked(value1), abi.encodePacked(value1), abi.encodePacked(name1)); + filter(SelectionType.Equal, 0, abi.encodePacked(value2), abi.encodePacked(value2), abi.encodePacked(name2)); + filter(SelectionType.NotEqual, 0, abi.encodePacked(value1), abi.encodePacked(value2), abi.encodePacked(name2)); + filter(SelectionType.NotEqual, 0, abi.encodePacked(value2), abi.encodePacked(value1), abi.encodePacked(name1)); + + // FILTER BY NAME + filter(SelectionType.Equal, 1, abi.encodePacked(name1), abi.encodePacked(value1), abi.encodePacked(name1)); + filter(SelectionType.Equal, 1, abi.encodePacked(name2), abi.encodePacked(value2), abi.encodePacked(name2)); + filter(SelectionType.NotEqual, 1, abi.encodePacked(name1), abi.encodePacked(value2), abi.encodePacked(name2)); + filter(SelectionType.NotEqual, 1, abi.encodePacked(name2), abi.encodePacked(value1), abi.encodePacked(name1)); + } +} diff --git a/packages/world/test/tables/Customer.sol b/packages/world/test/tables/Customer.sol new file mode 100644 index 0000000000..1fa4e9cf30 --- /dev/null +++ b/packages/world/test/tables/Customer.sol @@ -0,0 +1,326 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.0; + +/* Autogenerated file. Do not edit manually. */ + +// Import schema type +import { SchemaType } from "@latticexyz/schema-type/src/solidity/SchemaType.sol"; + +// Import store internals +import { IStore } from "@latticexyz/store/src/IStore.sol"; +import { StoreSwitch } from "@latticexyz/store/src/StoreSwitch.sol"; +import { StoreCore } from "@latticexyz/store/src/StoreCore.sol"; +import { Bytes } from "@latticexyz/store/src/Bytes.sol"; +import { Memory } from "@latticexyz/store/src/Memory.sol"; +import { SliceLib } from "@latticexyz/store/src/Slice.sol"; +import { EncodeArray } from "@latticexyz/store/src/tightcoder/EncodeArray.sol"; +import { Schema, SchemaLib } from "@latticexyz/store/src/Schema.sol"; +import { PackedCounter, PackedCounterLib } from "@latticexyz/store/src/PackedCounter.sol"; + +struct CustomerData { + uint32 balance; + string name; +} + +library Customer { + /** Get the table's schema */ + function getSchema() internal pure returns (Schema) { + SchemaType[] memory _schema = new SchemaType[](2); + _schema[0] = SchemaType.UINT32; + _schema[1] = SchemaType.STRING; + + return SchemaLib.encode(_schema); + } + + function getKeySchema() internal pure returns (Schema) { + SchemaType[] memory _schema = new SchemaType[](1); + _schema[0] = SchemaType.BYTES32; + + return SchemaLib.encode(_schema); + } + + /** Get the table's metadata */ + function getMetadata() internal pure returns (string memory, string[] memory) { + string[] memory _fieldNames = new string[](2); + _fieldNames[0] = "balance"; + _fieldNames[1] = "name"; + return ("Customer", _fieldNames); + } + + /** Register the table's schema */ + function registerSchema(bytes32 _tableId) internal { + StoreSwitch.registerSchema(_tableId, getSchema(), getKeySchema()); + } + + /** Register the table's schema (using the specified store) */ + function registerSchema(IStore _store, bytes32 _tableId) internal { + _store.registerSchema(_tableId, getSchema(), getKeySchema()); + } + + /** Set the table's metadata */ + function setMetadata(bytes32 _tableId) internal { + (string memory _tableName, string[] memory _fieldNames) = getMetadata(); + StoreSwitch.setMetadata(_tableId, _tableName, _fieldNames); + } + + /** Set the table's metadata (using the specified store) */ + function setMetadata(IStore _store, bytes32 _tableId) internal { + (string memory _tableName, string[] memory _fieldNames) = getMetadata(); + _store.setMetadata(_tableId, _tableName, _fieldNames); + } + + /** Get balance */ + function getBalance(bytes32 _tableId, bytes32 key) internal view returns (uint32 balance) { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = bytes32((key)); + + bytes memory _blob = StoreSwitch.getField(_tableId, _keyTuple, 0); + return (uint32(Bytes.slice4(_blob, 0))); + } + + /** Get balance (using the specified store) */ + function getBalance(IStore _store, bytes32 _tableId, bytes32 key) internal view returns (uint32 balance) { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = bytes32((key)); + + bytes memory _blob = _store.getField(_tableId, _keyTuple, 0); + return (uint32(Bytes.slice4(_blob, 0))); + } + + /** Set balance */ + function setBalance(bytes32 _tableId, bytes32 key, uint32 balance) internal { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = bytes32((key)); + + StoreSwitch.setField(_tableId, _keyTuple, 0, abi.encodePacked((balance))); + } + + /** Set balance (using the specified store) */ + function setBalance(IStore _store, bytes32 _tableId, bytes32 key, uint32 balance) internal { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = bytes32((key)); + + _store.setField(_tableId, _keyTuple, 0, abi.encodePacked((balance))); + } + + /** Get name */ + function getName(bytes32 _tableId, bytes32 key) internal view returns (string memory name) { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = bytes32((key)); + + bytes memory _blob = StoreSwitch.getField(_tableId, _keyTuple, 1); + return (string(_blob)); + } + + /** Get name (using the specified store) */ + function getName(IStore _store, bytes32 _tableId, bytes32 key) internal view returns (string memory name) { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = bytes32((key)); + + bytes memory _blob = _store.getField(_tableId, _keyTuple, 1); + return (string(_blob)); + } + + /** Set name */ + function setName(bytes32 _tableId, bytes32 key, string memory name) internal { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = bytes32((key)); + + StoreSwitch.setField(_tableId, _keyTuple, 1, bytes((name))); + } + + /** Set name (using the specified store) */ + function setName(IStore _store, bytes32 _tableId, bytes32 key, string memory name) internal { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = bytes32((key)); + + _store.setField(_tableId, _keyTuple, 1, bytes((name))); + } + + /** Get the length of name */ + function lengthName(bytes32 _tableId, bytes32 key) internal view returns (uint256) { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = bytes32((key)); + + uint256 _byteLength = StoreSwitch.getFieldLength(_tableId, _keyTuple, 1, getSchema()); + return _byteLength / 1; + } + + /** Get the length of name (using the specified store) */ + function lengthName(IStore _store, bytes32 _tableId, bytes32 key) internal view returns (uint256) { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = bytes32((key)); + + uint256 _byteLength = _store.getFieldLength(_tableId, _keyTuple, 1, getSchema()); + return _byteLength / 1; + } + + /** Get an item of name (unchecked, returns invalid data if index overflows) */ + function getItemName(bytes32 _tableId, bytes32 key, uint256 _index) internal view returns (string memory) { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = bytes32((key)); + + bytes memory _blob = StoreSwitch.getFieldSlice(_tableId, _keyTuple, 1, getSchema(), _index * 1, (_index + 1) * 1); + return (string(_blob)); + } + + /** Get an item of name (using the specified store) (unchecked, returns invalid data if index overflows) */ + function getItemName( + IStore _store, + bytes32 _tableId, + bytes32 key, + uint256 _index + ) internal view returns (string memory) { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = bytes32((key)); + + bytes memory _blob = _store.getFieldSlice(_tableId, _keyTuple, 1, getSchema(), _index * 1, (_index + 1) * 1); + return (string(_blob)); + } + + /** Push a slice to name */ + function pushName(bytes32 _tableId, bytes32 key, string memory _slice) internal { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = bytes32((key)); + + StoreSwitch.pushToField(_tableId, _keyTuple, 1, bytes((_slice))); + } + + /** Push a slice to name (using the specified store) */ + function pushName(IStore _store, bytes32 _tableId, bytes32 key, string memory _slice) internal { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = bytes32((key)); + + _store.pushToField(_tableId, _keyTuple, 1, bytes((_slice))); + } + + /** Pop a slice from name */ + function popName(bytes32 _tableId, bytes32 key) internal { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = bytes32((key)); + + StoreSwitch.popFromField(_tableId, _keyTuple, 1, 1); + } + + /** Pop a slice from name (using the specified store) */ + function popName(IStore _store, bytes32 _tableId, bytes32 key) internal { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = bytes32((key)); + + _store.popFromField(_tableId, _keyTuple, 1, 1); + } + + /** Update a slice of name at `_index` */ + function updateName(bytes32 _tableId, bytes32 key, uint256 _index, string memory _slice) internal { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = bytes32((key)); + + StoreSwitch.updateInField(_tableId, _keyTuple, 1, _index * 1, bytes((_slice))); + } + + /** Update a slice of name (using the specified store) at `_index` */ + function updateName(IStore _store, bytes32 _tableId, bytes32 key, uint256 _index, string memory _slice) internal { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = bytes32((key)); + + _store.updateInField(_tableId, _keyTuple, 1, _index * 1, bytes((_slice))); + } + + /** Get the full data */ + function get(bytes32 _tableId, bytes32 key) internal view returns (CustomerData memory _table) { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = bytes32((key)); + + bytes memory _blob = StoreSwitch.getRecord(_tableId, _keyTuple, getSchema()); + return decode(_blob); + } + + /** Get the full data (using the specified store) */ + function get(IStore _store, bytes32 _tableId, bytes32 key) internal view returns (CustomerData memory _table) { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = bytes32((key)); + + bytes memory _blob = _store.getRecord(_tableId, _keyTuple, getSchema()); + return decode(_blob); + } + + /** Set the full data using individual values */ + function set(bytes32 _tableId, bytes32 key, uint32 balance, string memory name) internal { + bytes memory _data = encode(balance, name); + + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = bytes32((key)); + + StoreSwitch.setRecord(_tableId, _keyTuple, _data); + } + + /** Set the full data using individual values (using the specified store) */ + function set(IStore _store, bytes32 _tableId, bytes32 key, uint32 balance, string memory name) internal { + bytes memory _data = encode(balance, name); + + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = bytes32((key)); + + _store.setRecord(_tableId, _keyTuple, _data); + } + + /** Set the full data using the data struct */ + function set(bytes32 _tableId, bytes32 key, CustomerData memory _table) internal { + set(_tableId, key, _table.balance, _table.name); + } + + /** Set the full data using the data struct (using the specified store) */ + function set(IStore _store, bytes32 _tableId, bytes32 key, CustomerData memory _table) internal { + set(_store, _tableId, key, _table.balance, _table.name); + } + + /** Decode the tightly packed blob using this table's schema */ + function decode(bytes memory _blob) internal view returns (CustomerData memory _table) { + // 4 is the total byte length of static data + PackedCounter _encodedLengths = PackedCounter.wrap(Bytes.slice32(_blob, 4)); + + _table.balance = (uint32(Bytes.slice4(_blob, 0))); + + // Store trims the blob if dynamic fields are all empty + if (_blob.length > 4) { + uint256 _start; + // skip static data length + dynamic lengths word + uint256 _end = 36; + + _start = _end; + _end += _encodedLengths.atIndex(0); + _table.name = (string(SliceLib.getSubslice(_blob, _start, _end).toBytes())); + } + } + + /** Tightly pack full data using this table's schema */ + function encode(uint32 balance, string memory name) internal view returns (bytes memory) { + uint40[] memory _counters = new uint40[](1); + _counters[0] = uint40(bytes(name).length); + PackedCounter _encodedLengths = PackedCounterLib.pack(_counters); + + return abi.encodePacked(balance, _encodedLengths.unwrap(), bytes((name))); + } + + /** Encode keys as a bytes32 array using this table's schema */ + function encodeKeyTuple(bytes32 key) internal pure returns (bytes32[] memory _keyTuple) { + _keyTuple = new bytes32[](1); + _keyTuple[0] = bytes32((key)); + } + + /* Delete all data for given keys */ + function deleteRecord(bytes32 _tableId, bytes32 key) internal { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = bytes32((key)); + + StoreSwitch.deleteRecord(_tableId, _keyTuple); + } + + /* Delete all data for given keys (using the specified store) */ + function deleteRecord(IStore _store, bytes32 _tableId, bytes32 key) internal { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = bytes32((key)); + + _store.deleteRecord(_tableId, _keyTuple); + } +}