Hi everybody

I'm sending on the list a file including patches for "dio" and "session"
and some new files that add support for Mysql and Oracle.

These patches are 99% work of Arnulf Wiedemann. I tested
the code on Mysql (Arnulf is intensively using the module for Oracle)
and made some minor changes.
Mysql doesn't remove the expired sessions rows from
rivet_session_cache: mysql accepts the syntax 'ON DELETE CASCADE'
for portability when creating the database, but it takes no action
upon deletion of an expired session from 'rivet_session'. Actual
support for this hasn't  been added as of version 5.0. In order
to suggest a workaround I added the file
'session-purge-mysql.sql' with a sql statement that
could be run from cron job. Yet the problem of a wider
portabilty of the do_garbage_collect method is open.

regards

-- Massimo Manghi

Index: rivet/packages/session/session-class.tcl
===================================================================
--- rivet/packages/session/session-class.tcl	(revision 405803)
+++ rivet/packages/session/session-class.tcl	(working copy)
@@ -102,6 +102,8 @@
 
     constructor {args} {
 	eval configure $args
+    	$dioObject registerSpecialField rivet_session session_update_time NOW
+	$dioObject registerSpecialField rivet_session session_start_time NOW
     }
 
     method status {args} {
@@ -149,7 +151,7 @@
 
 	set sessionIdKey "$uniqueID[clock clicks][pid]$args[clock seconds]$scrambleCode[get_entropy_bytes]"
 	debug "gen_session_id - feeding this to md5: '$sessionIdKey'"
-	return [::md5::md5 $sessionIdKey]
+	return [::md5::md5 -hex -- $sessionIdKey]
     }
 
     #
@@ -159,7 +161,13 @@
     #
     method do_garbage_collection {} {
 	debug "do_garbage_collection: performing garbage collection"
-	set result [$dioObject exec "delete from $sessionTable where timestamp 'now' - session_update_time > interval '$gcMaxLifetime seconds';"]
+#	set result [$dioObject exec "delete from $sessionTable where timestamp 'now' - session_update_time > interval '$gcMaxLifetime seconds';"]
+	set del_cmd "delete from $sessionTable where "
+	append del_cmd [$dioObject makeDBFieldValue $sessionTable session_update_time now SECS]
+	append del_cmd " - [$dioObject makeDBFieldValue $sessionTable session_update_time {} SECS]"
+	append del_cmd " > $gcMaxLifetime"
+	debug "do_garbage_collection: > $del_cmd  <"
+	set result [$dioObject exec $del_cmd]
 	$result destroy
     }
 
