* About Karuta
Just like PostgreSQL and MySQL have SQL, Sakura has Karuta, a prolog-like language targetting the BEAM. [[karuta][https://github.com/dont-rely-on-nulls/karuta]] is the programming language that presents the different sublanguages to manage an RDBMS.
We believe that what E. F. Codd envisioned was a stack of sublanguages that are embedded in a more powerful language, that expand it's abstraction scope as you move up the stack, trying to provide guarantees at each level. A data retrieval language should halt and has no business into being Turing Complete, but a predicate procedure language might. In light of this, Sakura is supposed to provide a common DSL that can differentiate between such sublanguages and execute things accordingly.
The approach is that everything is a relation itself. So a constraint for instance is expressed as a relation less_than over NxN instead of simply being an operator '<'.
* Sublanguages
Here's an overview of the sublanguages and how they are manifested in Karuta:
** DRL (Data Retrieval Language)
- Pure querying, total/terminating subset;
- No user recursion with no self references.
** ICL (Integrity Control Language)
- Integrity rules (key, FK-like, checks, assertions, aggregates-for-integrity).
- Semantically predicates, but with enforcement timing (on write/commit), not free query operators.
- Implemented in =lib/icl/= (=Ast=, =Parser=, =Executor=).
** DCL (Database Control Language)
- Branch management: create/checkout branches, query HEAD and tip hashes.
- Merge: 3-way merge of two branch tips with configurable conflict strategy (=PreferLeft=, =PreferRight=, =RevertToAncestor=).
- Implemented in =lib/dcl/= (=Ast=, =Parser=, =Executor=).
- Example commands:
#+begin_src sexp
(CreateBranch (name "feature") (tip "<hash>"))
(Checkout "feature")
(GetHead)
(GetBranchTip "main")
(UpdateBranchTip (name "main") (tip "<hash>"))
(Merge (left "main") (right "feature") (strategy PreferLeft))
#+end_src
** DML (Data Manipulation Language)
- Insert/update/delete;
- Delta semantics, can trigger constraint evaluation.
** DDL (Data Definition Language)
- Define relations/views (ephemeral relations)/indexes/types/modules/predicate signatures and capabilities.
** TCL (Transaction/Control Language)
- Begin/commit/rollback, isolation/consistency controls, maybe savepoints.
** PPL (Procedural Predicate Language)
- Stored procedures/functions, recursion allowed, orchestration logic.
- Can call DRL/DML/ICL/DCL-aware operations, but not all PPL predicates are DRL-safe.
** ACL (Authorization/Policy Language) (optional early, important later)
- Who can query/mutate/define; row/predicate-level access policies.
* Prototype
#+begin_src prolog
% DDL
:- schema[users] {
:- signature[people-sig] {
person[_, _].
another-person[string, natural].
valid_age[natural].
a-procedure[int, int].
a-view[_, _, _].
}.
:- module[people, people-sig] {
% This is using the arity directly, because the types are being specified in the signature.
% These Sakura-provided type extensions after '::' are the what informs Sakura through metadata on connection which type of Karuta predicate this is used as in Karuta.
another-person/2 :: sakura:persisted
another-person[Name, FavoriteNumber].
person[string, natural] :: sakura:persisted
person[Name, Age] :- valid_age[Age].
% ICL
another-constraint[list[person], string, natural] :: sakura:constraint
another-constraint[[], _, _].
another-constraint[[person[Name, Age]|Rest], Whatever1, Whatever2] :-
(eq[Name, Whatever1] ; !eq[Name, Whatever1], !eq[Age, Whatever2]),
another-constraint[Rest, Whatever1, Whatever2].
% ICL
my-predicate[int, string, nat] :: sakura:constraint
my-predicate[Arg1, Arg2, Arg3] :-
another-module:my-predicate[Arg1, Arg2, Arg3],
on[insert, update],
timing[immediate].
% ICL
valid_age/1 :: sakura:constraint
valid_age[Age] with [karuta, sakura] :-
nat[Age],
greater_or_equal[Age, 0],
% DRL
aggregate[person, 2, Tuples],
another-constraint[Tuples, "lemos", 26],
on[insert, update],
timing[immediate],
target[age].
% PPL
a-procedure/2 :: sakura:stored
a-procedure[Arg1, Arg2] :-
eq[Arg1, Arg2].
% DDL
a-view[string, natural, natural] :: sakura:ephemeral
a-view[Name, Age, FavoriteNumber] :-
person[Name, Age],
another-person[Name, FavoriteNumber].
}.
}.
my-predicate[users:people:person, string, nat] :: signature
my-predicate[Arg1, Arg2, Arg3] :-
another-module:my-predicate[Arg1, Arg2, Arg3].
main[Arg1, Arg2] :-
karuta:plus[Arg1, Arg2, Out],
karuta:aggregate[numbers:inc, 2, Tuples],
% Project directive should give us these configurations
project:sakura[Secret, Port, Host],
project:sakura:config[Users1, Commit],
sakura:connect[Secret, Port, Host, Connection],
sakura:database[Connection, Users1, Commit, DB1Handle],
% TCL
sakura:transaction[TXContext, DB1Handle],
% DML
sakura:insert[users:people:person["Toad", 18], TXContext],
(sakura:error?[TXContext, Reason], sakura:rollback[TXContext], print["We rolled back: ~s!", Reason]) ;
(sakura:new[TXContext, [name], Difference1],
sakura:commit[TXContext, DB2Handle],
sakura:delta[DB1Handle, DB2Handle, Difference2],
% DRL
@relation1[Arg2, Out]).
#+end_src