1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
|
* 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
|