@@ -294,14 +302,14 @@
     #
     method store {packageName key data} {
 	set a(session_id) [id]
-	set a(package) $packageName
-	set a(key) $key
+	set a(package_) $packageName
+	set a(key_) $key
 
 	regsub -all {\\} $data {\\\\} data
 	set a(data) $data
 
-	debug "store session data, package '$packageName', key '$key', data '$data'"
-	set kf [list session_id package key]
+	debug "store session data, package_ '$packageName', key_ '$key', data '$data'"
+	set kf [list session_id package_ key_]
 
 	if {![$dioObject store a -table $sessionCacheTable -keyfield $kf]} {
 	    puts "Failed to store $sessionCacheTable '$kf'"
@@ -315,21 +323,21 @@
     #   for this session
     #
     method fetch {packageName key} {
-	set kf [list session_id package key]
+	set kf [list session_id package_ key_]
 
 	set a(session_id) [id]
-	set a(package) $packageName
-	set a(key) $key
+	set a(package_) $packageName
+	set a(key_) $key
 
 	set key [$dioObject makekey a $kf]
 	if {![$dioObject fetch $key a -table $sessionCacheTable -keyfield $kf]} {
 	    status [$dioObject errorinfo]
 	    puts "error: [$dioObject errorinfo]"
-	    debug "fetch session data failed, package '$packageName', key '$key', error '[$dioObject errorinfo]'"
+	    debug "fetch session data failed, package_ '$packageName', key_ '$key', error '[$dioObject errorinfo]'"
 	    return ""
 	}
 
-	debug "fetch session data succeeded, package '$packageName', key '$key', result '$a(data)'"
+	debug "fetch session data succeeded, package_ '$packageName', key_ '$key', result '$a(data)'"
 
 	return $a(data)
     }
@@ -443,6 +451,7 @@
     method debug {message} {
 	if {$debugMode} {
 	    puts $debugFile "$this (debug) $message<br>"
+	    flush $debugFile
 	}
     }
 }
Index: rivet/packages/dio/dio_Mysql.tcl
===================================================================
--- rivet/packages/dio/dio_Mysql.tcl	(revision 405803)
+++ rivet/packages/dio/dio_Mysql.tcl	(working copy)
@@ -23,8 +23,9 @@
 	inherit Database
 
 	constructor {args} {eval configure $args} {
-	    if {[catch {package require Mysqltcl}] \
-		    && [catch {package require mysql}]} {
+	    if {       [catch {package require Mysqltcl}]   \
+	    	    && [catch {package require mysqltcl}]   \
+		    && [catch {package require mysql}   ] } {
 		return -code error "No MySQL Tcl package available"
 	    }
 
@@ -105,6 +106,51 @@
 	    return $conn
 	}
 
+	method makeDBFieldValue {table_name field_name val {convert_to {}}} {
+		if {[info exists specialFields([EMAIL PROTECTED])]} {
+		    switch $specialFields([EMAIL PROTECTED]) {
+			DATE {
+			  	set secs [clock scan $val]
+				set my_val [clock format $secs -format {%Y-%m-%d}]
+				return "DATE_FORMAT('$my_val', '%Y-%m-%d')"
+			  }
+			DATETIME {
+			  	set secs [clock scan $val]
+				set my_val [clock format $secs -format {%Y-%m-%d %T}]
+				return "DATE_FORMAT('$my_val', '%Y-%m-%d %T')"
+			  }
+			NOW {
+			    switch $convert_to {
+				SECS {
+				    if {[::string compare $val "now"] == 0} {
+					set	secs    [clock seconds]
+					set	my_val  [clock format $secs -format {%Y%m%d%H%M%S}]
+					return	$my_val
+				    } else {
+					return  "DATE_FORMAT(session_update_time,'%Y%m%d%H%i%S')"
+				    }
+				}
+				default {
+				    if {[::string compare $val, "now"] == 0} {
+			  		set secs [clock seconds]
+				    } else {
+					set secs [clock scan $val]
+				    }
+				    set my_val [clock format $secs -format {%Y-%m-%d %T}]
+				    return "DATE_FORMAT('$my_val', '%Y-%m-%d %T')"
+				}
+			    }
+			}
+			default {
+			  	# no special code for that type!!
+				return "'[quote $val]'"
+			}
+		    }
+		} else {
+			return "'[quote $val]'"
+		}
+	}
+
 	public variable db "" {
 	    if {[info exists conn]} {
 		mysqluse $conn $db
@@ -130,7 +176,7 @@
 	method nextrow {} {
 	    return [mysqlnext $resultid]
 	}
-
+	
     } ; ## ::itcl::class MysqlResult
 
 }
Index: rivet/packages/dio/pkgIndex.tcl
===================================================================
--- rivet/packages/dio/pkgIndex.tcl	(revision 405803)
+++ rivet/packages/dio/pkgIndex.tcl	(working copy)
@@ -1,5 +1,6 @@
 package ifneeded DIO 1.0 [list source [file join $dir dio.tcl]]
 package ifneeded DIODisplay 1.0 [list source [file join $dir diodisplay.tcl]]
-package ifneeded dio_Mysql 0.1 [list source [file join $dir dio_Mysql.tcl]]
+package ifneeded dio_Mysql 0.2 [list source [file join $dir dio_Mysql.tcl]]
 package ifneeded dio_Postgresql 0.1 [list source [file join $dir dio_Postgresql.tcl]]
 package ifneeded dio_Sqlite 0.1 [list source [file join $dir dio_Sqlite.tcl]]
+package ifneeded dio_Oracle 0.2 [list source [file join $dir dio_Oracle.tcl]]
Index: rivet/packages/dio/dio.tcl
===================================================================
--- rivet/packages/dio/dio.tcl	(revision 405803)
+++ rivet/packages/dio/dio.tcl	(working copy)
@@ -138,7 +138,7 @@
 			# is appended with a "field LIKE value"
 
 			if {[::string first {%} $elem] != -1} {
-			    append req " $field LIKE '[quote $elem]'"
+			    append req " $field LIKE [makeDBFieldValue $myTable $field $elem]"
 		        } elseif {[regexp {^([<>]) *([0-9.]*)$} $elem _ fn val]} {
 			    # value starts with <, or >, then space, 
 			    # and a something
@@ -148,7 +148,7 @@
 			    append req " $field$fn$val"
 			} else {
 			    # otherwise it's a straight key=value comparison
-			    append req " $field='[quote $elem]'"
+			    append req " $field=[makeDBFieldValue $myTable $field $elem]"
 			}
 
 			continue
@@ -171,14 +171,14 @@
 	upvar 1 $arrayName array
 
 	if {[lempty $myTable]} { set myTable $table }
+	set vals [::list]
+	set vars [::list]
 	foreach field $fields {
 	    if {![info exists array($field)]} { continue }
-	    append vars "$field,"
-	    append vals "'[quote $array($field)]',"
+	    lappend vars "$field"
+	    lappend vals "[makeDBFieldValue $myTable $field $array($field)]"
 	}
-	set vals [::string range $vals 0 end-1]
-	set vars [::string range $vars 0 end-1]
-	return "insert into $myTable ($vars) VALUES ($vals)"
+	return "insert into $myTable ([join $vars {,}]) VALUES ([join $vals {,}])"
     }
 
     #
@@ -194,12 +194,12 @@
     protected method build_update_query {arrayName fields {myTable ""}} {
 	upvar 1 $arrayName array
 	if {[lempty $myTable]} { set myTable $table }
+	set string [::list]
 	foreach field $fields {
 	    if {![info exists array($field)]} { continue }
-	    append string "$field='[quote $array($field)]',"
+	    lappend string "$field=[makeDBFieldValue $myTable $field $array($field)]"
 	}
-	set string [::string range $string 0 end-1]
-	return "update $myTable SET $string"
+	return "update $myTable SET [join $string {,}]"
     }
 
     #
@@ -235,19 +235,13 @@
 	## If we're not using multiple keyfields, just return a simple
 	## where clause.
 	if {[llength $myKeyfield] < 2} {
-	    return " WHERE $myKeyfield = '[quote $myKey]'"
+	    return " WHERE $myKeyfield = [makeDBFieldValue $table $myKeyfield $myKey]"
 	}
 
 	# multiple fields, construct it as a where-and
-	set first 1
-	set req ""
+	set req " WHERE 1 = 1"
 	foreach field $myKeyfield key $myKey {
-	    if {$first} {
-		append req " WHERE $field='[quote $key]'"
-		set first 0
-	    } else {
-		append req " AND $field='[quote $key]'"
-	    }
+	    append req " AND $field=[makeDBFieldValue $table $field $key]"
 	}
 	return $req
     }
