aboutsummaryrefslogtreecommitdiffstats
path: root/docs/code.org
blob: 30ff9c9eb66ced0495304d114d2f4d5b60daf768 (plain) (blame)
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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
#+TITLE: Code Documentation

* Overview

Domino is a content-addressed, immutable relational database engine written in Erlang. The system uses cryptographic hashing (SHA-256) and Merkle trees to provide version control, deduplication, and efficient synchronization capabilities.

* Architecture

** Design Patterns

- *Content-Addressed Storage* :: All data (attributes, tuples, relations, databases) are identified by their cryptographic hash
- *Immutable Data Structures* :: All operations create new versions rather than modifying existing data
- *Merkle Trees* :: Relations and databases use merkle trees for efficient diffing and synchronization
- *Volcano Iterator Model* :: Query execution uses lazy evaluation with process-based iterators
- *Actor Model* :: Iterators are implemented as independent Erlang processes
- *Transactional Storage* :: Mnesia provides ACID guarantees for all mutations

* Module Structure

** operations.erl

The core database operations module and main API.

*** Public API

**** Database & Relation Management
- =setup/0= :: Initialize Mnesia schema and create all tables
- =create_database/1= :: Create a new database state
- =create_relation/3= :: Add a relation to a database with schema
- =get_relations/1= :: List all relation names in a database
- =get_relation_hash/2= :: Get relation hash by name

**** Tuple Operations
- =create_tuple/3= :: Insert a tuple into a relation (transactional)
- =retract_tuple/3= :: Remove a tuple from a relation (default: transactional)
- =retract_tuple/4= :: Remove a tuple with optional transaction control

**** Relation Operations
- =clear_relation/2= :: Remove all tuples from a relation (default: transactional)
- =clear_relation/3= :: Clear relation with optional transaction control
- =retract_relation/2= :: Remove a relation from database (default: transactional)
- =retract_relation/3= :: Remove relation with optional transaction control

**** Iterator Pattern (Volcano Model)
- =get_tuples_iterator/2= :: Create lazy iterator process for relation tuples
- =next_tuple/1= :: Pull next tuple from iterator (returns ={ok, Tuple} | done=)
- =close_iterator/1= :: Terminate iterator process
- =collect_all/1= :: Helper to consume entire iterator into list

**** Utility Functions
- =hash/1= :: SHA-256 hash of term
- =hashes_from_tuple/1= :: Extract all tuple hashes from a relation

** relation.erl

Simple helper module for building tuples and relations conceptually.

*** Public API
- =new_attribute/2= :: Create attribute map: =#{name => Name, value => Value}=
- =new_tuple/1= :: Convert list of attributes to map
- =new_relation/1= :: Convert list of tuples to sets:set

** poc.erl

Proof-of-concept demonstrating the Volcano Iterator Model for query execution.

*** Key Concepts
- Content-addressed storage simulation using in-memory maps
- Lazy iterator chain: Scan → Select → Project
- Message-passing protocol for tuple streaming
- Automatic deduplication via hashing

** main.erl

OTP application entry point. Starts Mnesia on application startup.

* Data Structures

All core records are defined in =include/operations.hrl=.

** attribute
#+BEGIN_SRC erlang
-record(attribute, {
    hash,    % SHA-256 hash of value
    value    % Actual value (any Erlang term)
}).
#+END_SRC

** tuple
#+BEGIN_SRC erlang
-record(tuple, {
    hash,           % SHA-256 hash of entire tuple
    relation,       % Relation name (atom)
    attribute_map   % #{attr_name => value_hash}
}).
#+END_SRC

** relation
#+BEGIN_SRC erlang
-record(relation, {
    hash,    % hash({name, schema, tree_root})
    name,    % Relation name (atom)
    tree,    % Merkle tree of tuple hashes
    schema   % Schema definition map
}).
#+END_SRC

** database_state
#+BEGIN_SRC erlang
-record(database_state, {
    hash,       % Merkle tree root hash
    name,       % Database name (atom)
    tree,       % Merkle tree of relations
    relations,  % #{relation_name => relation_hash}
    timestamp   % Creation/modification timestamp
}).
#+END_SRC

* Data Flow

** Creating a Tuple

