Branchy SQL — Versioned SQLite
Build a tiny "Git for relational data" on top of SQLite. Your program manages a single SQLite file with named branches — isolated views of the same tables. Writes on one branch do not affect any other branch until they are merged in.
You implement three interesting operations: branch, diff, and merge (three-way, with conflict detection). Everything else — PUT, DEL, GET, DUMP — is plumbing the evaluator uses to set up and inspect state.
Protocol
Your program is a long-running REPL. It reads commands one per line on stdin and writes responses on stdout, until EOF or QUIT. Flush after every response — the evaluator reads line-by-line and will hang if you buffer.
Commands
| Command | Response |
|---|---|
INIT <db-path> |
OK — open/create the SQLite file. A branch named main exists from the start. |
SCHEMA <table> <col1:type>,<col2:type>,... |
OK — declare a table. First column is the primary key. Types: INTEGER or TEXT. |
PUT <branch> <table> <pk> <col=val>,<col=val>,... |
OK — insert-or-update a row on <branch>. <pk> is the integer primary key. Non-PK columns listed in any order; omitted columns default to NULL. |
DEL <branch> <table> <pk> |
OK — delete the row with that pk from <branch> (no-op if missing). |
GET <branch> <table> <pk> |
col=val|col=val|... (excluding pk, columns in SCHEMA order) or NULL if the row does not exist on that branch. |
BRANCH <new> <from> |
OK — fork <new> from <from>'s current state. |
DIFF <a> <b> |
One line of JSON, then END. See Diff format below. |
MERGE <src> <dst> |
OK on success, or CONFLICT <table>:<pk> for the first conflicting row (sorted by (table, pk)) — in which case <dst> is left unchanged. |
DUMP <branch> |
All rows on <branch>, one per line as <table> <pk> <col=val>|<col=val>|..., sorted by (table, pk), terminated by END. |
QUIT |
Exit cleanly. No response. |
Diff format
DIFF a b emits the changes you would apply to take branch a's state to branch b's state. One line of JSON followed by END:
{"added":[{"table":"users","pk":2,"row":{"name":"Bob","age":30}}],"removed":[{"table":"users","pk":4,"row":{"name":"Dan","age":40}}],"modified":[{"table":"users","pk":3,"before":{"age":29},"after":{"age":30}}]}
added— rows present inbbut nota.rowcontains every non-pk column.removed— rows present inabut notb.rowcontains every non-pk column (taken froma).modified— rows with the same pk in both branches but at least one differing column.beforeandaftercontain only the columns that changed.- Entries sorted by
(table, pk). Use the exact key order:added,removed,modified. No whitespace inside the JSON.
Merge semantics
MERGE src dst performs a three-way merge:
- Find the fork point — the state of
dstat the momentsrcwas branched offdst's lineage. Branches form a tree rooted atmain; the eval guaranteessrcanddstshare a lineage (one is an ancestor of the other, or they share an ancestor on this lineage chain). - Compute
src_Δ= changes from fork point tosrc, anddst_Δ= changes from fork point todst. - A conflict is a
(table, pk)whose final state insrcdiffers from its final state indstand both sides changed it. - On conflict: reply
CONFLICT <table>:<pk>for the first conflict (sorted by(table, pk)) and do not modifydst. - Otherwise: apply
src_Δtodstand replyOK.
Constraints
- All tables use
INTEGER PRIMARY KEYas the first column. No composite keys, no foreign keys, no indexes you need to manage. - Column types are
INTEGERandTEXTonly. - Values in commands are alphanumeric plus underscore — no spaces, no quoting, no NULLs in input (omitted columns are NULL).
- Branches form a tree rooted at
main. The evaluator only attempts merges between branches in the same lineage. - The DB file path is absolute. You may create auxiliary tables in it as you wish — the evaluator never reads the file directly.
Example
INIT /tmp/branchy.db
OK
SCHEMA users id:INTEGER,name:TEXT,age:INTEGER
OK
PUT main users 1 name=Alice,age=30
OK
PUT main users 2 name=Bob,age=25
OK
BRANCH feature main
OK
PUT feature users 3 name=Carol,age=28
OK
PUT feature users 1 age=31
OK
DIFF main feature
{"added":[{"table":"users","pk":3,"row":{"name":"Carol","age":28}}],"removed":[],"modified":[{"table":"users","pk":1,"before":{"age":30},"after":{"age":31}}]}
END
MERGE feature main
OK
GET main users 1
name=Alice|age=31
GET main users 3
name=Carol|age=28
QUIT
Notes
- The eval treats your program as a black box. It never inspects your code — only the commands you respond to and (optionally) the SQLite file you persist.
- You can use any language that can speak stdin/stdout and link a SQLite library.
Scoring
Your solution is evaluated against 10 test cases (100 points total). Point values grow exponentially — later tests are worth disproportionately more than earlier ones, so passing the hardest tier is where the score lives.
| Test | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
|---|---|---|---|---|---|---|---|---|---|---|
| Points | 1 | 2 | 3 | 5 | 7 | 9 | 12 | 16 | 20 | 25 |