FISCO BCOS CRUD Usage Guide¶
Author : Liao Feiqiang | FISCO BCOS Core Developer
This article will introduce the CRUD function of FISCO BCOS to help developers develop blockchain applications more efficiently and easily。
Why Design CRUD Features?¶
In FISCO BCOS 1.0, nodes use MPT data structure to store data locally through LevelDB, which is limited by the size of the local disk, and when the volume of business increases, the data will expand dramatically, and data migration is also very complex, bringing greater cost and maintenance difficulty to data storage。
In order to break through the capacity and performance bottlenecks, FISCO BCOS 2.0 has been redesigned for the underlying storage to achieve distributed storage, bringing capacity and performance improvements。FISCO BCOS 2.0 designs a CRUD thanks to the use of a library table structure for distributed storage(Create Add, Read, Update Update, and Delete Delete)Let the interface be more natural。CRUD’s library-table-oriented development approach is in line with business development habits and also provides another option for business development(Previously, only Solidity contracts could be used), thus making blockchain application development more convenient。
What are the advantages of CRUD??¶
The core design idea of CRUD is to provide a blockchain application development specification for SQL programming。The benefits are obvious, mainly in two liters and two drops。
Improve the efficiency of development block chain application¶
CRUD is similar to the traditional business SQL programming development model, greatly reducing the difficulty of contract development。Developers use contracts as stored procedures in the database to convert the read and write operations of blockchain data into table-oriented read and write operations, which is simple and easy to use, greatly improving the efficiency of developing blockchain applications。
Improve the performance of blockchain applications¶
The underlying logic of CRUD is implemented based on pre-compiled contracts, and its data storage adopts distributed storage. Transactions generated by reading and writing data are no longer executed by slow EVM virtual machines, but by high-speed pre-compiled contract engines, thus improving the efficiency of contract data reading and writing, making blockchain applications developed based on CRUD have higher performance。
Reduce contract maintenance and upgrade complexity¶
The logic of CRUD contracts is separated from storage, so that developers only need to care about the core business logic, and data is no longer bound to specific contracts, making it easier to upgrade contracts。When the contract logic needs to be changed, a new contract can be deployed. The new contract can read and write the tables and data created by the old contract。
Reduce migration costs for SQL business¶
A large part of traditional business applications manage data through database tables, and CRUD contracts can smoothly migrate business applications designed for SQL to the blockchain, reducing business migration costs。
How to use CRUD?¶
Introducing the two-liter, two-drop advantages of CRUD, I’m sure you’re very concerned about how CRUD is specifically used? In fact, CRUD is simple and easy to use. At present, CRUD functions can be used in two ways, namely CRUD contract and SDK CRUD Service interface。
CRUD Contracts¶
The medium for developers to interact with the blockchain is mainly smart contracts, and the CRUD function will naturally be integrated into smart contracts。The integration method is very light, and the Solidity contract only needs to introduce the Table.sol abstract interface contract file provided by FISCO BCOS。Table.sol contains a smart contract interface dedicated to distributed storage. The interface is implemented on the blockchain node. You can create tables and add, delete, and query tables。Contracts that introduce this abstract interface are called CRUD contracts to distinguish them from Solidity contracts that do not reference the interface。
The Table.sol abstract interface contract file includes the following abstract contract interfaces, which are described separately below。
TableFactory Contract¶
Used to create and open a table, its fixed contract address is 0x1001, and the interface is as follows:
| Interface | Function | Parameters | Return value |
|---|---|---|---|
| createTable(string ,string, string) | Create Table | Table name, primary key name (currently only a single primary key is supported), and other field names of the table (separated by commas) | The error code (int256) is returned. For details, see the following table |
| opentTable(string) | Open Table | Table Name | Returns the address of the contract table. If the table name does not exist, an empty address is returned |
The createTable interface returns:
| error code | Description |
|---|---|
| 0 | Creation successful |
| -50000 | User does not have permissions |
| -50001 | Create table name already exists |
| -50002 | Table name exceeds 48 characters |
| -50003 | valueField length exceeds 64 characters |
| -50004 | valueField total length exceeds 1024 characters |
| -50005 | keyField length exceeds 64 characters |
| -50007 | Duplicate field exists |
| -50007 | Illegal character in field |
| 其他 | Other errors encountered while creating |
Entry Contract¶
Entry represents a record object, and an Entry object represents a row of records. Its interface is as follows:
| Interface | Function | Parameters | Return value |
|---|---|---|---|
| set(string, int) | Setting Fields | Field name, field value | void |
| set(string, string) | Setting Fields | Field name, field value | void |
| set(string, address) | Setting Fields | Field name, field value | void |
| getInt(string) | Get Field Value | Field Name | void |
| getString(string) | Get Field Value | Field Name | void |
| getBytes64(string) | Get Field Value | Field Name | void |
| getBytes32(string) | Get Field Value | Field Name | void |
| getAddress(string) | Get Field Value | Field Name | void |
Entries contract¶
Entries are record collection objects. Entries are used to store Entry objects. Their interfaces are as follows:
| Interface | Function | Parameters | Return value |
|---|---|---|---|
| get(int) | Gets the Entry for the specified index | Index of Entries | Address of Contract Entry |
| size() | Get the size of Entries | None | Size of Entries(int256) |
Condition contract¶
The filter object specified when querying, updating, and deleting records. Its interface is as follows:
| Interface | Function | Parameters | Return value |
|---|---|---|---|
| EQ(string, int) | equality condition | Field name, field value | void |
| EQ(string, string) | equality condition | Field name, field value | void |
| NE(string, int) | unequal conditions | Field name, field value | void |
| NE(string, string) | unequal conditions | Field name, field value | void |
| GT(string, int) | Greater than condition | Field name, field value | void |
| GE(string, int) | Greater than or equal to condition | Field name, field value | void |
| LT(string, int) | less than condition | Field name, field value | void |
| LE(string, int) | less than or equal to condition | Field name, field value | void |
| limit(int) | Record Selection Criteria | How many records are returned | void |
| limit(int, int) | Record Selection Criteria | Record start line position, how many records are returned | void |
Table Contracts¶
The object used to add, delete, modify, and query a table. Its interfaces are as follows:
| Interface | Function | Parameters | Return value |
|---|---|---|---|
| select(string, Condition) | Query data | Primary key value, filter condition object | Returns the address of Entris |
| insert(string, Entry) | Insert Data | Primary Key Value, Record Object | Returns the number of rows affected by insert |
| update(string, Entry, Condition) | Update data | Primary key value, record object, filter condition object | Returns the number of rows affected by update |
| remove(string, Condition) | Delete Data | Primary key value, filter condition object | Returns the number of rows affected by remove |
| newEntry() | Creating an Entry Object | None | Returns the new Entry contract address |
| newCondition() | Create a Condition object | None | Returns the new Condition contract address |
With the above understanding of the Table.sol abstract interface contract, the CRUD contract can now be formally developed。Taking the official CRUD contract TableTest.sol as an example, this paper introduces the trilogy of its development:
Step 1: Introduce Table.sol¶
Place the Table.sol contract in the TableTest.sol peer directory and introduce Table.sol in the TableTest.sol contract file. The code is as follows:
import "./Table.sol";
Step 2: Create the table¶
In the TableTest.sol contract file, the core code for creating a table is as follows:
/ / Create a TableFactory object whose fixed address on the blockchain is 0x1001
TableFactory tf =TableFactory(0x1001);
/ / Create the t _ test table. The primary key of the table is name, and the other fields are item _ id and item _ name
int count =tf.createTable("t_test", "name","item_id,item_name");
/ / Check whether the creation is successful
if(count >= 0)
{
// success
}
Note:
-createTable execution principle: After createTable is successfully executed, it will be displayed in the blockchain system table _ sys _ tables _(The blockchain startup automatically creates the table, specifically recording the information of all tables in the blockchain)The table information for t _ test is inserted into the table name, primary key name, and other field names, but the table is not formally created。When adding, deleting, and modifying the t _ test table, it first determines whether the t _ test table exists. If it does not exist, it queries the _ sys _ tables _ table to obtain information about the t _ test table. If the query contains information about the t _ test table, it creates the table. Otherwise, the execution fails。If the t _ test table exists, add, delete, modify, and query operations continue。
This step is optional: for example, if the new contract only reads and writes the table created by the old contract, you do not need to create the table。
Step 3: CRUD the table¶
In the TableTest.sol contract file, insert the record core code as follows:
TableFactory tf =TableFactory(0x1001);
/ / Open the created t _ test table
Table table =tf.openTable("t_test");
/ / Check whether the table is an empty address
if(table == address(0x0))
{
// error
}
/ / Create an Entry object, that is, create an empty record
Entry entry = table.newEntry();
/ / Set the Entry object, that is, set the field value of the record
entry.set("name", name);
entry.set("item_id",item_id);
entry.set("item_name",item_name);
/ / Call the insert method of Table to insert records
/ / Return the number of records affected by the insertion. If the value is 1, the insertion is successful. Otherwise, the insertion fails
int count = table.insert(name,entry);
The core code of the query record is as follows:
TableFactory tf =TableFactory(0x1001);
Table table =tf.openTable("t_test");
/ / Check whether the table is an empty address
if(table == address(0x0))
{
// error
}
/ / Create a Condition object. If the condition is empty, it means no filtering. You can also use conditional filtering as needed
Condition condition =table.newCondition();
/ / Call the select method of Table to query records and obtain the record collection
Entries entries =table.select(name, condition);
/ / get the size of the record collection
int size = entries.size();
bytes32[] memoryuser_name_bytes_list = new bytes32[](uint256(size));
int[] memory item_id_list = newint[](uint256(size));
bytes32[] memoryitem_name_bytes_list = new bytes32[](uint256(size));
/ / Traverse the record collection
/ / Store the values of the three fields of the record into three arrays, which is convenient to return the data of the query
for(int i = 0; i< size; ++i) {
/ / Get the records in the record collection based on the index
Entry entry = entries.get(i);
/ / Query the field value based on the field name of the record
/ / Note that the types of field values are different and the corresponding get method needs to be selected
user_name_bytes_list[uint256(i)] =entry.getBytes32("name");
item_id_list[uint256(i)] =entry.getInt("item_id");
item_name_bytes_list[uint256(i)] =entry.getBytes32("item_name");
}
The core code of the update record is as follows:
TableFactory tf =TableFactory(0x1001);
Table table =tf.openTable("t_test");
/ / Check whether the table is an empty address
if(table == address(0x0))
{
// error
}
Entry entry = table.newEntry();
entry.set("item_name",item_name);
Condition condition =table.newCondition();
/ / Set filter conditions
condition.EQ("name",name);
condition.EQ("item_id",item_id);
/ / Call the update method of Table to update the record
/ / The number of records affected by the update. If the value is greater than 0, the update succeeds. Otherwise, the update fails
int count = table.update(name, entry,condition);
The core code for deleting records is as follows:
TableFactory tf =TableFactory(0x1001);
Table table =tf.openTable("t_test");
/ / Check whether the table is an empty address
if(table == address(0x0))
{
// error
}
Condition condition =table.newCondition();
condition.EQ("name",name);
condition.EQ("item_id",item_id);
/ / Call the remove method of Table to delete records
/ / Returns the number of records affected by deletion. If the value is greater than 0, the deletion is successful. Otherwise, the deletion fails
int count = table.remove(name,condition);
SDKCRUD Service Interface¶
Through the CRUD contract, we can see that as long as the contract method involves data read and write operations, first open the corresponding table, and then call the read and write related interfaces to read and write blockchain data。At the same time, we note that the development of CRUD contracts is still inseparable from writing contracts, compiling contracts, deploying contracts, and finally calling contracts to implement related functions。So is there a more convenient, more concise way?For example, developers can read and write data on the blockchain without writing contracts or deploying contracts。The answer is obvious, we have achieved such extreme ease of use requirements。
FISCO BCOS SDK provides CRUD Service data link ports. These interfaces are implemented by calling a pre-compiled CRUD contract built into the blockchain, which is responsible for adding, deleting, modifying and checking user tables。The Java SDKCRUD service is implemented in the org.fisco.bcos.web3j.precompile.crud.CRUDService class (the Python SDK is similar to the Nodejs SDK). Its interface is as follows:
| Interface | Function | Parameters | Return value |
|---|---|---|---|
| createTable(Table table) | Create Table | Table Object(Need to set its table name, primary key field name and other field names。where the other field names are strings separated by commas) | error code |
| select(Table table, Condition condition) | Query data | Table object, Condition object | Entries object |
| insert(Table table, Entry entry) | Insert Data | Table object, Entry object | error code |
| update(Table table, Entry entry, Condition condition) | Update data | Table object, Entry object, Condition object | error code |
| remove(Table table, Condition condition) | Delete Data | Table object, Condition object | error code |
| desc(String tableName) | Query table information | Table Name | KeyField and valueField for tables |
The above interfaces cover the creation, viewing, addition, deletion, modification and query of tables。Users only need to call the SDK interface to complete the relevant operations, see the following address for specific examples gitee address view。
The call to the write interface will generate the equivalent transaction to the call to the CRUD contract interface, which will not be stored until the consensus node consensus is consistent。It is worth noting that using the CRUD Service interface, the FISCO BCOS console implements easy-to-use sql statement commands for each interface, Welcome to the following address experience。
Comparison of Two Kinds of CRUD Usage¶
Perhaps users have such doubts, CRUDService interface is so simple and easy to use, then we only rely on this group of interfaces to complete the blockchain business??In fact, it is not true, compared to the CRUD contract, its limitations are mainly in two points:
-Limited data access range: The CRUDService interface is very specific, and its essence is a set of read and write interfaces for blockchain user table data。So for non-user table data, this set of interfaces is powerless。For example, the state variable of the Solidity contract is not stored in a user-created table, and the data for the state variable cannot be queried through this set of interfaces。However, the CRUD contract is essentially a Solidity contract, except that an additional Table.sol contract file is introduced to give it CRUD functionality。Solidity state data and user table data can be manipulated through CRUD contracts。
Limited business processing capacity: Many blockchain businesses not only use blockchain data, but also need to design relevant contract logic to submit data to the blockchain only if the relevant conditions and verifications are met。
What are the Effective CRUD best practices?¶
Although the CRUD function is simple and easy to use, it also requires a clear understanding of how to use CRUD efficiently。
Clause 1: In most cases, please choose CRUD contract to develop blockchain applications¶
In the development of blockchain applications, if it is determined that the data is only on-chain data and the data is only stored in user tables and does not rely on contracts to execute relevant business logic, then consider using the CRUD Service interface。Otherwise, CRUD contracts are recommended。In addition, in order to facilitate development and debugging, you can always use the CRUD Service read interface (select and desc interface) or use the console corresponding to the relevant commands for data query。
Clause 2: Contents of Table.sol file cannot be modified¶
The Table.sol file defines abstract interfaces related to CRUD functions, and each interface blockchain has a corresponding concrete implementation。If the user modifies the file, it will cause problems such as transaction execution failure or abnormal function。
Clause 3: Primary key of CRUD user table is not unique¶
Users are accustomed to having unique primary key fields in relational databases, so the primary key in the default CRUD user table is also unique, which is not。When inserting multiple records, you can specify the same primary key。The primary key is not unique because the CRUD table stores the mapping from the primary key to the corresponding Entries, and then adds, deletes, and checks based on the primary key。Therefore, the CRUD interface is called(Insert, update, query, and delete records)The primary key value needs to be passed in(Not a primary key name)。In addition, the primary key value is too long, which will affect the efficiency of reading and writing. It is recommended not to set the primary key value too long。
Article 4: When using CRUD contract, the table field is too many, use array or struct to encapsulate the field parameters¶
Due to the limitations of the Solidity contract language, the number of local variables in a contract method must not exceed 16, otherwise there will be”Stacktoo deep, try removing local variables”Compilation Error。Therefore, for the case of more table fields, you can consider using arrays or struct encapsulation for multiple field parameters to reduce the number of local variables and make them meet the compilation conditions。
Clause 5: When using a CRUD contract, if table operations are involved in the method, the table needs to be opened first¶
TableFactory abstract contract has openTable(string)method, which returns the open table object。We may set the table object as a global variable of the contract for direct use the next time we operate the table。Note that this is an error operation, because each time the table is opened, the table object it gets is registered at a temporary address, which is no longer valid after the next block, so the table object cannot be set to be used as a global variable, but the table needs to be opened before the method needs to manipulate the table。In addition, because TableFactory is registered in the blockchain with the fixed address 0x1001, you can set the TableFactory object as a global variable without having to create the object every time。
Article 6: Using CRUD contracts, you can store data using a mixture of tables and state variables¶
The CRUD contract is a Solidity contract with CRUD functionality, so the state variable storage method before Solidity can still be used。When storing a variable value, it may be convenient to use a variable storage directly, so you can define several state variables and create several user tables for data storage。Table storage is recommended for structured data。
Clause 7: Adopt permission control to manage user tables¶
The logic of the CRUD contract is separated from the user table data, so the write operations of the user table (including insert, update, and delete operations) are no longer controlled by the restrictions of the contract interface。To prevent any account from being able to write user tables, it is recommended to use permission control to manage user tables (click Reference Permission Control for specific use), specify that a specific account has permission to write to the user table。