1. Store each attribute value separately (enables deduplication)
2. Build attribute_map with hashes: =#{name => AttrHash1, age => AttrHash2}=
3. Create tuple record with hash
4. Insert tuple hash into relation's merkle tree
5. Compute new relation hash from tree root
6. Update database merkle tree
7. Return updated database and relation

All operations occur within a Mnesia transaction.

** Querying Tuples

1. Lookup relation hash from database
2. Read relation from Mnesia
3. Extract tuple hashes from merkle tree
4. Spawn iterator process with hash list
5. Client pulls tuples via message passing
6. Iterator resolves: hash → tuple → attribute hashes → actual values
7. Return materialized tuple map

* Usage Examples

** Basic Workflow

#+BEGIN_SRC erlang
%% Setup
operations:setup().

%% Create database
DB = operations:create_database(my_app).

%% Create relation with schema
{DB1, _Rel} = operations:create_relation(DB, users, #{
    name => string,
    age => integer
}).

%% Insert tuples
{DB2, _} = operations:create_tuple(DB1, users, #{name => "Alice", age => 30}).
{DB3, _} = operations:create_tuple(DB2, users, #{name => "Bob", age => 25}).

%% Query tuples
Iterator = operations:get_tuples_iterator(DB3, users).
Tuples = operations:collect_all(Iterator).
%% Returns: [#{name => "Alice", age => 30}, #{name => "Bob", age => 25}]

%% Remove tuple
{ok, RelHash} = operations:get_relation_hash(DB3, users).
[TupleHash|_] = operations:hashes_from_tuple(RelHash).
{atomic, {DB4, _}} = operations:retract_tuple(DB3, RelHash, TupleHash).

%% Clear relation (remove all tuples, keep schema)
{atomic, {DB5, _}} = operations:clear_relation(DB4, RelHash).

%% Remove relation entirely
{atomic, DB6} = operations:retract_relation(DB5, users).
#+END_SRC

** Iterator Protocol

#+BEGIN_SRC erlang
Iterator = operations:get_tuples_iterator(DB, users).

%% Pull tuples one by one
case operations:next_tuple(Iterator) of
    {ok, Tuple} -> process_tuple(Tuple);
    done -> io:format("No more tuples~n")
end.

%% Or collect all at once
AllTuples = operations:collect_all(Iterator).

%% Clean up
operations:close_iterator(Iterator).
#+END_SRC

* Key Implementation Details

** Immutability & Versioning

Every modification creates a new hash. Old versions remain accessible via their hash. Database evolution is tracked via timestamp and hash chain.

** Deduplication

Identical attribute values are stored once (content-addressed). Example: 1000 users with age=30 store the value "30" only once.

** Transaction Semantics

All mutations are wrapped in =mnesia:transaction/1=. Dirty reads are used for immutable data lookups.

** Merkle Tree Strategy

- Relations store tuple hashes as keys: ={TupleHash, <<>>}=
- Databases store relation names → hashes: ={RelationName, RelationHash}=
- Enables efficient diff computation between versions
- Foundation for distributed synchronization

** Iterator Pattern

- Decouples query execution from data access
- Enables pipelined query operators (future: select, project, join)
- Memory bounded (doesn't materialize entire relation)
- Process-based concurrency via Erlang actors

** Schema Flexibility

Schema stored as map (e.g., =#{name => string, age => integer}=). Currently not enforced, but provides foundation for future domain validation.

* Dependencies

- =merklet= (2.0.0) :: Merkle tree implementation for set reconciliation
- =mnesia= :: Distributed database for persistent storage
- =crypto= :: SHA-256 hashing

* Testing

The test suite (=test/operations_test.erl=) validates:

- Functional correctness (CRUD operations)
- Hash determinism and consistency
- Transactional integrity
- Iterator protocol compliance
- State evolution tracking
- Cross-database consistency

Run tests with:

#+BEGIN_SRC sh
rebar3 eunit
#+END_SRC

* Current State

** Implemented
- Core data model (attribute, tuple, relation, database)
- Content-addressed storage with SHA-256
- Immutable versioning
- Merkle tree integration
- Iterator-based lazy evaluation
- Transactional CRUD operations
- Comprehensive test suite

** Not Yet Implemented
- Relational algebra operators (σ, π, ⋈, ∪, ∩, −)
- Schema validation and type checking
- Infinite relations (aleph₀)
- Join indexing
- Query optimization
- Distributed synchronization
- REPL/query console