/relvar

Primitive, Amateur Relational Database in JS

Primary LanguageJavaScript

relvar.js

An amateur database inspired by CJ Date and Project M36.

Documentation will be sparse as I'm focused more on the development, but here's a demo that should give you the idea of what I'm gunning to do:

var {relvar, union, _sel, _un, selection
    , rvts, logrv, S, P, SP, _j, join, inv_selection, _but
    , where, _where, minus, _minus, rename, _ren, matching, _mat, not_matching, _nmat
    , db, save_db, assign_rv, update, _up, extend, _ext, count, _cnt, sum, _sum, assign_js_constraint} = require("../main.js");

const _arv = (...rvs) => (db) => assign_rv(db, ...rvs).db;

var GameDB = db(require("path").join(__dirname, "test.yaml"), {name: "GameDB"});
var {db: GameDB} = save_db(GameDB);

var C = relvar({
    attrs: {
        "C#": "number",
        "Cname": "string",
        "HP": "number"
    },
    tuples: [
        {"C#": 1, "Cname": "Reese of Wellington", "HP": 4}
        ,{"C#": 2, "Cname": "Sir Nic of the Weils", "HP": 18}
    ]
});

var I = relvar({
    attrs: {
        "I#": "number",
        "Iname": "string",
        "Durability": "number",
        "Damage": "number"
    },
    tuples: [
        {"I#": 1, "Iname": "Bronze Poniard", "Durability": 10, "Damage": 10}
        ,{"I#": 2, "Iname": "Glazed Donut", "Durability": 1, "Damage": 1}
        ,{"I#": 3, "Iname": "Venom-Soaked Blade", "Durability": 5, "Damage": 25}
        ,{"I#": 4, "Iname": "Ancient Spoon", "Durability": 1, "Damage": 80}
        ,{"I#": 5, "Iname": "Holy Hand Grenade of Antioch", "Durability": 1, "Damage": 999}
        ,{"I#": 6, "Iname": "Common Shortsword", "Durability": 30, "Damage": 15}
        ,{"I#": 7, "Iname": "Rot-Hilted Axe", "Durability": 8, "Damage": 15}
    ]
});

var CI = relvar({
    attrs: {
        "C#": "number",
        "I#": "number",
        "Slot": "number"
    },
    tuples: [
        {"C#": 1, "I#": 1, "Slot": 3},
        {"C#": 1, "I#": 3, "Slot": 4},
        {"C#": 2, "I#": 2, "Slot": 4},
        {"C#": 2, "I#": 4, "Slot": 3},
        {"C#": 2, "I#": 1, "Slot": 10},
    ]
});

var {db: GameDB} = GameDB.tap(_arv(["C", C], ["I", I], ["CI", CI])).tap(save_db);

var {run} = require("../db_lang.js");

console.log("C:")
logrv(C);

console.log("C join CI:")
join(C, CI).tap(logrv);

console.log("C{C#, Cname} join CI")
selection(C, "C#", "Cname").tap(_j(CI)).tap(logrv);

console.log("C join CI join I");
join(C, CI).tap(_j(I)).tap(logrv);

console.log("(C join CI join I){ALL BUT C#, HP, I#}");
join(C, CI).tap(_j(I)).tap(_but("C#", "HP", "I#")).tap(logrv);

console.log("Variant to test/display commutative properties of join:")
console.log("(C join I join CI){ALL BUT C#, HP, I#}");
join(C, I, CI).tap(_but("C#", "HP", "I#")).tap(logrv);

console.log("Give me all items that are owned by no character:")
console.log("I NOT MATCHING CI");
not_matching(I, CI).tap(logrv);

console.log("All characters with an item that deals more than 30 damage:")
console.log("C MATCHING (CI JOIN (I WHERE {Damage > 30}))")
matching(C, join(CI, where(I, i=>i.Damage > 30))).tap(logrv)

console.log("Just their names:")
console.log("(C MATCHING (CI JOIN (I WHERE {Damage > 30}))){Cname}")
matching(C, join(CI, where(I, i=>i.Damage > 30))).tap(_sel("Cname")).tap(logrv)

