#!/bin/sh
# the backslash makes the following line a comment to tcl \
exec /usr/local/tcl/bin/tcl "$0" ${1+"$@"}
#
# CGI bin script
# watch out for a lot of site specific stuff.  
# read it and understand it BEFORE trying to use it.
#
# Version IIp, new name plotSQL, a clone of searchSQL.5 which 
# attempts to add a crude plotting capability.  This involves
# potentially large ppm files and could be dangerous.
#
# Version 9, re-imported from Keck, has extract capability
# Version 10, has table stats capability
#
# set up the basics
set tclroot /usr/local/tcl
loadlibindex $tclroot/local/ucosyb.tlib
#
set wwdir /usr/local/www/mountain
#
global env
set env(SYBASE) /usr/local/sybase
global debugs
set debugs {}
set sqlq ""
set csqlq ""
set gsqlq ""
#
#
global dbpipe1 sybmsg 
set sybmsg(nullvalue) "NULL"
#
# rowlimit
# limit max number of rows that we will return -- 5,000 seems quite
# generous.  with large tables like SAOCAT the potential for tying
# up the session is quite good -- 256K rows!
set rowlimit 5000
# establish lists
set colatt(0) "" 
#
#
set ofp [open "/tmp/plot-log" w]
# set hfp [open "/tmp/pl.html" w]
#############################################################Procs
# lifted this kludge directly from Jackson@stsci
proc unescape {v} {
    # This a kludge
    regsub -all {\%25} $v {XXXPERCENTXXX} v
    set nv $v
    while {[regexp {%[0-9A-F][0-9A-F]} $v blah]} {
      scan $blah "%%%x" cv
      if {[ctype char $cv]=="&"} {
          regsub -all $blah $v \\& nv
      } else {
          regsub -all $blah $v [ctype char $cv] nv
      }
      set v $nv
    }
    regsub -all {XXXPERCENTXXX} $v {%} v
    return $v
}
#
proc getTableAttr {} {

	global sybmsg debugs cols colatt db usr tbl ofp keys fqtn

        set sqlcmd "select a.name, c.name, a.length, a.status from \
$db.dbo.syscolumns a, $db.dbo.sysobjects b, $db.dbo.systypes c where \
a.id = b.id and b.name = '$tbl' and b.uid = user_id('$usr') and \
a.usertype = c.usertype"

        set sqt "syscolumns/sysobjects/systypes"
        set res [doSQL 1]

        if {[lindex $res 0] == "ERROR"} {
                echo "Something went wrong here:"
                echo "$sqlcmd\n$sybmsg(msgno): $sybmsg(msgtext)"
                exit 1
        }

        while {1 == 1} {

                set line [sybNext 1]
                if {[lindex $line 0] == "ERROR"} {
                        echo "$sybmsg(msgno): $sybmsg(msgtext)"
                        break
                }

                if {$line == ""} {break}

		lassign $line c t s n
#               column name, type, length, nulls?

                lappend cols $c
		set colatt($c,type) $t
		set colatt($c,size) $s
		set colatt($c,null) $n
		set colatt($c,key) 0

        	case $t in {
        	{char varchar text datetime smalldatetime} {
                	set q "'"
        	}
        	{default} {
                	set q ""
        	}
        	}

		set colatt($c,q) $q

        }

	set table $usr.$tbl
        set pk [getPkeys $table]


        if {[lindex $pk 0] == "ERROR"} {
                puts $ofp "ERROR $pk"
                set pk ""
        }
        set keys {}
        foreach k $pk {
                if {$k > 0} {
			set kc [lindex $cols [expr {$k - 1}]]
			puts $ofp "keyindex $k is col $kc"
                        lappend keys $kc 
			set colatt($kc,key) 1
                }
        }

	if {[llength $keys] > 1} {
		puts stdout "Sorry, table $fqtn has more than one primary key field.  We can't handle that at this revision.  See the maintainer."
		puts stdout "<p> <hr> <p> </body> </html>"
		exit 1
	}
	if {[lempty $keys]} {
		puts stdout "Sorry, table $fqtn has no primary key field.  We can't handle that in this revision.  See the maintainer."
		puts stdout "<p> <hr> <p> </body> </html>"
                exit 1
        }

}
#
proc userComp {coln ustring} {

#       Ustring is a user sql expression given to us from a sciform
#       (normal forms do not use this feature)

	global colatt ofp 
#       The user string began with one of :
#               < > = <= >= between in 'one of'
#       and we are going to parse it a little and make it correct --
#       we permit the user to use expressions like "< 5 and > 2", which
#       must be padded out to "coln < 5 and coln > 2".
#       if the user forgot the "and" in a between expression, supply it.
#	shall we not fix the user's quoting?
#	we are not handling IN (ONE OF) yet?

	set q $colatt($coln,q)

	puts $ofp "enter userComp with $coln $ustring"
	set ops "<=>!"

	
        set word0 [lindex $ustring 0]

	set not ""
	if {$word0 == "not"} {
		set not not
		set ustring [lreplace $ustring 0 0]
		set word0 [lindex $ustring 0]
	}
        if {$word0 == "between"} {
			puts $ofp "ustring is $ustring"
                        set v1 [lindex $ustring 1]
                        if {([lsearch $ustring "and"] < 0) && \
				([lsearch $ustring "AND"] < 0)} {
                        set ustring [linsert $ustring 2 and]
                        }
                        set v2 [lindex $ustring 3]

			set v1 [format %s%s%s $q $v1 $q]
			set v2 [format %s%s%s $q $v2 $q]

                        if {$v1 > $v2} {
                                set ustring [lreplace $ustring 1 1 $v2]
                                set ustring [lreplace $ustring 3 3 $v1]
                        }
                set ustring "$coln $not $ustring"
                return "$ustring"
        }

	if {($word0 == "in") || \
		("[lindex $ustring 0] [lindex $ustring 1]" == "one of")} {
		if {$word0 == "in"} {
		} else {
			regsub -all "one of" $ustring "in" ustring
		}

		set vs [string trim [lrange $ustring 1 end] \(\)]

		if {[string first , $vs] >= 0} {
			set v [split $vs ,]
		}

		set ustring "$coln $not in ("
		foreach v $vs {
			append ustring [format "%s%s%s," $q $v $q]
		}
		set ustring [string trimright $ustring ,]
		append ustring ")"

		return $ustring

	}
#
	if {$word0 == "like"} {

		set v [lrange $ustring 1 end]

		if {$q != "'"} {
			return ""
		}

		if {[string first % $v] < 0} {	
			set v "$v%"
		}

		set ustring "$coln $not like $q$v$q"

		return $ustring

	}
#
	if {$word0 == "sounds_like"} {

		if {$q != "'"} {
			return ""
		}

		set v [lindex $ustring 1]

		set ustring "soundex($coln) $not like soundex($q$v$q)"

		return $ustring

	}

#	not in, between, or one-of, must be simple boolean

	puts $ofp "col $coln word0 is $word0"
	if {[string first [crange $word0 0 0] $ops] < 0} {
		set ustring "$coln = $q$ustring$q"
		return "$ustring"
	}	

        set lastword ""
	set us ""
        foreach word $ustring {
                if {[string toupper $lastword] == "AND"} {
                        if {$word != "$coln"} {
                                lappend us $coln
                        }
                
		}
		if {[string first [crange $lastword 0 0] $ops] >= 0} {
			set word [format %s%s%s $colatt($coln,q) $word $colatt($coln,q)]
		}
		lappend us $word
        	set lastword $word
        }

        return "($coln $us)"
}
#
#
#
proc makeSQLcmd {} {

	global p m s colatt ofp cols pcols fqtn csqlq sqlq keys alt_name 
	global usql usrsql gsqlq plot gcols fcols tsqlq pltype

#	These are the single-arg stat and math functions in Sybase 4.9
	set validstatf "sum min max avg" 
	set validmathf "abs acos asin atan ceiling cos cot degrees exp floor log log10 radians rand sign sin sqrt tan"

	set perrs ""
	set wher ""
	set key 0
	set pcols ""
	foreach c $cols {
		set err [catch {set p($c)}]
		if {!$err} {
			append pcols "$c "
			if {[lsearch $keys $c] >= 0} {
				set key 1
			}
		}
		set err [catch {set m($c)}]	
		if {$err} {
			puts $ofp "got error setting m(c)"
			continue
		}
		if {$m($c) != ""} {
		puts $ofp "about to exec userComp $c $m($c)"
		set uc [userComp $c $m($c)]
		if {$uc != ""} {
		append wher "$uc and "
		} else {
		append perrs "<br>Illegal expression $m(c)"
		}
		}
	}

	puts $ofp "pcols is $pcols"
# Nasty hackery:  if user did not select primary key column, force them
# to select it!
	if {!$key} {
		set pcols "$keys $pcols"
	}

	if {!$usrsql} {
		puts $ofp "set match from boxes"
		set wher [string trimright $wher "and "]
	} else {
		puts $ofp "set match from usql"
		set wher $usql
	}
			
	if {$wher != ""} {
		set wher "where $wher"
	}

# Now check whether we want a _stat_ function on any of pcols
# regular math functions must be handled otherwise

	set fcols ""
	set i 0
	foreach pc $pcols {
		set sf ""
		catch {set sf $s($pc)}
		if {$sf != ""} {
			if {[lsearch $validstatf $sf] >= 0} {
			if {$sf == "avg"} {
			lappend fcols "${sf}(convert(float,$pc))"
			} else {
			lappend fcols "${sf}($pc)"
			set alt_name($i) "${sf}($pc)"
			}
			} else {
			if {[lsearch $validmathf $sf] >= 0} {
			set pcols [lreplace $pcols $i $i "${sf}($pc)"]
			set alt_name($i) "${sf}($pc)"
			} else {
			puts stdout "Sorry, <b>$sf</b> is not a valid stat/math function in SQL;  function suppressed for column $pc.<br>"
			}
			}
		} 
		incr i
	}
# If you are doing stat funs you have to do all stat funs, no regular
# selects allowed!
	if {![lempty $fcols]} {
		set pcols $fcols
	}

	if {$pltype == "stats"} {
		foreach c $pcols {
			append scols "min($c) max($c) avg($c) "
		}
		set scols [join $scols { , }]
	} else {
	set scols [join $pcols { , }]
	}
	set sqlq "select $scols from $fqtn $wher"	
	set csqlq "select count(*) from $fqtn $wher"
	set tsqlq "select count(*) from $fqtn"

	if {$plot} {
	foreach gc $gcols {
		append gs "max($gc),min($gc),"
	}
	set gs [string trimright $gs ,]
	set gsqlq "select $gs from $fqtn $wher"
	}

	if {$perrs != ""} {
	puts stdout "ERRORS in <i>$sqlq</i>:<br>$perrs<hr>"
	}

}
#
#---------------------------------------------------------------MAIN
#
if [info exists env(CONTENT_LENGTH)] then {
    puts $ofp "Found CONTENT_LENGTH"
    set message [split [read stdin $env(CONTENT_LENGTH)] &]
    puts $ofp "got list $message"
    foreach pair $message {
      puts $ofp "got pair $pair"
      set name [lindex [split $pair =] 0]
      set val [lindex [split $pair =] 1]
      regsub -all {\+} $val { } val
      set val [unescape $val]
      set val [string trim $val]
      if [info exists value($name)] then {
          lappend value($name) $val
      } else {
          set value($name) $val
      }
    }
    # In this case one value came in 'input' field
#     set input $value(input)
#    puts $ofp "got input $input"
} else {
#    puts $ofp "Did not find CONTENT_LENGTH"
    # In this case value came in argv
    set input $argv
#    puts $ofp "got input $input"
}
puts stdout "Content-type: text/html\n"
puts stdout "<html><head><title>SQL query and results:</title></head><body>"
flush stdout

