Version 0 of TclOO Reactive Relational Database

Updated 2015-12-27 06:31:38 by Napier

Premise

I am not highly versed in the world of databases nor am I claiming this is better or worse than anything else available, but I thought it would be insightful to post this example and possibly get some feedback on it. It is far from complete but it operates as I need it to thus far. The idea is to create a basic database scheme which allows querying of data, event listeners on data changes (subscribe / unsubscribe), and validation to keep the specified structure of the data in-check.

package require TclOO
namespace import oo::*

class create DB {
    variable db:State; variable db:Map; variable db:Categories
    
    constructor args {
        set db:State {}
        set db:Categories {}
        set db:Map {}
    }
    
    method specify {category "with" args} {
        if {$category in ${db:Categories}} {throw error "DB ERROR: $category Already Specified"}
        if {[string equal $with "with"]} { set keys $args } else { lappend keys $with {*}$args }
        lappend {db:Categories} $category
        foreach key $args {dict set keyDict $key ""}
        dict set {db:Map} $category [dict create schema $keys names {} valueMap $keyDict listeners {}]
    }
    
    method build {category name args} {
        my Validate category
        if {$name in [dict get ${db:Map} $category names]} {throw error "DB ERROR: $name already exists in $category"}
        set schema [dict get ${db:Map} $category schema]
        if {[my CheckSchema $schema [dict keys $args]]} {
            dict set {db:Map} $category names [ lappend dict {*}[dict get ${db:Map} $category names] $name ]
            dict set {db:Map} $category listeners $name ""
            dict for {k v} $args { my setValue $category $name $k $v }
        } else { throw error "\t DB ERROR: Schema Does Not Match Provided Data: \n Required: $schema \n Received: [dict keys $args]" }
    }
    
    method query {category "for" key "matching" value} {
        my Validate category
        if { $key ni [dict get ${db:Map} $category schema] } {throw error "DB Error: $key is not in $category Schema"}
        if { [dict exists ${db:Map} $category valueMap $key $value] } {
            return [dict get ${db:Map} $category valueMap $key $value]
        } else { return "" }
    }
    
    method set {category name args} {
        my Validate category
        set changes 0
        dict for {k v} $args { incr changes [my setValue $category $name $k $v] }
        if {$changes >= 1} {
            foreach listener [dict get ${db:Map} $category listeners $name] {
                if {$listener == ""} {continue}
                {*}$listener $name {*}[dict get ${db:State} $category $name]
            }
        }
    }
    
    method subscribe {category name callback} {
        my Validate category
        if { $callback in [dict get ${db:Map} $category listeners $name] } { throw error "DB Error: Listener already Registered" }
        dict set {db:Map} $category listeners $name [ lappend dict {*}[dict get ${db:Map} $category listeners $name] $callback ]
    }
    
    method Validate {args} {
        if { "category" in $args } {
            upvar 1 category category
            if { $category ni ${db:Categories} } { throw error "DB Error: $category category does not exist" } 
        }
    }
    
    method setValue {category name key value} {
        if {![dict exists ${db:Map} $category valueMap $key $value]} { dict set {db:Map} $category valueMap $key $value "" }
        set oldValue [expr {[dict exists ${db:State} $category $name $key] ? [dict get ${db:State} $category $name $key] : "" }]
        if {[string equal $oldValue $value]} {puts "$oldValue equals $value"; return 0}
        if {$oldValue != ""} {
            dict set {db:Map} $category valueMap $key $oldValue [ lsearch -all -inline -not -exact [dict get ${db:Map} $category valueMap $key $oldValue] $name ]
        }
        dict set {db:Map} $category valueMap $key $value [ lappend dict {*}[dict get ${db:Map} $category valueMap $key $value] $name ]
        dict set {db:State} $category $name $key $value
        return 1
    }
    
    method CheckSchema {l1 l2} {
        foreach i $l1 { if { [ lsearch -exact $l2 $i ] == -1 } { return 0 } }
        foreach i $l2 { if { [ lsearch -exact $l1 $i ] == -1 } { return 0 } }    
        return 1
    }
    
    method getAll {} {
        puts "\n---------------------------"
        puts "\t State: \n ${db:State}"
        puts "\t Categories: \n ${db:Categories}"
        puts "\t Map: \n ${db:Map}"
        puts "---------------------------\n"
    }
}