where(I, i=>i.Damage > 30)
.tap(_j(CI))
.tap(cii => matching(C, cii))
.tap(_sel("Cname"))
.tap(logrv);

console.log("Characters and the items they DON'T have: ")
console.log("((C{C#} JOIN I{I#}) MINUS (CI{C#, I#}) JOIN C{C#, Cname} JOIN I{I#, Iname}) {Cname, Iname} RENAME {Cname AS Character, Iname AS Missing Item}")
join(selection(C, "C#"), selection(I, "I#"))
.tap(_minus( selection(CI, "C#", "I#") ))
.tap(_j( selection(C, "C#", "Cname"),  selection(I, "I#", "Iname") ))
.tap(_sel("Cname", "Iname"))
.tap(_ren(["Cname", "Character"], ["Iname", "Missing Item"]))
.tap(logrv);

console.log("Fix overpowered items: ")
console.log("UPDATE I WHERE Damage > 30: {Damage := Damage / 2}")
update(I, i=>i.Damage > 30, ["Damage", i => Math.floor(i.Damage/2)])
.tap(logrv);

console.log("Show Damage Per Durability (DPD): ")
console.log("EXTEND I: {DPD := Damage / Durability}");
extend(I, ["DPD", "number", i=>Math.round(i.Damage/i.Durability)])
.tap(logrv);


console.log("Count of all character-item combos:\n\tCOUNT ( CI ) =", count(CI));
console.log("Count of all distinct slots in character-item combos:\n\tCOUNT ( CI { Slot } ) :", count(CI, "Slot"));
console.log("Sum of all item damage:\n\tSUM (I, Damage) =", sum(I, "Damage"));
console.log("Sum of all distinct item damage values:\n\tSUM (I, { Damage }) =", sum(I, ["Damage"]));
console.log("Sum of tripled item damage:\n\tSUM (I, 3 * Damage) = ", sum(I, "Damage", i=>i.Damage*3) )
console.log("Sum of all distinct tripled item damage:\n\tSUM( EXTEND I : { Damage := Damage * 3 }, {Damage}) = ", sum(extend(I, ["Damage", "number", i=>i.Damage*3]) , ["Damage"]))

console.log("My D implementation is behind on most features, but the plumbing exists: ");
console.log("(C join CI join I){ALL BUT C#, HP, I#}");
run(GameDB, "(C join I join CI){ALL BUT C#, HP, I#}").tap(logrv);

// With D, I hope to be able to allow constraints to be written in it
// as a simple solution rather than needing the prefab or custom JS.


// Add a uniqueness constraint to I.

const uniq_i = (rvs) => count(rvs.I) == count(rvs.I, "I#");
var {db: GameDB} = assign_js_constraint(GameDB, ["UNIQ_I", uniq_i]);

console.log("Attempt to violate the constraint:");
const new_i = union(I, {tuples: [{"I#": 7, "Iname": "Floppy Disk", "Durability": 40, "Damage": 3}] });
const attempt_assign = assign_rv(GameDB, ["I", new_i]);
console.dir(attempt_assign.c == "FAILED" ? attempt_assign.issue : "Oops. This shouldn't happen.");

console.log("Concede, follow the constraint:");
const new_i_2 = union(I, {tuples: [{"I#": 8, "Iname": "Floppy Disk", "Durability": 40, "Damage": 3}] });
const this_should_work = assign_rv(GameDB, ["I", new_i_2]);
logrv(this_should_work.db.data.relvars["I"]);

// Initialize an existing DB with supplied constraints: 

var my_constraints = {
    "UNIQ_I": uniq_i
};

var newGame = db(require("path").join(__dirname, "test2.yaml")
            , {name: "NewGame", supplied: {constraints_js: my_constraints}});

    // I don't recommend you do this normally.
    // This is just to ensure that the fresh file has the constraint and data.
    // Your usual DB will already have the constraint in its data.
    // if you've already built test2.yaml, you could comment these three lines out, and it'll still work.
    newGame.data.relvars = GameDB.data.relvars;
    newGame.data.constraints_js = ["UNIQ_I"];
    save_db(newGame);

console.log("Attempt to violate again on new DB:");
const attempt_assign_2 = assign_rv(newGame, ["I", new_i]);
console.dir(attempt_assign_2.c == "FAILED" ? attempt_assign_2.issue : "Oops. This shouldn't happen.");

