Database Operations
On-chain data storage and retrieval is a critical feature of smart contracts. EOS implements an in-memory database that allows data to be stored in a table format. Each item in a table has a unique primary index, called a primary key, which is of type u64
. The raw data stored in the table can be binary data of any length. When the storage function of a smart contract is called, the data is serialized and stored in the table. When reading, the stored data is deserialized back as a class object. Additionally, EOS supports secondary index tables of types u64
, u128
, u256
, double
, and long double
, which can be considered special tables with a fixed data length. Primary and secondary index tables can be used together to implement multiple indexes. There can be multiple secondary index tables, and the values in these tables can be repeated, but the primary index in the primary index table must be unique.
The following example demonstrates the usage of EOS's in-memory database.
Store
Storage is the simplest function of the database, and the following code demonstrates this functionality.
# db_example1.codon
from chain.database import primary
from chain.contract import Contract
@table("mytable")
class A(object):
a: primary[u64]
b: str
def __init__(self, a: u64, b: str):
self.a = primary[u64](a)
self.b = b
@contract(main=True)
class MyContract(Contract):
def __init__(self):
super().__init__()
@action('teststore')
def test_store(self):
print('db_test')
item = A(123u64, 'hello, world')
table = A.new_table(n'hello', n'')
table.store(item, n'hello')
The code above is explained as follows:
-
In it, the line of code
@table("mytable")
indicates the creation of a table type namedmytable
. Note thatmytable
is aname
type.@table
instructs the compiler to generate additional code related to table operations, like thenew_table
method above, which is generated by the compiler. -
A.new_table(n'hello', n'')
creates a new table object,n'hello'
is used to specify the account in which the table is stored, the second parameter represents the scope, which can generally be left empty. It is used in conjunction with the account name and table name to distinguish different tables. -
The line of code
table.store(item, n'hello')
stores data in the on-chain database.n'hello'
in it specifies that the RAM resources are paid through thehello
account, and it is necessary to have already signed with theactive
authority of thehello
account in the Transaction.
Compile:
python-contract build db_example/db_example1.codon
ipyeos -m pytest -s -x test.py -k test_store
The test code you are running is as follows:
def test_store():
t = init_db_test('db_example1')
ret = t.push_action('hello', 'teststore', "", {'hello': 'active'})
t.produce_block()
logger.info("++++++++++%s\n", ret['elapsed'])
Note:
In this example, if there is already data with the primary key 123u64
in the table, an exception will be thrown when the function is called.
To modify the above test case to the following code:
def test_example1():
t = init_db_test('db_example1')
ret = t.push_action('hello', 'teststore', "", {'hello': 'active'})
t.produce_block()
logger.info("++++++++++%s\n", ret['elapsed'])
# will raise exception
ret = t.push_action('hello', 'teststore', "", {'hello': 'active'})
t.produce_block()
When running the test with the same command, if the push_action
is called for the second time, the function will throw an exception similar to the following:
could not insert object, most likely a uniqueness constraint was violated
To avoid such exceptions, the update
method must be used when updating data in the table. Before calling store
, it is necessary to check whether the primary index already exists in the table. If it does, store
method cannot be called, and update
method must be used instead. The following example demonstrates how to use it:
find/update
This section demonstrates the lookup and update functionality of the database.
# db_example1.codon
...
@contract(main=True)
class MyContract(Contract):
...
@action('testupdate')
def test_update(self, value: str):
print('db_test')
table = A.new_table(n'hello', n'')
key = 123u64
it = table.find(key)
if it.is_ok():
print('+++++update value:', value)
item = A(key, value)
table.update(it, item, n'hello')
else:
print('+++++store value:', value)
item = A(key, value)
table.store(item, n'hello')
The following is the test code:
def test_update():
t = init_db_test('db_example1')
ret = t.push_action('hello', 'testupdate', {'value': 'hello, bob'}, {'hello': 'active'})
t.produce_block()
logger.info("++++++++++%s\n", ret['elapsed'])
ret = t.push_action('hello', 'testupdate', {'value': 'hello, alice'}, {'hello': 'active'})
t.produce_block()
Compile using the following command:
python-contract build db_example/db_example1.codon
Execute the test code with the following command:
ipyeos -m pytest -s -x test.py -k test_update
When calling
t.push_action('hello', 'testupdate', {'value': 'hello, bob'}, {'hello': 'active'})
it will output:
+++++store value: hello, bob
When calling testupdate
action again:
t.push_action('hello', 'testupdate', {'value': 'hello, alice'}, {'hello': 'active'})
it will output:
+++++update value: hello, alice
As you can see, the above code is a bit complicated. First, it needs to call find
to determine whether the value corresponding to the primary index exists, and then decide whether to call store
or update
. It should be noted that, during the update process, the value of the primary index cannot be changed, otherwise an exception will be thrown.
You can try to modify the update code to:
item = A(key+1u64, value)
table.update(it, item, n'hello')
You will see an exception thrown in the smart contract.
Remove
The following code demonstrates how to remove an item from the database.
# db_example/db_example1.codon
@action('testremove')
def test_remove(self):
print('test remove')
item = A(123u64, 'hello, world')
table = A.new_table(n'hello', n'')
table.store(item, n'hello')
it = table.find(123u64)
assert it.is_ok()
table.remove(it)
it = table.find(123u64)
assert not it.is_ok()
Test code:
def test_remove():
t = init_db_test('db_example1')
ret = t.push_action('hello', 'testremove', "", {'hello': 'active'})
t.produce_block()
logger.info("++++++++++%s\n", ret['elapsed'])
Compile using the following command:
python-contract build db_example/db_example1.codon
Test using the following command:
ipyeos -m pytest -s -x test.py -k test_remove
The above code first calls the store
method to store the data with index 123u64
in the database, then calls remove
to delete it, and uses assert
to check the result. If everything is normal, the program will not throw any exceptions.
Lowerbound/Upperbound
These two methods are also used to search for elements in the database. Unlike the find
method, these two functions are used for fuzzy searching. Among them, the lowerbound
method returns an Iterator
whose id
is >=
the specified id
, and the upperbound
method returns an Iterator
whose id
is >
the specified id
. Let's take a look at the usage below:
# db_example/db_example1.codon
...
@contract(main=True)
class MyContract(Contract):
...
@action('testbound')
def test_bound(self):
print('db_test')
table = A.new_table(n'hello', n'')
payer = n'hello'
value = A(1u64, "alice")
table.store(value, payer)
value = A(3u64, "bob")
table.store(value, payer)
value = A(5u64, "john")
table.store(value, payer)
it = table.lowerbound(1u64)
value2: A = it.get_value()
print("+++++:", value2.a, value2.b)
assert value2.a == 1u64 and value2.b == 'alice'
it = table.upperbound(1u64)
value2: A = it.get_value()
print("+++++:", value2.a, value2.b)
assert value2.a == 3u64 and value2.b == 'bob'
Test code:
def test_bound():
t = init_db_test('db_example4')
ret = t.push_action('hello', 'testbound', {}, {'hello': 'active'})
t.produce_block()
logger.info("++++++++++%s\n", ret['elapsed'])
Compile using the following command:
python-contract build db_example/db_example1.codon
Run the test using the following command:
ipyeos -m pytest -s -x test.py -k test_bound
Output:
+++++: 1 alice
+++++: 3 bob
Querying the Primary Index of a Table Using API
The above examples are all about how to operate the database table on the chain through the smart contract. In fact, by using the get_table_rows
API provided by EOS off the chain, you can also query the table on the chain.
In the test code, the definition of get_table_rows
is as follows:
def get_table_rows(self, _json, code, scope, table,
lower_bound, upper_bound,
limit,
key_type='',
index_position='',
encode_type='',
reverse = False,
show_payer = False):
""" Fetch smart contract data from an account.
key_type: "i64"|"i128"|"i256"|"float64"|"float128"|"sha256"|"ripemd160"
index_position: "2"|"3"|"4"|"5"|"6"|"7"|"8"|"9"|"10"
encode_type: "dec" or "hex", default to "dec"
"""
Let's break down the parameters for this function:
_json
: If True, it returns JSON-formatted database records, if False, it returns the raw data in hexadecimal format.code
: Represents the account where the table is located.scope
: Generally set as an empty string. Whencode
andtable
are the same, differentscope
can be used to differentiate different tables.table
: The name of the table to be queried.lower_bound
: The starting value of the primary or secondary index, specified bykey_type
. It can be numeric, a string of numbers, or a hexadecimal string.upper_bound
: The ending value of the primary or secondary index, specified bykey_type
. It can be numeric, a string of numbers, or a hexadecimal string. If empty, it means no upper limit has been set. If a non-empty value is set, the results will return all values that are>=lower_bound
and<=upper_bound
.limit
: Limits the number of returned values. If the queried records exceed the limit,more
will be set totrue
in the returned values, andnext_key
will point to the next valid index.key_type
: The values can be:"name"
,"i64"
,"i128"
,"i256"
,"float64"
,"float128"
,"sha256"
,"ripemd160"
. For the primary index (i.e.,index_position
is1
), the value can only be"name"
or"i64"
. For secondary index (i.e.,index_position >= 2
), the value could be any of the listed types. The encoding method oflower_bound
andupper_bound
under each value will be explained separately below.index_position
: Specifies the relative position of the index. If it's empty or1
, it denotes the primary index. Any number above2
denotes the position of the secondary index.encode_type
: It's either"dec"
or"hex"
, defaulting to"dec"
. It specifies the encoding format oflower_bound
,upper_bound
, and the return valuenext_key
.reverse
: Specifies whether the data should be returned in reverse order.show_payer
: Specifies whether to display the RAM resource paying account.
Detailed explanation for key_type
:
- "name" is a
name
type string. - "i64" can be a numeric type or a string of numbers, such as 123 or "123".
- "i128" can be a numeric type, a string of numbers, or a hexadecimal string, such as: 123, "123", "0xaabb", "aabb".
- "i256" when the value of
encode_type
is"dec"
or an empty string""
, the encoding format is: a hexadecimal string, represented in little-endian mode, 64 characters in length. For example:fb54b91bfed2fe7fe39a92d999d002c550f0fa8360ec998f4bb65b00c86282f5
will be converted into twouint128_t
type values in little-endian mode:50f0fa8360ec998f4bb65b00c86282f5
andfb54b91bfed2fe7fe39a92d999d002c5
. When the value ofencode_type
is"hex"
, it uses the same encoding method as the"sha256"
type, which is big-endian mode. - "float64": The value is a floating-point string, like
"123.456"
. - "float128": When the value of
encode_type
is"dec"
or an empty string""
, the value is a floating-point string, like"123.456"
, and the range it represents can only be within the range allowed byfloat64
. When the value ofencode_type
is"hex"
,encode_type
represents the data as a hexadecimal string in little-endian mode. - "sha256": A hexadecimal string represented in big-endian mode, 64 characters long, will be converted into two
uint128_t
type values in little-endian mode: such asf58262c8005bb64b8f99ec6083faf050c502d099d9929ae37ffed2fe1bb954fb
will be converted into50f0fa8360ec998f4bb65b00c86282f5
andfb54b91bfed2fe7fe39a92d999d002c5
. Refer to the keytype_converter structure's code for more details. - "ripemd160": A hexadecimal string, 64 characters long, big-endian mode, will be converted into two
uint128_t
type values in little-endian mode: such as83a83a3876c64c33f66f33c54f1869edef5b5d4a000000000000000000000000
will be converted intoed69184fc5336ff6334cc676383aa883
and0000000000000000000000004a5d5bef
. Refer to the keytype_converter structure's code for more details.
The get_table_rows
function's parameters are quite complex, here's a summary:
- If
lower_bound
andupper_bound
are empty, it means the query has no range limit. - When the value of
key_type
is"i256"
and"float128"
, the encoding method oflower_bound
andupper_bound
is also affected byencode_type
.
First of all, to query a table using get_table_rows
, the structure of the table must be visible in the ABI description. You can use the following code to describe the table in the corresponding generated ABI file:
# db_example5.codon
from chain.database import primary
from chain.contract import Contract
@table("mytable")
class A(object):
a: primary[u64]
b: str
def __init__(self, a: u64, b: str):
self.a = primary[u64](a)
self.b = b
@contract(main=True)
class MyContract(Contract):
def __init__(self):
super().__init__()
@action('test')
def test(self):
print('db_test')
table = A.new_table(n'hello', n'')
payer = n'hello'
value = A(1u64, "alice")
table.store(value, payer)
value = A(3u64, "bob")
table.store(value, payer)
value = A(5u64, "john")
table.store(value, payer)
it = table.lowerbound(1u64)
value2: A = it.get_value()
print("+++++:", value2.a, value2.b)
assert value2.a() == 1u64 and value2.b == 'alice'
it = table.upperbound(1u64)
value2: A = it.get_value()
print("+++++:", value2.a, value2.b)
assert value2.a() == 3u64 and value2.b == 'bob'
Here, the table
decorator is used to make the compiler include the structure of the table in the ABI.
After adding this table
decorator to the class, the compiler will automatically add the get_primary
and new_table
functions to the class.
At the same time, the member variables of the class must also meet certain requirements: first, a primary index variable must be declared, and the type must be database.primary
. The implementation of the primary
class is as follows:
class primary[T](object):
value: T
def __init__(self, value: T):
self.value = value
def get_primary(self) -> u64:
if isinstance(self.value, u64):
return self.value
return self.value.get_primary()
def __pack__(self, enc: Encoder):
self.value.__pack__(enc)
def __unpack__(dec: Decoder) -> primary[T]:
return primary[T](T.__unpack__(dec))
def __call__(self) -> T:
return self.value
def __size__(self) -> int:
return self.value.__size__()
The primary
class is a template class. If the type of the value
in primary
is not of type u64
, then the type must implement the get_primary
method. The primary
class also has a __call__
method to facilitate access to value
. In the following discussion of multiple indexes, secondary indexes will also be used. The type of the secondary index must be database.secondary
.
Compilation:
python-contract build db_example/db_example5.codon
You will see the following description in the generated db_example5.abi:
"tables": [
{
"name": "mytable",
"type": "A",
"index_type": "i64",
"key_names": [],
"key_types": []
}
]
Now consider the test code:
def test_example5():
t = init_db_test('db_example5')
ret = t.push_action('hello', 'test', {}, {'hello': 'active'})
t.produce_block()
logger.info("++++++++++%s\n", ret['elapsed'])
rows = t.get_table_rows(True, 'hello', '', 'mytable', 1, '', 10)
logger.info('++++++=rows: %s', rows)
Run the test:
ipyeos -m pytest -s -x test.py -k test_example5
Output:
++++++=rows: {'rows': [{'a': 1, 'b': 'alice'}, {'a': 3, 'b': 'bob'}, {'a': 5, 'b': 'john'}], 'more': False, 'next_key': ''}
Operation of the secondary indexes
First, consider the following example:
# db_example7.codon
from chain.contract import Contract
from chain.database import primary, secondary
from chain.database import IdxTable64, IdxTable128, Iterator
from chain.name import Name
@table("mytable")
class A(object):
a: database.primary[u64]
b: secondary[u64]
c: secondary[u128]
def __init__(self, a: u64, b: u64, c: u128):
self.a = primary[u64](a)
self.b = secondary[u64](b)
self.c = secondary[u128](c)
@contract(main=True)
class MyContract(Contract):
def __init__(self):
super().__init__()
@action('test')
def test(self):
payer = n"hello"
table = A.new_table(n"hello", n"")
item = A(1u64, 2u64, 3u128)
table.store(item, payer)
idx_table_b = table.get_idx_table_by_b()
it = idx_table_b.find(2u64)
print("++++++it.primary:", it.primary)
assert it.primary == 1u64
idx_table_c = table.get_idx_table_by_c()
it = idx_table_c.find(3u128)
print("++++++it.primary:", it.primary)
assert it.primary == 1u64
In this example, two secondary indexes are defined:
b: secondary[u64]
c: secondary[u128]
In the code, get_idx_table_by_b
and get_idx_table_by_c
are used to obtain the tables of the secondary indexes, and the returned object types are IdxTable64
and IdxTable128
, respectively. The tables of secondary indexes have similar method names as the tables of the primary index, and can also perform the function of secondary index lookup.
Test code:
# test.py
def test_example7():
t = init_db_test('db_example7')
ret = t.push_action('hello', 'test', {}, {'hello': 'active'})
t.produce_block()
logger.info("++++++++++%s\n", ret['elapsed'])
Compile:
python-contract build db_example/db_example7.codon
Run the test:
ipyeos -m pytest -s -x test.py -k test_example7
Output:
++++++it.primary: 1
++++++it.primary: 1
Updating the secondary indexes
In practical applications, sometimes it is necessary to update the secondary index. Please first look at the following code:
# db_example8.codon
from chain.contract import Contract
from chain.database import primary, secondary
from chain.database import IdxTable64, IdxTable128, Iterator
from chain.name import Name
@table("mytable")
class A(object):
a: database.primary[u64]
b: secondary[u64]
c: secondary[u128]
def __init__(self, a: u64, b: u64, c: u128):
self.a = primary[u64](a)
self.b = secondary[u64](b)
self.c = secondary[u128](c)
@contract(main=True)
class MyContract(Contract):
def __init__(self):
super().__init__()
@action('test')
def test(self):
payer = n"hello"
table = A.new_table(n"hello", n"")
item = A(1u64, 2u64, 3u128)
table.store(item, payer)
item = A(111u64, 222u64, 333u128)
table.store(item, payer)
idx_table_b = table.get_idx_table_by_b()
it_sec = idx_table_b.find(2u64)
print("++++++it.primary:", it_sec.primary)
assert it_sec.primary == 1u64
table.update_b(it_sec, 22u64, payer)
it_sec = idx_table_b.find(22u64)
assert it_sec.is_ok()
print("++++++it.primary:", it_sec.primary)
assert it_sec.primary == 1u64
Note the following code in the above code:
idx_table_b = table.get_idx_table_by_b()
it_sec = idx_table_b.find(2u64)
print("++++++it.primary:", it_sec.primary)
assert it_sec.primary == 1u64
table.update_b(it_sec, 22u64, payer)
it_sec = idx_table_b.find(22u64)
assert it_sec.is_ok()
print("++++++it.primary:", it_sec.primary)
assert it_sec.primary == 1u64
Brief description of the process:
it_sec = idx_table_b.find(2u64)
: Looks up the value2u64
in the secondary index and returns theSecondaryIterator
type resultit_sec
.table.update_b(it_sec, 22u64, payer)
: This line of code implements the update function and updates the value ofb
to22u64
.it_sec = idx_table_b.find(22u64)
: Looks up the new secondary index.assert assert it_sec.is_ok()
: Used to confirm whether the secondry index has been updated successfully.assert it_sec.primary == 1u64
: Used to confirm whether the primary index is correct.
The update_b
code is generated by the compiler and is shown below:
def update_b(self, it: SecondaryIterator, b: u64, payer: Name) -> None:
# Update the `b` secondary index.
self.idx_b.update(it, b, payer)
# Find the primary index.
it_primary = self.table.find(it.primary)
check(it_primary.is_ok(), "primary iterator not found")
# Get the value for the primary index.
value: A = it_primary.get_value()
# Update the value for the primary index.
value.b = secondary[u64](b)
self.table.update(it_primary, value, payer)
From the code, it is apparent that when updating the secondary index, the corresponding value in the primary index will also be updated.
Deleting the secondary index
@action('testremove')
def test_remove(self):
payer = n"hello"
table = A.new_table(n"hello", n"")
item = A(1u64, 2u64, 3u128)
table.store(item, payer)
idx_table_b = table.get_idx_table_by_b()
it_sec = idx_table_b.find(2u64)
assert it_sec.primary == 1u64
it = table.find(it_sec.primary)
table.remove(it)
it_sec = idx_table_b.find(2u64)
assert not it_sec.is_ok()
print('done!')
In this example, first call store
to store an object A with the primary index of 1u64
and the first secondary index value of 2u64
. Then query 2u64
and confirm that it_sec.primary == 1u64
. Next, call remove
to delete the data with the primary index of 1u64
. Finally, query 2u64
again and confirm that the element has been deleted.
# test.py
def test_remove_secondary():
t = init_db_test('db_example8')
ret = t.push_action('hello', 'testremove', {}, {'hello': 'active'})
t.produce_block()
logger.info("++++++++++%s\n", ret['elapsed'])
Compilation:
python-contract build db_example/db_example8.codon
Running the test:
ipyeos -m pytest -s -x test.py -k test_remove_secondary
Using the API to perform secondary index queries on a table
In the db_example8.codon
example, two secondary indexes are defined, with the types u64
and u128
, respectively. The get_table_rows
API also supports finding corresponding values through secondary indexes.
def test_example9():
t = init_db_test('db_example8')
ret = t.push_action('hello', 'test', {}, {'hello': 'active'})
t.produce_block()
logger.info("++++++++++%s\n", ret['elapsed'])
# find by secondary u64
rows = t.get_table_rows(True, 'hello', '', 'mytable', 22, '', 10, 'i64', '2')
logger.info("++++++++++%s", rows['rows'])
assert rows['rows'][0]['b'] == 22
# find by secondary u128
rows = t.get_table_rows(True, 'hello', '', 'mytable', '3', '', 10, 'i128', '3')
logger.info("++++++++++%s", rows['rows'])
assert rows['rows'][0]['c'] == '3'
Explanation of the code below:
To find the value in the table through the second index b
:
rows = t.get_table_rows(True, 'hello', '', 'mytable', 22, '', 10, 'i64', '2')
Here, i64
is the index type of b
, and 2
is zero-based index corresponding to the index.
To find the value in the table through the second index c
:
rows = t.get_table_rows(True, 'hello', '', 'mytable', '3', '', 10, 'i128', '3')
Here, i128
is the index type of c
. Note that the value 3
in the lowerbound
parameter is the value of the secondary index. Since u128
has exceeded the range of 64-bit integers, a numeric string is used to represent it. Finally, the last parameter 3
is the corresponding index number.
The results of running the above test code are as follows:
++++++++++[{'a': 1, 'b': 22, 'c': '3'}, {'a': 111, 'b': 222, 'c': '333'}]
++++++++++[{'a': 1, 'b': 22, 'c': '3'}, {'a': 111, 'b': 222, 'c': '333'}]
Implementation principles of the database
The above code demonstrates the basic operations of the database. However, during the compilation process, some methods and classes are generated by the compiler. The following code displays the code generated by the compiler.
# db_example6.codon
from chain.contract import Contract
from chain.database import primary, secondary
from chain.database import IdxTable64, IdxTable128, Iterator
from chain.mi import MultiIndexBase
from chain.name import Name
@packer
class A(object):
a: database.primary[u64]
b: secondary[u64]
c: secondary[u128]
def __init__(self, a: u64, b: u64, c: u128):
self.a = primary[u64](a)
self.b = secondary[u64](b)
self.c = secondary[u128](c)
def get_primary(self) -> u64:
return self.a()
class MultiIndexA(MultiIndexBase[A]):
idx_b: IdxTable64
idx_c: IdxTable128
def __init__(self, code: Name, scope: Name, table: Name):
MultiIndexBase[A].__init__(code, scope, table)
idx_table_base = table.value & 0xfffffffffffffff0u64
self.idx_b = IdxTable64(0, code, scope, Name(idx_table_base | u64(0)))
self.idx_c = IdxTable128(1, code, scope, Name(idx_table_base | u64(1)))
def store(self, item: A, payer: Name) -> Iterator[A]:
id: u64 = item.get_primary()
it = self.table.store(item, payer)
self.idx_b.store(id, item.b(), payer)
self.idx_c.store(id, item.c(), payer)
return it
def update(self, it: Iterator[A], item: A, payer: Name):
self.table.update(it, item, payer)
primary = item.get_primary()
secondary = item.b()
it_secondary, old_secondary = self.idx_b.find_by_primary(primary)
if not secondary == old_secondary:
self.idx_b.update(it_secondary, secondary, payer)
secondary = item.c()
it_secondary, old_secondary = self.idx_c.find_by_primary(primary)
if not secondary == old_secondary:
self.idx_c.update(it_secondary, secondary, payer)
def remove(self, it: Iterator[A]):
sec_it, _ = self.idx_b.find_by_primary(it.get_primary())
self.idx_b.remove(sec_it)
sec_it, _ = self.idx_c.find_by_primary(it.get_primary())
self.idx_c.remove(sec_it)
self.table.remove(it)
def remove(self, primary: u64):
it = self.table.find(primary)
if it.is_ok():
self.remove(it)
def get_idx_table_by_b(self) -> IdxTable64:
return self.idx_b
def get_idx_table_by_c(self) -> IdxTable128:
return self.idx_c
def update_b(self, it: SecondaryIterator, b: u64, payer: Name) -> None:
self.idx_b.update(it, b, payer)
it_primary = self.table.find(it.primary)
check(it_primary.is_ok(), "primary iterator not found")
value: A = it_primary.get_value()
value.b = secondary[u64](b)
self.table.update(it_primary, value, payer)
def update_c(self, it: SecondaryIterator, c: u128, payer: Name) -> None:
self.idx_c.update(it, c, payer)
it_primary = self.table.find(it.primary)
check(it_primary.is_ok(), "primary iterator not found")
value: A = it_primary.get_value()
value.c = secondary[u128](c)
self.table.update(it_primary, value, payer)
@extend
class A:
def new_table(code: Name, scope: Name):
return MultiIndexA(code, scope, n"mytable")
@contract(main=True)
class MyContract(Contract):
def __init__(self):
super().__init__()
@action('test')
def test(self):
payer = n"hello"
table = A.new_table(n"hello", n"")
item = A(1u64, 2u64, 3u128)
table.store(item, payer)
idx_table_b = table.get_idx_table_by_b()
it = idx_table_b.find(2u64)
print("++++++it.primary:", it.primary)
assert it.primary == 1u64
idx_table_c = table.get_idx_table_by_c()
it = idx_table_c.find(3u128)
print("++++++it.primary:", it.primary)
assert it.primary == 1u64
This example demonstrates a scenario where there are two secondary indexes. Only the name table
was changed to packer
. In this case, the compiler will not generate any code related to the database.
By comparison, it is apparent that the compiler generates a class named MultiIndexA
, which inherits from the MultiIndexBase
class defined in mi.codon
. This class has the following methods:
- def store(self, item: A, payer: Name) -> Iterator[A]
- def update(self, it: Iterator[A], item: A, payer: Name)
- def remove(self, it: Iterator[A])
- def get_idx_table_by_b(self) -> IdxTable64:
- def get_idx_table_by_c(self) -> IdxTable128:
- def update_b(self, it: SecondaryIterator, b: u64, payer: Name) -> None:
- def update_c(self, it: SecondaryIterator, c: u128, payer: Name) -> None:
In addition, the class A
is generated, along with the following additional methods:
get_primary
: retrieves the primary indexget_idx_table_by_b
: retrieves the table indexed byb
, returning an instance of theIdxTable64
classget_idx_table_by_c
: retrieves the table indexed byc
, returning an instance of theIdxTable128
classnew_table
Test code:
def test_example6():
t = init_db_test('db_example6')
ret = t.push_action('hello', 'test', {}, {'hello': 'active'})
t.produce_block()
logger.info("++++++++++%s\n", ret['elapsed'])
Compilation:
python-contract build db_example/db_example6.codon
Running the test:
ipyeos -m pytest -s -x test.py -k test_example6
Output:
++++++it.primary: 1
++++++it.primary: 1
Summary
The data storage functionality in EOS is relatively comprehensive, and the secondary index table function makes data querying very flexible. This chapter provides a detailed explanation of the code for table operations, including adding, deleting, modifying, and querying. This chapter contains a lot of content and requires some time to digest. You can try to modify the examples and run them to gain a better understanding of the content covered in this chapter.