@@ -532,7 +526,7 @@
     #
     # insert - a pure insert, without store's somewhat clumsy
     # efforts to see if it needs to be an update rather than
-    # an insert
+    # an insert -- this shouldn't require fields, it's broken
     #
     method insert {table arrayName} {
 	upvar 1 $arrayName $arrayName $arrayName array
@@ -602,6 +596,14 @@
 	return [string "select count(*) from $myTable"]
     }
 
+    method makeDBFieldValue {table_name field_name val} {
+    	return "'[quote $val]'"
+    }
+
+    method registerSpecialField {table_name field_name type} {
+    	set specialFields([EMAIL PROTECTED]) $type
+    }
+
     ##
     ## These are methods which should be defined by each individual database
     ## interface class.
@@ -611,6 +613,7 @@
     method exec    {args} {}
     method nextkey {args} {}
     method lastkey {args} {}
+    method now {} {}
 
     ##
     ## Functions to get and set public variables.
@@ -627,6 +630,8 @@
     method host {{string ""}} { configure_variable host $string }
     method port {{string ""}} { configure_variable port $string }
 
+    protected variable specialFields
+
     public variable interface	""
     public variable errorinfo	""
 
# dio_Mysql.tcl -- Mysql backend.

# Copyright 2002-2004 The Apache Software Foundation

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at

#	http://www.apache.org/licenses/LICENSE-2.0

# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# $Id: dio_Oracle.tcl 265421 2004-10-29 20:17:54Z karl $

