Added assert and Scheduler, and started adding proper test frameworks
[tcl-tlc.git] / scripts / datasource.itcl
blobfc523bcd9b01e449f24b1735b43f2abe672af292
1 # vim: ft=tcl foldmarker=<<<,>>>
3 # item_schema example: <<<
4 # variable item_schema {
5 # "Schema" {schema}
6 # "Table" {table combobox \
7 # -choices {sql_features sql_languages sql_packages} \
8 # -initial_choice sql_languages
9 # }
10 # "Owner" {owner}
11 # "Has Indexes" {indexed checkbutton}
12 # "Has Rules" {ruled checkbutton}
13 # "Has Triggers" {hastriggers checkbutton}
14 # }
15 # >>>
17 class tlc::Datasource {
18 inherit tlc::Handlers
20 constructor {args} {}
21 destructor {}
23 public {
24 variable criteria {}
25 variable criteria_values ""
26 variable quote 1
27 variable id_column 0 ;# column to use as the ID column
28 variable criteria_map {}
29 variable defaults {}
30 variable item_schema {}
32 method get_item_schema {}
33 method set_criteria_map {mapping}
34 method get_criteria {}
35 method set_criteria {arraylist}
36 method get_criteria_values {}
37 method set_criteria_values {arraylist}
38 method set_field_defs {arraylist}
39 method get_field_defs {}
40 method set_defaults {rowarray}
41 method get_defaults {}
43 method get_list {criteria {headersvar {}}}
44 method get_labelled_list {criteria {headersvar {}}}
45 method get_id_column {}
46 method add_item {row {col_list ""}}
47 method update_item {oldrow newrow {old_col_list ""} {new_col_list ""}}
48 method remove_item {row {col_list ""}}
49 method extract_id {row}
50 method get_full_row {id}
51 method can_do {action args}
53 method lookup {key match {mode -exact}}
54 method slice {column args}
57 protected {
58 variable can_do
59 variable acriteria
60 variable acriteria_values
61 variable last_headers {}
63 method replace_criteria {str {criteria_arraylist {}}
64 {recursion_level 0}}
65 method resolve_row {row col_list}
66 method criteria_changed {}
71 body tlc::Datasource::constructor {args} { #<<<1
72 array set can_do {}
74 eval configure $args
78 body tlc::Datasource::destructor {} { #<<<1
79 invoke_handlers destroyed
83 body tlc::Datasource::replace_criteria {str {criteria_arraylist {}} {recursion_level 0}} { #<<<1
84 # TODO: figure out a "proper" way to allow substitutions, and still allow
85 # 1) %var% strign literals that match to pass through
86 # 2) a barrier against infinite recursion
88 set ret $str
89 if {$criteria_arraylist=={}} {
90 set driving_force $criteria_values
91 } else {
92 set driving_force $criteria_arraylist
94 set map_directives {}
95 array set valmappings $criteria_map
96 foreach {idx val} $driving_force {
97 if {[info exists valmappings($idx)]} {
98 array set tmp $valmappings($idx)
99 if {[info exists tmp($val)]} {
100 lappend map_directives "%${idx}%" "$tmp($val)"
101 } else {
102 lappend map_directives "%${idx}%" "$val"
104 } else {
105 lappend map_directives "%${idx}%" "$val"
108 set ret [string map "$map_directives" "$str"]
109 # now, check for recursive re-inclusion of criteria within criteria only
110 foreach {idx val} $criteria_values {
111 if {[string first "%$idx%" $ret]>-1} {
112 if {$recursion_level<5} {
113 set ret [replace_criteria $ret $driving_force
114 [incr recursion_level]]
116 break
119 return $ret
123 body tlc::Datasource::get_criteria {} { #<<<1
124 # this basically gives the form defination give from the intersection of
125 # the Criteria value and the names of the field_defs array-var
127 return $criteria
131 body tlc::Datasource::get_criteria_values {} { #<<<1
132 return $criteria_values
136 body tlc::Datasource::set_criteria {arraylist} { #<<<1
137 # sets the criteria (and acriteria) from an array-style list. the list
138 # element style is: {criteria_label} {criteria_varname {form style}}
140 set criteria $arraylist
144 body tlc::Datasource::set_criteria_values {arraylist} { #<<<1
145 # sets the replacement values for the criteria tokenlist from an
146 # array-style list the list element style is:
147 # {criteria_varname} {variable_value}
149 set criteria_values $arraylist
153 body tlc::Datasource::set_field_defs {arraylist} { #<<<1
154 set field_defs $arraylist
157 body tlc::Datasource::get_field_defs {} { #<<<1
158 return $field_defs
161 body tlc::Datasource::resolve_row {row col_list} { #<<<1
162 # resolves a list of values (row) and a column name list (col_list) into an
163 # array-style list
164 # inputs: row (raw data list); col_list: column names for items in row
165 # returns: array-style list of the style: {col_name} {col_value}
167 foreach col $col_list val $row {
168 lappend ret $col $val
171 return $ret
175 body tlc::Datasource::get_labelled_list {criteria {headersvar {}}} { #<<<1
176 # does the same as a get_list, but each row contains interleaved
177 # header-names with each field; makes the loading of an array or a treeview
178 # structure a lot easier for the client
180 if {$headersvar != {}} {
181 upvar $headersvar headers
183 set rawlist [get_list $criteria headers]
184 set llist ""
185 foreach rawrow $rawlist {
186 set lrow ""
187 foreach rawcol $rawrow head $headers {
188 lappend lrow $head $rawcol
190 lappend llist $lrow
193 # puts "\n\nDatasource::get_labelled_list: returning: ($llist)"
194 return $llist
198 body tlc::Datasource::get_list {criteria {headersvar {}}} { #<<<1
202 body tlc::Datasource::get_id_column {} { #<<<1
203 return [list $id_column [lindex $last_headers $id_column]]
207 body tlc::Datasource::add_item {row {col_list {}}} { #<<<1
211 body tlc::Datasource::update_item {oldrow newrow {old_col_list ""} {new_col_list ""}} { #<<<1
215 body tlc::Datasource::remove_item {row {col_list ""}} { #<<<1
219 body tlc::Datasource::extract_id {row} { #<<<1
220 return [lindex $row $id_column]
224 body tlc::Datasource::criteria_changed {} { #<<<1
228 body tlc::Datasource::set_criteria_map {mapping} { #<<<1
229 set criteria_map $mapping
233 body tlc::Datasource::get_item_schema {} { #<<<1
234 return $item_schema
237 body tlc::Datasource::set_defaults {rowarray} { #<<<1
238 set defaults $rowarray
241 body tlc::Datasource::get_defaults {} { #<<<1
242 return $defaults
245 body tlc::Datasource::get_full_row {id} { #<<<1
246 # purpose: to return all fields defined in the filed definitions for the id specified -- to be used
247 # by a client who will be doing an update later
248 # returns: array-style list of {col} {val} {col} {val} ...
249 # this is to be implemented by the client
252 body tlc::Datasource::can_do {action args} { #<<<1
253 if {[llength $args] == 0} {
254 return [expr {[info exists can_do($action)] && $can_do($action)}]
255 } elseif {[llength $args] == 1} {
256 set can_do($action) [expr {[lindex $args 0]}]
257 } else {
258 error "Wrong # of args: must be action ?newvalue?"
263 body tlc::Datasource::lookup {key match {mode -exact}} { #<<<1
264 switch -- $mode {
265 -exact -
266 -glob -
267 -regexp {}
269 default {
270 error "Invalid match mode: \"$mode\", must be one of -exact, -glob or -regexp" "" [list bad_match_mode $mode]
274 set rows [get_list {} headers]
276 set idx [lsearch $headers $key]
277 if {$idx == -1} {
278 error "Invalid key: \"$key\", must be one of \"[join $headers {", "}]\""
281 if {[package vsatisfies $::tcl_version 8.5]} {
282 set matches [lsearch -all -inline $mode -index $idx $rows $match]
283 } else {
284 set matches {}
285 foreach row $rows {
286 switch $mode -- [lindex $row $idx] $match {
287 lappend matches $row
288 } default {
289 continue
294 set build {}
295 foreach row $matches {
296 set a {}
297 foreach h $headers v $row {
298 lappend a $h $v
300 lappend build $a
303 return $build
307 body tlc::Datasource::slice {column args} { #<<<1
308 # slice returns all instances of a column in the datasource, sorted
309 # to taste
311 set raw [get_list {} headers]
313 # Check that the specified column is valid <<<
314 if {[lsearch $headers $column] == -1} {
315 error "Invalid slice column \"$column\", should be one of ([join $headers {, }])" "" \
316 [list invalid_column $column]
318 # Check that the specified column is valid >>>
320 # Parse options <<<
321 set sortcolumn $column
322 set sortmode "dictionary"
323 set sortdir increasing
325 set remaining $args
326 while {[llength $remaining] > 0} {
327 set option [lindex $remaining 0]
328 set remaining [lrange $remaining 1 end]
330 if {[string index $option 0] != "-"} {
331 error "Expecting an option, got \"$option\"" "" [list syntax_error]
334 switch -- $option {
335 -orderby - -sort { #<<<
336 set sortcolumn [lindex $remaining 0]
337 set remaining [lrange $remaining 1 end]
339 if {[lsearch $headers $sortcolumn] == -1} {
340 error "Specified sort column ($sortcolumn) doesn't exist. Should be one of ([join $headers {, }])" "" \
341 [list invalid_sortcolumn $sortcolumn $headers]
344 if {[string index [lindex $remaining 0] 0] != "-"} {
345 set sortdir [lindex $remaining 0]
346 set remaining [lrange $remaining 1 end]
348 switch -- $sortdir {
349 asc - ascending - increasing {
350 set sortdir increasing
353 desc - descending - decreasing {
354 set sortdir decreasing
357 default {
358 error "Invalid sortdir specified: ($sortdir)" "" \
359 [list invalid_sortdir $sortdir]
363 #>>>
366 -sortmode { #<<<
367 set sortmode [lindex $remaining 0]
368 set remaining [lrange $sortmode 1 end]
370 switch -- $sortmode {
371 ascii - dictionary - integer - real {}
373 default {
374 error "Invalid sortmode: \"$sortmode\"" "" \
375 [list invalid_sortmode $sortmode]
378 #>>>
381 default {
382 error "Invalid option \"$option\"" "" \
383 [list invalid_option $option]
387 # Parse options >>>
389 set sort_col_idx [lsearch $headers $sortcolumn]
390 set slice_col_idx [lsearch $headers $column]
392 set build {}
393 foreach row [lsort -$sortmode -$sortdir -index $sort_col_idx $raw] {
394 lappend build [lindex $row $slice_col_idx]
397 return $build