# puts $ofp "got value array indices [array names value]"
# possible value names are x (colname) y.col, p.col, m.col, s.col, sqlq, and fqtn
set err [catch {set value(fqtn)}]
if {$err} {
# 	puts $ofp "table name not set -- BAIL"
	exit 1
}
foreach ind [array names value] {

	puts $ofp "Got index $ind"
	lassign [split $ind .] typ cn

	if {$cn != ""} {
		puts $ofp "Got one for type $typ col $cn :  $value($ind)"
		set [set typ]($cn) "[string trimright [string trimleft $value($ind) \{ ] \} ]"
	}	
}
#
set err [catch {array names p}]
if {$err} {
	puts stdout "BADNESS No p array values at all!  Write to webmaster.<p>"
	puts stdout "<p> <hr> <p> </body> </html>"
	exit 1
} 
# are we plotting?
set uniq [getclock]
#
set datamartdir $wwdir/datamart
if {![info exists value(x)]} {
	set plot 0
}  else {
 set plot 1
}
#
catch {set pltype $value(pltype)}
if {![info exists pltype]} {
	set pltype line
}
if {$pltype == "data"} {
	set dfile "$datamartdir/$uniq.dat"
	set plot 0
}
if {$plot} {
set err [catch {array names y}] 
# if we are plotting, make sure we print all the we need to plot
if {$err} {
	puts stdout "OOPS, you specified X but no Y.  can't plot anything."
	set plot 0
} else {
	set xc $value(x)
	puts $ofp "PLOT selected"
	set plot 1
	set ys ""
	catch {set ycs [array names y]}
	if {$ycs == ""} {
	puts stdout "Sorry, you specified no Y values, no plot can be made.<BR>"
	set plot 0
	}
	set gcols "$xc $ycs"
	set pn [array names p]
	set oops [lindex [intersect3 $gcols $pn] 0]
	foreach o $oops {
		puts $ofp "plot Y $o without print?  add $o to prints"
		set p($o) 1
	}
}
}
#
set err [catch {array names m}]
if {$err} {
	puts stdout "BADNESS no m array values at all!  write to webmaster<p>"
	puts stdout "<p> <hr> <p> </body> </html>"
	exit 1
} 
#
lassign [split $value(fqtn) .] db usr tbl
set fqtn $value(fqtn)
#
set err [catch {set server $value(server)}]
if {$err} {
	puts stdout "No server value, BAIL!"
	puts stdout "<p> <hr> <p> </body> </html>"
	exit 1
}
if {$server == "UCO-ADMIN"} {
	set mb info
} else {
	set mb metabase
}
# dictionary
#       This is the name of the data dictionary table -- if any!
#       You want to uncomment the next line if you don't implement it
#       read only to all users
# set dictionary "NONE"
set dictionary $mb.dbo.data_dict
set datapaths $mb.dbo.datapaths
set hreftable $mb.dbo.hrefs
#
puts stdout "<h1> SQL and Results : table $tbl </h1> <p> <hr> "
flush stdout
set base $db
set user guest
set pass rsvp
set err [catch {set dbpipe1 [sybOpen $base $user $pass $server] } res]
if {$err} {
	puts stdout "<h2>Sorry</h2>"
	puts stdout "There was some kind of error accessing the Sybase server"
	puts stdout "called $server:<br>$res<br>$sybmsg(dberrstr)<br>"
	puts stdout "<hr></body></html>"
	exit 1
}
set dbpipe2 [sybOpen $base $user $pass $server] 
flush $ofp
#
# check for presence of fqtn in HREF datapaths
#
set sqlcmd "select * from $datapaths where fqtn = '$fqtn'"
set sqt $datapaths
set hrefs 0
set hcols ""
set hjoin 0
set hnote 0
set err [catch {doSQL 1} out]
if {$err} {
	puts $ofp "No datapaths select was possible:\n$out"
} else {
while {1} {
	set line [sybNext 1]
	if {$line == ""} {break}
	lassign $line fqt fld href lref hty
	if {$hty == "N"} {
		set hnote 1
		append ncols "$fld "
	} else {
		append hcols "$fld "
	}
	set colatt($fld,path) $href
	if {$fld == "HREFS"} {
		set hjoin 1
	}
	set hrefs 1
}
}
#
if {$hjoin} {
	if {[llength $hcols] > 1} {
	puts stdout "ERROR, you cannot use both simple datapaths refs and joins against the hrefs table.  See the maintainer."
	puts stdout "<p> <hr> <p> </body> </html>"
	}
}
#
puts $ofp "hrefs is $hrefs hcols is $hcols"
# puts stdout "argv is $argv"
# set sqlcmd "$argv"
#
set usrsql 0
set err [catch {set value(sqlq)}]
if {$err} {
	set usql ""
} else {
	set usql $value(sqlq)
}
if {$usql != ""} {
# set sqlq "select * from $fqtn where $usql"
# set csqlq "select count(*) from $fqtn where $usql"
puts $ofp "user sql is $usql"
set usrsql 1
}