package provide dio_Oracle 0.1

namespace eval DIO {
    ::itcl::class Oracle {
	inherit Database

	constructor {args} {eval configure $args} {
	    if {[catch {package require Oratcl}]} {
		return -code error "No Oracle Tcl package available"
	    }

	    eval configure $args

	    if {[lempty $db]} {
		if {[lempty $user]} {
		    set user $::env(USER)
		}
		set db $user
	    }
	}

	destructor {
	    close
	}

	method open {} {
	    set command "::oralogon"

	    if {![lempty $user]} { append command " $user" }
	    if {![lempty $pass]} { append command "/$pass" }
	    if {![lempty $host]} { append command "@$host" }
	    if {![lempty $port]} { append command -port $port }

	    if {[catch $command error]} { return -code error $error }

	    set conn $error

	    if {![lempty $db]} { 
	    	# ??? mysqluse $conn $db 
	    }
	}

	method close {} {
	    if {![info exists conn]} { return }
	    catch {::oraclose $conn}
	    unset conn
	}

	method exec {req} {
	    if {![info exists conn]} { open }

	    set _cur [::oraopen $conn]
	    set cmd ::orasql
	    set is_select 0
	    if {[::string tolower [lindex $req 0]] == "select"} {
		set cmd ::orasql
		set is_select 1
	    }
	    set errorinfo ""
#puts "ORA:$is_select:$req:<br>"
	    if {[catch {$cmd $_cur $req} error]} {
#puts "ORA:error:$error:<br>"
		set errorinfo $error
		catch {::oraclose $_cur}
		set obj [result $interface -error 1 -errorinfo [::list $error]]
		return $obj
	    }
	    if {[catch {::oracols $_cur name} fields]} { set fields "" }
	    ::oracommit $conn
	    set my_fields $fields
	    set fields [::list]
	    foreach field $my_fields {
		set field [::string tolower $field]
		lappend fields $field
	    }
	    set error [::oramsg $_cur rows]
	    set res_cmd "result"
	    lappend res_cmd $interface -resultid $_cur 
	    lappend res_cmd -numrows [::list $error] -fields [::list $fields]
	    lappend res_cmd -fetch_first_row $is_select
	    set obj [eval $res_cmd]
	    if {!$is_select} {
		::oraclose $_cur
	    }
	    return $obj
	}

	method lastkey {} {
	    if {![info exists conn]} { return }
	    return [mysqlinsertid $conn]
	}

	method quote {string} {
	    regsub -all {'} $string {\'} string
	    return $string
	}

	method sql_limit_syntax {limit {offset ""}} {
	    # temporary
	    return ""
	    if {[lempty $offset]} {
		return " LIMIT $limit"
	    }
	    return " LIMIT [expr $offset - 1],$limit"
	}

	method handle {} {
	    if {![info exists conn]} { open }
	    return $conn
	}

	method makeDBFieldValue {table_name field_name val {convert_to {}}} {
		if {[info exists specialFields([EMAIL PROTECTED])]} {
			switch $specialFields([EMAIL PROTECTED]) {
			DATE {
			  	set secs [clock scan $val]
				set my_val [clock format $secs -format {%Y-%m-%d}]
				return "to_date('$my_val', 'YYYY-MM-DD')"
			  }
			DATETIME {
			  	set secs [clock scan $val]
				set my_val [clock format $secs -format {%Y-%m-%d %T}]
				return "to_date('$my_val', 'YYYY-MM-DD HH24:MI:SS')"
			  }
			NOW {
			    switch $convert_to {
				SECS {
				    if {[::string compare $val "now"] == 0} {
					set secs [clock seconds]
				    	set my_val [clock format $secs -format {%Y%m%d%H%M%S}]
					return $my_val
				    } else {
				    	return "to_char($field_name, 'YYYYMMDDHH24MISS')"
				    }
				}
				default {
				    if {[::string compare $val "now"] == 0} {
					set secs [clock seconds]
				    } else {
					set secs [clock scan $val]
				    }
				    set my_val [clock format $secs -format {%Y-%m-%d %T}]
				    return "to_date('$my_val', 'YYYY-MM-DD HH24:MI:SS')"
				}
			    }
			}
			default {
			  	# no special cod for that type!!
				return "'[quote $val]'"
			  }
			}
		} else {
			return "'[quote $val]'"
		}
	}

	public variable db "" {
	    if {[info exists conn]} {
		mysqluse $conn $db
	    }
	}

	public variable interface	"Oracle"
	private variable conn
	private variable _cur

    } ; ## ::itcl::class Mysql

    ::itcl::class OracleResult {
	inherit Result

	public variable fetch_first_row 0
	private variable _data ""
	private variable _have_first_row 0

	constructor {args} {
	    eval configure $args
	    if {$fetch_first_row} {
		if {[llength [nextrow]] == 0} {
			set _have_first_row 0
			numrows 0
		} else {
			set _have_first_row 1
			numrows 1
		}
	    }
	    set fetch_first_row 0
	}

	destructor {
		if {[string length $resultid] > 0} {
			catch {::oraclose $resultid}
		}
	}

	method nextrow {} {
	    if {[string length $resultid] == 0} {
		return [::list]
	    }
	    if {$_have_first_row} {
		set _have_first_row 0
		return $_data
	    }
	    set ret [::orafetch $resultid -datavariable _data]
	    switch $ret {
	    0 {
		return $_data
	      }
	    1403 {
		::oraclose $resultid
		set resultid ""
		return [::list]
	      }
	    default {
		# FIXME!! have to handle error here !!
		return [::list]
	      }
	    }
	}
	
    } ; ## ::itcl::class OracleResult

}
--
--  Define SQL tables for session management code
--
--  Author: Arnulf  (minor changes by Massimo Manghi)
--  
--  02 May 2006 
--

DROP TABLE IF EXISTS `rivet_session`;
create table rivet_session (
    ip_address		varchar(16) default NULL,
    session_start_time	datetime    default NULL,
    session_update_time	datetime    default NULL,
    session_id		varchar(64) NOT NULL default '',
    PRIMARY KEY	(session_id)
); 

DROP TABLE IF EXISTS `rivet_session_cache`;
create table rivet_session_cache(
    session_id		varchar(128)	default NULL REFERENCES rivet_session(session_id) ON DELETE CASCADE,
    package_		varchar(64)	default NULL,
    key_		varchar(128)	default NULL,
    data                varchar(256)	default NULL,

    UNIQUE KEY riv_sess_cache_ix( session_id, key_ ),
    KEY rivet_session_cache_idx (session_id)
);
-- create index rivet_session_cache_idx ON rivet_session_cache( session_id );

-- 
--  Session management database creation for Oracle
-- 
--  Arnulf 
--
CREATE TABLE rivet_session
    (ip_address                     VARCHAR2(23) DEFAULT NULL,
    session_start_time             DATE DEFAULT NULL,
    session_update_time            DATE DEFAULT NULL,
    session_id                     VARCHAR2(50) NOT NULL
    )
/

ALTER TABLE rivet_session ADD PRIMARY KEY (session_id)

/

CREATE TABLE rivet_session_cache
    (session_id                     VARCHAR2(50) DEFAULT NULL,
    package_                       VARCHAR2(100) DEFAULT NULL,
    key_                           VARCHAR2(50) DEFAULT NULL,
    data                           VARCHAR2(255) DEFAULT NULL
  )
/

CREATE UNIQUE INDEX riv_sess_cache_ix ON rivet_session_cache
  (
    session_id,
    package_,
    key_
  )
/

CREATE INDEX rivet_session_cache_idx ON rivet_session_cache
  (
    session_id
  )
--  Note
--
--  9 May 2006
--
--  Though Mysql sql interpreter supports the specification 'ON CASCADE DELETE' for CREATE TABLE, as of
--  version 5.0 this is only for sql code portability: deleting a row in rivet_session doesn't 
--  imply Mysql removes the row in rivet_session_cache sharing the same session_id.
--  This table gets progressively cluttered with rows referring to sessions that are long dead.
--  
--  As a possible workaround the programmer may build a cron procedure that runs the following
--  sql statement.

delete rivet_session_cache from rivet_session_cache  left join rivet_session as t2 using(session_id) where t2.session_id is null;

--  I don't see any cleaner approach unless the code for the garbage collection in the Session class
--  is modified for sake of portability. (Massimo Manghi)


---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]

Reply via email to