Output:

C:
┌────────────┬──────────────────────┬────────────┐
│ C#::number │ Cname::string        │ HP::number │
├────────────┼──────────────────────┼────────────┤
│ 1          │ Reese of Wellington  │ 4          │
├────────────┼──────────────────────┼────────────┤
│ 2          │ Sir Nic of the Weils │ 18         │
└────────────┴──────────────────────┴────────────┘
C join CI:
┌────────────┬──────────────────────┬────────────┬────────────┬──────────────┐
│ C#::number │ Cname::string        │ HP::number │ I#::number │ Slot::number │
├────────────┼──────────────────────┼────────────┼────────────┼──────────────┤
│ 1          │ Reese of Wellington  │ 4          │ 1          │ 3            │
├────────────┼──────────────────────┼────────────┼────────────┼──────────────┤
│ 1          │ Reese of Wellington  │ 4          │ 3          │ 4            │
├────────────┼──────────────────────┼────────────┼────────────┼──────────────┤
│ 2          │ Sir Nic of the Weils │ 18         │ 2          │ 4            │
├────────────┼──────────────────────┼────────────┼────────────┼──────────────┤
│ 2          │ Sir Nic of the Weils │ 18         │ 4          │ 3            │
├────────────┼──────────────────────┼────────────┼────────────┼──────────────┤
│ 2          │ Sir Nic of the Weils │ 18         │ 1          │ 10           │
└────────────┴──────────────────────┴────────────┴────────────┴──────────────┘
C{C#, Cname} join CI
┌────────────┬──────────────────────┬────────────┬──────────────┐
│ C#::number │ Cname::string        │ I#::number │ Slot::number │
├────────────┼──────────────────────┼────────────┼──────────────┤
│ 1          │ Reese of Wellington  │ 1          │ 3            │
├────────────┼──────────────────────┼────────────┼──────────────┤
│ 1          │ Reese of Wellington  │ 3          │ 4            │
├────────────┼──────────────────────┼────────────┼──────────────┤
│ 2          │ Sir Nic of the Weils │ 2          │ 4            │
├────────────┼──────────────────────┼────────────┼──────────────┤
│ 2          │ Sir Nic of the Weils │ 4          │ 3            │
├────────────┼──────────────────────┼────────────┼──────────────┤
│ 2          │ Sir Nic of the Weils │ 1          │ 10           │
└────────────┴──────────────────────┴────────────┴──────────────┘
C join CI join I
┌────────────┬──────────────────────┬────────────┬────────────┬──────────────┬────────────────────┬────────────────────┬────────────────┐
│ C#::number │ Cname::string        │ HP::number │ I#::number │ Slot::number │ Iname::string      │ Durability::number │ Damage::number │
├────────────┼──────────────────────┼────────────┼────────────┼──────────────┼────────────────────┼────────────────────┼────────────────┤
│ 1          │ Reese of Wellington  │ 4          │ 1          │ 3            │ Bronze Poniard     │ 10                 │ 10             │
├────────────┼──────────────────────┼────────────┼────────────┼──────────────┼────────────────────┼────────────────────┼────────────────┤
│ 1          │ Reese of Wellington  │ 4          │ 3          │ 4            │ Venom-Soaked Blade │ 5                  │ 25             │
├────────────┼──────────────────────┼────────────┼────────────┼──────────────┼────────────────────┼────────────────────┼────────────────┤
│ 2          │ Sir Nic of the Weils │ 18         │ 2          │ 4            │ Glazed Donut       │ 1                  │ 1              │
├────────────┼──────────────────────┼────────────┼────────────┼──────────────┼────────────────────┼────────────────────┼────────────────┤
│ 2          │ Sir Nic of the Weils │ 18         │ 4          │ 3            │ Ancient Spoon      │ 1                  │ 80             │
├────────────┼──────────────────────┼────────────┼────────────┼──────────────┼────────────────────┼────────────────────┼────────────────┤
│ 2          │ Sir Nic of the Weils │ 18         │ 1          │ 10           │ Bronze Poniard     │ 10                 │ 10             │
└────────────┴──────────────────────┴────────────┴────────────┴──────────────┴────────────────────┴────────────────────┴────────────────┘
(C join CI join I){ALL BUT C#, HP, I#}
┌──────────────────────┬──────────────┬────────────────────┬────────────────────┬────────────────┐
│ Cname::string        │ Slot::number │ Iname::string      │ Durability::number │ Damage::number │
├──────────────────────┼──────────────┼────────────────────┼────────────────────┼────────────────┤
│ Reese of Wellington  │ 3            │ Bronze Poniard     │ 10                 │ 10             │
├──────────────────────┼──────────────┼────────────────────┼────────────────────┼────────────────┤
│ Reese of Wellington  │ 4            │ Venom-Soaked Blade │ 5                  │ 25             │
├──────────────────────┼──────────────┼────────────────────┼────────────────────┼────────────────┤
│ Sir Nic of the Weils │ 4            │ Glazed Donut       │ 1                  │ 1              │
├──────────────────────┼──────────────┼────────────────────┼────────────────────┼────────────────┤
│ Sir Nic of the Weils │ 3            │ Ancient Spoon      │ 1                  │ 80             │
├──────────────────────┼──────────────┼────────────────────┼────────────────────┼────────────────┤
│ Sir Nic of the Weils │ 10           │ Bronze Poniard     │ 10                 │ 10             │
└──────────────────────┴──────────────┴────────────────────┴────────────────────┴────────────────┘
Variant to test/display commutative properties of join:
(C join I join CI){ALL BUT C#, HP, I#}
┌──────────────────────┬────────────────────┬────────────────────┬────────────────┬──────────────┐
│ Cname::string        │ Iname::string      │ Durability::number │ Damage::number │ Slot::number │
├──────────────────────┼────────────────────┼────────────────────┼────────────────┼──────────────┤
│ Reese of Wellington  │ Bronze Poniard     │ 10                 │ 10             │ 3            │
├──────────────────────┼────────────────────┼────────────────────┼────────────────┼──────────────┤
│ Reese of Wellington  │ Venom-Soaked Blade │ 5                  │ 25             │ 4            │
├──────────────────────┼────────────────────┼────────────────────┼────────────────┼──────────────┤
│ Sir Nic of the Weils │ Bronze Poniard     │ 10                 │ 10             │ 10           │
├──────────────────────┼────────────────────┼────────────────────┼────────────────┼──────────────┤
│ Sir Nic of the Weils │ Glazed Donut       │ 1                  │ 1              │ 4            │
├──────────────────────┼────────────────────┼────────────────────┼────────────────┼──────────────┤
│ Sir Nic of the Weils │ Ancient Spoon      │ 1                  │ 80             │ 3            │
└──────────────────────┴────────────────────┴────────────────────┴────────────────┴──────────────┘
Give me all items that are owned by no character:
I NOT MATCHING CI
┌────────────┬──────────────────────────────┬────────────────────┬────────────────┐
│ I#::number │ Iname::string                │ Durability::number │ Damage::number │
├────────────┼──────────────────────────────┼────────────────────┼────────────────┤
│ 5          │ Holy Hand Grenade of Antioch │ 1                  │ 999            │
├────────────┼──────────────────────────────┼────────────────────┼────────────────┤
│ 6          │ Common Shortsword            │ 30                 │ 15             │
├────────────┼──────────────────────────────┼────────────────────┼────────────────┤
│ 7          │ Rot-Hilted Axe               │ 8                  │ 15             │
└────────────┴──────────────────────────────┴────────────────────┴────────────────┘
All characters with an item that deals more than 30 damage:
C MATCHING (CI JOIN (I WHERE {Damage > 30}))
┌────────────┬──────────────────────┬────────────┐
│ C#::number │ Cname::string        │ HP::number │
├────────────┼──────────────────────┼────────────┤
│ 2          │ Sir Nic of the Weils │ 18         │
└────────────┴──────────────────────┴────────────┘
Just their names:
(C MATCHING (CI JOIN (I WHERE {Damage > 30}))){Cname}
┌──────────────────────┐
│ Cname::string        │
├──────────────────────┤
│ Sir Nic of the Weils │
└──────────────────────┘
┌──────────────────────┐
│ Cname::string        │
├──────────────────────┤
│ Sir Nic of the Weils │
└──────────────────────┘
Characters and the items they DON'T have:
((C{C#} JOIN I{I#}) MINUS (CI{C#, I#}) JOIN C{C#, Cname} JOIN I{I#, Iname}) {Cname, Iname} RENAME {Cname AS Character, Iname AS Missing Item}
┌──────────────────────┬──────────────────────────────┐
│ Character::string    │ Missing Item::string         │
├──────────────────────┼──────────────────────────────┤
│ Reese of Wellington  │ Glazed Donut                 │
├──────────────────────┼──────────────────────────────┤
│ Reese of Wellington  │ Ancient Spoon                │
├──────────────────────┼──────────────────────────────┤
│ Reese of Wellington  │ Holy Hand Grenade of Antioch │
├──────────────────────┼──────────────────────────────┤
│ Reese of Wellington  │ Common Shortsword            │
├──────────────────────┼──────────────────────────────┤
│ Reese of Wellington  │ Rot-Hilted Axe               │
├──────────────────────┼──────────────────────────────┤
│ Sir Nic of the Weils │ Venom-Soaked Blade           │
├──────────────────────┼──────────────────────────────┤
│ Sir Nic of the Weils │ Holy Hand Grenade of Antioch │
├──────────────────────┼──────────────────────────────┤
│ Sir Nic of the Weils │ Common Shortsword            │
├──────────────────────┼──────────────────────────────┤
│ Sir Nic of the Weils │ Rot-Hilted Axe               │
└──────────────────────┴──────────────────────────────┘
Fix overpowered items:
UPDATE I WHERE Damage > 30: {Damage := Damage / 2}
┌────────────┬──────────────────────────────┬────────────────────┬────────────────┐
│ I#::number │ Iname::string                │ Durability::number │ Damage::number │
├────────────┼──────────────────────────────┼────────────────────┼────────────────┤
│ 1          │ Bronze Poniard               │ 10                 │ 10             │
├────────────┼──────────────────────────────┼────────────────────┼────────────────┤
│ 2          │ Glazed Donut                 │ 1                  │ 1              │
├────────────┼──────────────────────────────┼────────────────────┼────────────────┤
│ 3          │ Venom-Soaked Blade           │ 5                  │ 25             │
├────────────┼──────────────────────────────┼────────────────────┼────────────────┤
│ 4          │ Ancient Spoon                │ 1                  │ 40             │
├────────────┼──────────────────────────────┼────────────────────┼────────────────┤
│ 5          │ Holy Hand Grenade of Antioch │ 1                  │ 499            │
├────────────┼──────────────────────────────┼────────────────────┼────────────────┤
│ 6          │ Common Shortsword            │ 30                 │ 15             │
├────────────┼──────────────────────────────┼────────────────────┼────────────────┤
│ 7          │ Rot-Hilted Axe               │ 8                  │ 15             │
└────────────┴──────────────────────────────┴────────────────────┴────────────────┘
Show Damage Per Durability (DPD): 
EXTEND I: {DPD := Damage / Durability}
┌────────────┬──────────────────────────────┬────────────────────┬────────────────┬─────────────┐
│ I#::number │ Iname::string                │ Durability::number │ Damage::number │ DPD::number │
├────────────┼──────────────────────────────┼────────────────────┼────────────────┼─────────────┤
│ 1          │ Bronze Poniard               │ 10                 │ 10             │ 1           │
├────────────┼──────────────────────────────┼────────────────────┼────────────────┼─────────────┤
│ 2          │ Glazed Donut                 │ 1                  │ 1              │ 1           │
├────────────┼──────────────────────────────┼────────────────────┼────────────────┼─────────────┤
│ 3          │ Venom-Soaked Blade           │ 5                  │ 25             │ 5           │
├────────────┼──────────────────────────────┼────────────────────┼────────────────┼─────────────┤
│ 4          │ Ancient Spoon                │ 1                  │ 80             │ 80          │
├────────────┼──────────────────────────────┼────────────────────┼────────────────┼─────────────┤
│ 5          │ Holy Hand Grenade of Antioch │ 1                  │ 999            │ 999         │
├────────────┼──────────────────────────────┼────────────────────┼────────────────┼─────────────┤
│ 6          │ Common Shortsword            │ 30                 │ 15             │ 1           │
├────────────┼──────────────────────────────┼────────────────────┼────────────────┼─────────────┤
│ 7          │ Rot-Hilted Axe               │ 8                  │ 15             │ 2           │
└────────────┴──────────────────────────────┴────────────────────┴────────────────┴─────────────┘
Count of all character-item combos:
        COUNT ( CI ) = 5
Count of all distinct slots in character-item combos:
        COUNT ( CI { Slot } ) : 3
Sum of all item damage:
        SUM (I, Damage) = 1145
Sum of all distinct item damage values:
        SUM (I, { Damage }) = 1130
Sum of tripled item damage:
        SUM (I, 3 * Damage) =  3435
Sum of all distinct tripled item damage:
        SUM( EXTEND I : { Damage := Damage * 3 }, {Damage}) =  3390
My D implementation is behind on most features, but the plumbing exists:
(C join CI join I){ALL BUT C#, HP, I#}
┌──────────────────────┬────────────────────┬────────────────────┬────────────────┬──────────────┐
│ Cname::string        │ Iname::string      │ Durability::number │ Damage::number │ Slot::number │
├──────────────────────┼────────────────────┼────────────────────┼────────────────┼──────────────┤
│ Reese of Wellington  │ Bronze Poniard     │ 10                 │ 10             │ 3            │
├──────────────────────┼────────────────────┼────────────────────┼────────────────┼──────────────┤
│ Reese of Wellington  │ Venom-Soaked Blade │ 5                  │ 25             │ 4            │
├──────────────────────┼────────────────────┼────────────────────┼────────────────┼──────────────┤
│ Sir Nic of the Weils │ Bronze Poniard     │ 10                 │ 10             │ 10           │
├──────────────────────┼────────────────────┼────────────────────┼────────────────┼──────────────┤
│ Sir Nic of the Weils │ Glazed Donut       │ 1                  │ 1              │ 4            │
├──────────────────────┼────────────────────┼────────────────────┼────────────────┼──────────────┤
│ Sir Nic of the Weils │ Ancient Spoon      │ 1                  │ 80             │ 3            │
└──────────────────────┴────────────────────┴────────────────────┴────────────────┴──────────────┘
Attempt to violate the constraint:
{ c: 'CONSTRAINTS_FAILED', constraint_names: [ 'UNIQ_I' ] }
Concede, follow the constraint:
┌────────────┬──────────────────────────────┬────────────────────┬────────────────┐
│ I#::number │ Iname::string                │ Durability::number │ Damage::number │
├────────────┼──────────────────────────────┼────────────────────┼────────────────┤
│ 1          │ Bronze Poniard               │ 10                 │ 10             │
├────────────┼──────────────────────────────┼────────────────────┼────────────────┤
│ 2          │ Glazed Donut                 │ 1                  │ 1              │
├────────────┼──────────────────────────────┼────────────────────┼────────────────┤
│ 3          │ Venom-Soaked Blade           │ 5                  │ 25             │
├────────────┼──────────────────────────────┼────────────────────┼────────────────┤
│ 4          │ Ancient Spoon                │ 1                  │ 80             │
├────────────┼──────────────────────────────┼────────────────────┼────────────────┤
│ 5          │ Holy Hand Grenade of Antioch │ 1                  │ 999            │
├────────────┼──────────────────────────────┼────────────────────┼────────────────┤
│ 6          │ Common Shortsword            │ 30                 │ 15             │
├────────────┼──────────────────────────────┼────────────────────┼────────────────┤
│ 7          │ Rot-Hilted Axe               │ 8                  │ 15             │
├────────────┼──────────────────────────────┼────────────────────┼────────────────┤
│ 8          │ Floppy Disk                  │ 40                 │ 3              │
└────────────┴──────────────────────────────┴────────────────────┴────────────────┘
Attempt to violate again on new DB:
{ c: 'CONSTRAINTS_FAILED', constraint_names: [ 'UNIQ_I' ] }