getTableAttr 
#	echo "I get cols $cols"
#	foreach c $cols {
#		echo "Col $c type $colatt($c,type) size $colatt($c,size) null $colatt($c,null) key $colatt($c,key)<br>"
		
#	}
makeSQLcmd
#
puts stdout "Your query ($pltype):<br><i>$sqlq</i><hr>"
flush stdout
set sqlcmd "$tsqlq"
set sqt $fqtn
doSQL 1
set err [catch {set res [doSQL 1]} out]
if {$err} {
	puts stdout "Error in doSQL<pre>$out</pre>"
}
if {[lindex $res 0] == "ERROR"} {
	puts stdout "Sorry, you got a Sybase error:<br>"
	puts stdout "<i>$sybmsg(msgno) -- $sybmsg(msgtext)</i><br>"
	puts stdout "Try again."
	puts stdout "<p> <hr> <p> </body> </html>"
	exit
}
set tct [sybNext 1]
set sqlcmd "$csqlq"
doSQL 1
if {$err} {
	puts stdout "Error in doSQL<pre>$out</pre>"
}
if {[lindex $res 0] == "ERROR"} {
	puts stdout "Sorry, you got a Sybase error:<br>"
	puts stdout "<i>$sybmsg(msgno) -- $sybmsg(msgtext)</i><br>"
	puts stdout "Try again."
	puts stdout "<p> <hr> <p> </body> </html>"
	exit
}
set ct [sybNext 1]
set cp [format "%5.2f" [expr 100 * $ct.0/$tct]]
# only if no stat functions do we impose rowlimit (RIK bugreport 4/96)
# or if a data-only request
if {($ct > $rowlimit) && ([lempty $fcols]) && ($pltype != "data")} {
puts stdout "$ct records would be returned out of $tct ($cp percent)<br>"
puts stdout "but $ct exceeds the return record limit of $rowlimit <p>"
puts stdout "Write to the page-maintainer if you are frustrated by this limit."
puts stdout "<p> <hr> <p> </body> </html>"
exit
}
#
puts stdout "<p>$ct records out of $tct ($cp percent) match your specification."
#
if {$plot} {
	set dfile /tmp/plot_$uniq.dat
	set mfile /tmp/plot_$uniq.mgo
	set efile plot_$uniq.ps
	set pfile /tmp/plot_$uniq.ppm
	set gfile /tmp/plot_$uniq.gif
	set pdfp [open $dfile w]
	set pmfp [open $mfile w]
	for_file line $wwdir/plot/mongo.boiler1 {
		regsub -all EPSFILE $line $wwdir/tmp/$efile line
		regsub -all DATAFILE $line $dfile line
		if {$pltype == "scat"} {
			if {$line == "connect"} {
				continue
			}
		}
		puts $pmfp "$line"
	}
# get minima and maxima of all plotted columns
	set sqlcmd $gsqlq
	set sqt $fqtn
	doSQL 1
	set limits [sybNext 1]
	foreach c $gcols {
		set limax($c) [lvarpop limits]
		set limin($c) [lvarpop limits]
	}
} 
# that wrote the boilerplate mongo script up to the box creation.
#
if {$plot} {
puts $pdfp "SQLLab: $sqlq"
}
#
# if data only, take a very radical departure!
if {$pltype == "data"} {

# Fork off a process!

	puts $ofp "About to send exec_sql $server $base $user $pass $dfile $sqlq"
	if {[set childPid [fork]] == 0} {
#       child process actions
        close stdout
        close stdin
        catch {exec $wwdir/plot/exec_sql $server $base $user $pass $dfile $sqlq}
        exit
        }
#	parent process actions

	puts stdout "<p>Your data will be left in $datamartdir in file $dfile<p>"
	puts stdout "You should wait until you are sure it is no longer growing"
	puts stdout "before trying to use this file.  Try wc to see if the line"
	puts stdout "count matches the count of records returned..."
	puts stdout "</body></html>"
	exit

} 
#
set sqlcmd "$sqlq"
set sqt $fqtn
doSQL 1
#
if {[string compare $sybmsg(nextrow) NO_MORE_ROWS] != 0} {
	puts stdout "Your data:<br>"
	set fmt ""
	set d [replicate "----------------" 16]
	set txtindx ""
	puts stdout "<pre>"
######################################################################
# formatting the output gets really messy.
######################################################################
    if {[string length $fmt] == 0} {
      set col_names [sybcols $dbpipe1]
      if {[llength $col_names] != [llength $pcols]} {
	if {$pltype != "stats"} {
	puts stdout "number of columns returned does not match request."
	puts stdout "requested $pcols, got $col_names"
	}
      }
      # extract text columns into separate areas
      set i [lsearch $sybmsg(coltypes) text]
      while {$i >= 0} {
        lappend txtindx $i
        lappend txtcols [lvarpop col_names $i]
        lappend txtlens [lvarpop sybmsg(collengths) $i]
        lvarpop sybmsg(coltypes) $i
        set i [lsearch $sybmsg(coltypes) text]
      }
	set col_types $sybmsg(coltypes)
	set col_sizes $sybmsg(collengths)
	
	if {$plot} {
	set xi [expr [lsearch $gcols $xc] + 1]
	puts $pmfp "xcolumn $xi\nxlimits\ncolor 1\nbox 1 0 0 0\n"
	foreach gc $gcols {
		set gi [lsearch $col_names $gc]
		lappend gindxs $gi
		lappend gtypes [lindex $col_types $gi]
		lappend gsizes [lindex $col_sizes $gi]
	}
        set gfmt [formatCols $gcols $gtypes $gsizes]
	}
      set fmt [formatCols $col_names $col_types $col_sizes]
      set num [numericCols $sybmsg(coltypes)]
      set fmtc $fmt
      set i 0
# Check two things:  is any column an annotated column, and has any column
# lost its name due to a math function?
      foreach c $col_names {
        if {$hnote} {
	if {[lsearch $ncols $c] >= 0} {
		set cfmt [lindex $fmt $i]
		set fmtc [lreplace $fmtc $i $i "<a href=$colatt($c,path)/$fqtn.$c.html>$cfmt</a>"]
	}
	}
	if {$c == ""} {
		set an ""
		set err [catch {set an $alt_name($i)}]
		if {!$err} {
		set col_names [lreplace $col_names $i $i $an]
		}	
	}
	incr i
	}
      }
	if {$pltype != "stats"} {
	if {$plot} {
	lappend olines [eval format \"$fmtc\" $col_names]
	} else {
        puts stdout [eval format \"$fmtc\" $col_names]
	}
	}

	if {$pltype == "stats"} {
	set dash $pcols
	} else {
      	set dash $col_names
	}
        for {set i 0} {$i < [llength $dash]} {incr i} {
        	set dash [lreplace $dash $i $i $d]
      	}

	if {$pltype != "stats"} {
	if {$plot} {
	lappend olines [eval format \"$fmt\" $dash]
	} else {
        puts stdout [eval format \"$fmt\" $dash]
	}
	}

#	if there are simple hrefs, diddle the format statement to insert 
#	the path and the col name (as the href label).
# This ONLY works for plain hrefs if there is ALWAYS an href for EVERY record.
# otherwise you gotta do the pseudo-join, see below
	if {$hrefs} {
		set i 0
		foreach c $col_names {
			if {[lsearch $hcols $c] >= 0} {
				set cfmt [lindex $fmt $i]
				set fmt [lreplace $fmt $i $i "<a href=$colatt($c,path)/$cfmt>$c</a>"]
			}
		incr i
		}
	}

	set j 1
	if {$plot} {
	foreach c $gcols {
		puts $pdfp "ColLab$j: $c"
		puts $pdfp "ColMax$j: [format %.6f $limax($c)]"
		puts $pdfp "ColMin$j: [format %.6f $limin($c)]"
		if {[lsearch $ycs $c] >= 0} {
		puts $pmfp "plotone $j $j"
		}
	incr j
	}
	}
      }
#
# Now output format is set
######################################################################
if {$plot} {
puts $pdfp "DataBeginHere:"
for_file line $wwdir/plot/mongo.boiler2 {
	puts $pmfp "$line"
}
close $pmfp
}
set ct 0
while {1} {

	set row [sybNext 1]
	if {$row == ""} {break}

	if {$pltype == "stats"} {

		puts $ofp "Type is stats!  fmt was\n$fmt"
		puts $ofp "fmtc is\n$fmtc"
		puts $ofp "dash is\n$dash"
		flush $ofp
		foreach c $pcols {
			append fmtc1 "[lvarpop fmtc] "
			append fmtc2 "[lvarpop fmtc] "
			append fmtc3 "[lvarpop fmtc] "
			set cmin [lvarpop row]
			set cmax [lvarpop row]
			set cavg [lvarpop row]
			append minfmt "[lvarpop fmt] "
			append maxfmt "[lvarpop fmt] "
			append avgfmt "[lvarpop fmt] "
			append minima "$cmin "
			append maxima "$cmax "
			append averages "$cavg "
			append ranges "[expr $cmax - $cmin] "
		}

        puts stdout "[eval format \"---- $fmtc2\" $pcols]"
        puts stdout "[eval format \"---- $fmtc2\" $dash]\n"
      	puts stdout "[eval format \"MIN: $maxfmt\" $minima]\n"
      	puts stdout "[eval format \"MAX: $maxfmt\" $maxima]\n"
      	puts stdout "[eval format \"AVG: $maxfmt\" $averages]\n"
      	puts stdout "[eval format \"RNG: $maxfmt\" $ranges]\n"

	continue
	}
      	set txtdata ""
      	foreach i $txtindx {
        lappend txtdata [lvarpop row $i]
      	}
#       If there are numeric columns, expr eval them to take care of
#       scientific notation (ouch) which breaks in our format statement.
#	You will get into trouble when you get huge integers that Tcl
#	cannot represent in native int format.
        foreach ix $num {
		set v [lindex $row $ix]
                if {($v != "NULL") && ($v != "")} {
                set row [lreplace $row $ix $ix [expr [lindex $row $ix]]]
                }
        }
#	HACK CITY!  check for free-form hrefs.
	set hrs ""
	if {$hrefs} {
		if {$hjoin} {

			set path $colatt(HREFS,path)

			set kv [lindex $row 0]
			set sqlcmd "select hfil,htxt from $hreftable where fqtn = '$fqtn' and pkfv = '$kv'"
			set sqt $hreftable
			doSQL 2

			while {1} {
				set line [sybNext 2]
				if {$line == ""} {break}
				lassign $line hf ht
				append hrs "<a href=$path/$hf>$ht</a> "
			}
		}
	}
	if {$plot} {
		set grow ""
		foreach i $gindxs {
		lappend grow [lindex $row $i]
		}
		puts $pdfp "[eval format \"$gfmt\" $grow]"
		lappend olines "[eval format \"$fmt\" $row]"
	} else {
      		puts stdout "[eval format \"$fmt\" $row] $hrs"
	}
      incr ct
#      if {[llength $txtindx] > 0} {
#        set i 0
#        foreach t $txtcols {
#          .m.o.out insert end "" [lindex $txtcols $i]
#          .m.o.out insert end [string range $d 0 30]
#          eval .m.o.out insert end [split [lindex $txtdata $i] \n]
#          .m.o.out insert end ""
#        }
#	}
}
if {$plot} {
	close $pdfp
	system "/usr/local/bin/mongo < $mfile >/tmp/mongochat.$uniq"
#	puts stdout "<!comment [fmtclock [getclock]]>"
	flush stdout
#	system "/usr/local/bin/gs -sOUTPUTFILE=$pfile -sDEVICE=ppmraw -PAPERSIZE=letterR -r110x110 $efile < /dev/null"
#	puts stdout "<!comment [fmtclock [getclock]]>"
#	flush stdout
#	system "/usr/local/new/pbm/ppmtogif < $pfile > $gfile"
#	foreach i {m d e p} {
#		unlink [set ${i}file]
#	}
	puts stdout "</pre>"
	puts stdout "Here is your <a href=http://www.ucolick.org/tmp/$efile>plot</a>, followed by raw data:<br>" 
	puts stdout "<hr>"
	puts stdout "<pre>"
	flush stdout
	catch {puts stdout "[join $olines \n]"}
}
puts stdout </pre>

sybClose 1
#
puts stdout "<p> <hr> <p> </body> </html>"
# puts $ofp "<p> <hr> <p> </body> </html>"
#close $hfp
flush stdout
exit 
