-
Notifications
You must be signed in to change notification settings - Fork 12
v0.8 Procedural Language
In this release, we are upgrading Kuneiform with three goals in mind:
- Enhanced logic, control flow, and arithmetic
- Easier composability between schemas (not relying on extensions)
- Co-location of calculations (to improve efficiency, especially for operations spanning many DBs)
To avoid confusion with actions, Kuneiform will also contains a procedure
, similar to actions. We won't make any breaking changes to actions, but we will plan on deprecating them in favor of procedures.
// returns no data
procedure my_proc($id int) public {}
// returns data
procedure my_proc($id int) public returns table($id int) {}
Procedures will not be able to call actions, and actions will not be able to call procedures. I would consider what we are building in procedures as "our second iteration" of actions, now that we know a lot more.
We can make procedures in plpgsql private using:
REVOKE EXECUTE ON FUNCTION my_schema.my_function() FROM PUBLIC;
To avoid confusion with extension syntax, we will also define a new way to import. The old use
syntax will be kept around for importing extensions into actions:
import xdbid as alias;
Users will be able to specify procedures from imports:
database db1; // dbid: xabc
table users public {}
procedure create_user($name text) public {}
database db2;
import xabc as db1;
action do_something($name text) public {
db1.create_user($name);
}
We will support the following types:
- BLOB: equivalent of Golang's []byte
- uint256: for storing token information from smart contracts
- uuid: for primary keys. users are currently storing them as 36 char strings
- boolean
- some sort of fixed point variable (if possible? this could seriously help Truflation, but there are some really hard technical limitations to overcome here)
After deliberation, I have decided that we have no choice but to support composite types in Kwil. Composite types are simply objects with fields that are other types:
type User {
name text,
age int
}
type School {
name text,
address text,
students User[]
}
// register_user registers a user with a school
procedure register_user(user User, school name) public {}
// assign constant
procedure other_proc() public {
$user = User{
name: text,
}
}
With first-class object support, we will also need to adopt a coherent object serialization scheme. We can use our current RLP system, however NEAR's Borsch seems to have some merits here as well. We will have to serialize this to a format postgres understands, but we should not expose this to the client as to avoid SQL injection. We will have to perform validation on the incoming data before passing to postgres.
Right now, Kwil supports the @caller
contextual variable. It will also support:
-
@txid
: the Kwil transaction id that called the procedure. If not a transaction, this will be empty. -
@height
: the block height that the transaction exists in. If not a transaction, the node will supply any height it wishes.
The procedural language can capture selects by returning them from a procedure:
procedure my_proc() public returns table(name text, age int) {
return SELECT name, age FROM users;
}
This can be accessed in other statements, through either loops, selects, or joins:
procedure proc2() public {
for $row in my_proc() {
// can access $row.name, $row.age, etc.
};
}
Procedures that return type table()
can be selected.
Should we support this for any procedure? I think we can.
procedure proc2() public {
select age from my_proc();
}
Procedures that return type table()
can be joined against.
procedure proc2() public {
select * from tbl
INNER JOIN my_proc() as p
ON tbl.id = p.id;
}
Basic values themselves can be returned. Only one value can be returned
procedure my_proc() public returns int {
$my_int int := 10;
return $my_int
}
To select individual values, we can do the following. This will exit on the first iteration, regardless of the amount of rows returned. If no rows are returned, it will raise an error.
procedure my_proc() public returns int {
for $row in SELECT id FROM users; {
return $row.id;
};
error('no row returned');
}
Composite types can also be assigned and returned:
type User {
name text,
age int
}
procedure my_proc() public returns User[] {
$user1 User := User{
name: 'satoshi',
age: '40'
}
$user2 User := User{
name: 'Yaiba',
age: '420'
}
return [$user1, $user2]
}
If, else, and elseif will all be supported:
procedure my_proc($id int) public {
if $id == 1 {
// smthn
} else if $id == 2 {
// smthn2
} else {
// smthn3
};
}
All types can also be made arrays. Arrays can be iterated over:
procedure my_proc($stmts text[]) public {
for $stmt in $stmts {
// $stmt is type text
}
}
I have already covered for loop usage for procedures returning tables and arrays. For loops can also be done for numbers:
procedure my_proc($end int) public {
// $end == 5
for $i in 1:$end {
// $i == 1,2,3,4,5
}
}