NAME | SYNOPSIS | DESCRIPTION | IMPLEMENTATION | NOTES | WARNINGS | SEE ALSO | AUTHORS |
DbObject - This object serves as an interface between the object-oriented entity classes and the relational database. All object data is represented as a tcl array. This class translates between that array and the sybtcl input and output to the relational database.
DbObject | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Standard Public Interface
Private Interface
|
This procedure makes a connection to the database using the given dbuser, dbpass, and dsquery information. This same procedure can also be used to open multiple handles. If after the first invocation, no information is given, it will open another connection using the same information as given for the first handle. It returns the integer handle that is opened.
This procedure closes the sybase connection of the highest number handle and returns that closed handle. Any variable used to hold that handle is unset.
This procedure set the current handle to the given handle and the current handle is returned. This remains the current handle until another DbObject::SetHandle is issued or that handle is closed. If no handle parameter is given, the current handle is returned.
This procedure begins a transaction on the private variable handle. It enforces single layer transactions, preventing nesting.
This procedure commits a transaction on the private variable handle.
This procedure rolls back a transaction on the private variable handle. Since only single layer transactions are supported, it is important that control is returned immediately to the original transaction beginning. In doing this, rollback can be called multiple times without ill effect.
This procedure sets up a query that may involve a join across multiple tables, depending on how the object is defined. The given objref array specifies the attributes that are to be retrieved. The where clause and order by clause specify the filtering and ordering of the retrieved results. Since the where and order clauses are raw SQL strings, the application programmer is encouraged to define new member procedures so these SQL specifications remain private to this object. Note, the same objref given to this procedure must be used to retrieve the results with DbObject::Next.
This procedure retrieves the query results and places them in the objref array.. It is important that the given objref array is the same as the array used in DbObject::Query since the attributes given in the array determine what attributes get loaded with the returned values. Each attribute is given the value returned by the query and an oid is also assigned if the return code is 1.
This procedure counts the rows that match the given where statement. A key can be passed that is unique on the primary table in the case of a join to allow the count to be distinct. It
This procedure cancels the result of a query. It is used to discard unretrieved results if they are unneeded.
This procedure inserts the object into the database. It only inserts into one table, so each attribute in the objref array must belong to that table or an error will be generated. Only the attributes that are given in the objref array are inserted. If a table contains attributes that are not given and they do not accept null, an error will be generated The oid is returned. It
This procedure updates the object represented by the given objref array. This array must contain a valid oid. Only the values of the attributes represented in the array are updated in the database so parts of the object can be updated. This procedure performs the update to only one table as indicated in the entity this(table) variable. Therefore, all parameters in objref must belong to this table or an error will occur.
This procedure deletes the object represented by the given objref array. The only required attribute is the oidsince this tells the procedure which object to delete.
This procedure issues a Sybase sql request. If a timeout (in seconds) is given, it will return to the Tcl event loop every timeout seconds.
This procedure retrieves the sybase row from the private handle connection..
This procedure concatenates the given where clause with any internal entity where clause and returns the final string. If the entity has no internal where clause, the given clause is simply returned.
This procedure formats the given value according to the given type. If the type is an integer, return the given value without modification. If it is a text or datetime type, return the value enclosed in single quotes. If the keyword function is used, the value after the keyword is simply returned. This allows the use of the sybase functions. If any parameter is specified as {} or "", it will be recorded in the database as a null. Nulls are returned as {}.
This procedure builds the SQL update string . Only the attributes found in objref are built in the list. the oid is not included
This procedure builds the SQL select clause used in an SQL query string. Only the attributes found in objref are built in the list. The oid is not included.
This procedure builds the SQL attribute list used in an SQL insert string. Only the attributes found in objref are built in the list. the oid is not included.
This procedure builds the SQL value list used in an SQL insert string. Only the values of the attributes found in objref are built in the list. The oid is not included
The following messages will be logged.
namespace eval DbObject { variable handle variable _dbuser variable _dbpass variable _dsquery variable handleIndex 0 variable numHandles 0 variable waitInterval 30 variable minSleepInterval 5 variable tranflag }
proc DbObject::Connect { {dbuser {}} {dbpass {}} {dsquery {}}} { variable handle variable _dbuser variable _dbpass variable _dsquery variable handleIndex variable numHandles variable tranflag global env sybmsg scriptName logEntryLS DbgEntryExit "Entering DbObject::Connect" #Save the given information the first time if {$numHandles == 0} { #dbuser and dbpass must be given # if {[string compare $dbuser {}] == 0 || [string compare $dbpass {}] == 0} { # error "DbObject::Connect: Connect Failed: No values given for dbuser and dbpass" # return $numHandles # } if {[string equal $dbuser {}]} { if [catch {set dbuser $env(DBUSER)} result ] { error "DbObject::Connect failed: $result" } } if {[string equal $dbpass {}]} { if [catch {set dbpass $env(DBPASS)} result ] { error "DbObject::Connect failed: $result" } } set _dbuser $dbuser set _dbpass $dbpass set _dsquery $dsquery } else { if {[string compare $dbuser {}] == 0 || [string compare $dbpass {}] == 0} { set dbuser $_dbuser set dbpass $_dbpass } } #Open the database Connection #Set isolation level to allow dirty reads, so selects do not cause deadlocks if {[string compare $dsquery {}] == 0} { if [ catch { set handle([incr DbObject::numHandles]) [sybconnect $dbuser $dbpass $dsquery $scriptName] sybsql $handle($numHandles) "set transaction isolation level 0" } result ] { error "DbObject::Connect: Connect Failed: $result" return $numHandles } } else { if [ catch { set handle([incr DbObject::numHandles]) [sybconnect $dbuser $dbpass $dsquery] sybsql $handle($numHandles) "set transaction isolation level 0" } result ] { error "DbObject::Connect: Connect Failed: $result" return $numHandles } } #Only set this the first time if {$numHandles == 1} { set handleIndex 1 } #Initialize the transaction for the new connection set tranflag($numHandles) 0 #This variable must be set to retrieve any binary data as hex. set sybmsg(binaryashex) 1 set sybmsg(nullvalue) {} logEntryLS DbgEntryExit "Exiting DbObject::Connect with handle, $handle($handleIndex)" return $numHandles }
proc DbObject::Close {} { variable handle variable handleIndex variable numHandles if {$numHandles == 0} {return $numHandles} logEntryLS DbgEntryExit "Entering DbObject::Close" set closedHandle $numHandles catch { sybclose $handle($numHandles) unset handle($numHandles) if {$handleIndex == $numHandles} {incr DbObject::handleIndex -1} incr DbObject::numHandles -1 } logEntryLS DbgEntryExit "Exiting DbObject::Close" return $closedHandle }
proc DbObject::SetHandle { {index {}} } { variable handle variable dbuser variable dbpass variable dsquery variable handleIndex variable numHandles #Is this a read accessor? if {[string compare $index {}] == 0} {return $handleIndex} logEntryLS DbgEntryExit "Entering DbObject::SetHandle" if {$index > $numHandles || $index <= 0} { error "DbObject::SetHandle: The requested handle does not exist" return $handleIndex } set handleIndex $index logEntryLS DbgEntryExit "Exiting DbObject::SetHandle" return $handleIndex }
proc DbObject::BuildType {type value} { #Include the ability to specify characteristic functions if {[string compare [lindex $value 0] function] == 0} { return [lindex $value 1] } #Include the ability to specify null values if {[string compare $value {}] == 0} { return null } switch -- $type { Text - Datetime {return '$value'} EnumText {return '[Entity::Terse $value]'} Binary { return [format "%#8.8x" $value] } Integer {return $value} EnumInt {return [Entity::Terse $value]} default {return {}} } }
proc DbObject::BuildValue {class objref} { upvar $objref obj logEntryLS DbgEntryExit "Entering DbObject::BuildValue" #Skip the oid if it is a primary key if {[info exists obj(oid)]} { if {[string compare [Entity::GetAttribute $class oid key] Primary] == 0} { set prime_oid $obj(oid) unset obj(oid) } } #Used in insert to build the value list foreach attr [array names obj] { set type [Entity::GetAttribute $class $attr type] if {[info exists valueClause] == 0} { set valueClause [DbObject::BuildType $type $obj($attr)] } else { set valueClause [cconcat $valueClause ", [DbObject::BuildType $type $obj($attr)]"] } } # will not have a valueClause if obj emply, so I added the next statement - Doug. if {[info exists valueClause] == 0} {set valueClause {}} logEntryLS DbgAll "DbObject::BuildValue valueClause: $valueClause" logEntryLS DbgEntryExit "Exiting DbObject::BuildValue" if {[info exist prime_oid] } { set obj(oid) $prime_oid } return $valueClause }
proc DbObject::BuildUpdate {class objref} { upvar $objref obj logEntryLS DbgEntryExit "Entering DbObject::BuildUpdate" set updateClause {} #This prepares the update list for an update foreach attr [array names obj] { #Skip the oid, mdt, dt switch $attr { oid - dt - mdt {continue} } #Retrieve the attribute column name and remove the alias set column [Entity::GetAttribute $class $attr column] set column [lindex [split $column .] 1] set type [Entity::GetAttribute $class $attr type] if {[string compare $updateClause {}] == 0} { set updateClause "$column = [DbObject::BuildType $type $obj($attr)]" } else { set updateClause [cconcat $updateClause ", $column = [DbObject::BuildType $type $obj($attr)]"] } } logEntryLS DbgAll "DbObject::BuildUpdate updateClause: $updateClause" logEntryLS DbgEntryExit "Exiting DbObject::BuildUpdate" puts "inside buildupdate updateClause: $updateClause" return $updateClause }
proc DbObject::BuildWhere {class where} { upvar #0 $class\::this this logEntryLS DbgEntryExit "Entering DbObject::BuildWhere" #This builds the where clause for the Query if {[string compare $this(where) {}] != 0} { if {[string compare $where {}] == 0} { return $this(where) } else { return "$this(where) and $where" } } logEntryLS DbgAll "DbObject::BuildWhere whereClause: $where" logEntryLS DbgEntryExit "Exiting DbObject::BuildWhere" return $where }
proc DbObject::BuildSelect {class objref} { upvar $objref obj set selectClause {} logEntryLS DbgEntryExit "Entering DbObject::BuildSelect" #This builds the select clause for both select and insert foreach attr [array names obj] { #Skip the oid if {[string compare oid $attr] == 0} {continue} if {[string match $selectClause {} ]} { set selectClause [Entity::GetAttribute $class $attr column] } else { set selectClause [cconcat $selectClause ", [Entity::GetAttribute $class $attr column]"] } } logEntryLS DbgAll "DbObject::BuildSelect selectClause: $selectClause" logEntryLS DbgEntryExit "Exiting DbObject::BuildSelect" return $selectClause }
proc DbObject::BuildInsert {class objref} { upvar $objref obj upvar #0 $class\::attr classAttr logEntryLS DbgEntryExit "Entering DbObject::BuildInsert" #Skip the oid if it is a primary key if {[info exists obj(oid)]} { if {[string compare [Entity::GetAttribute $class oid key] Primary] == 0} { set prime_oid $obj(oid) unset obj(oid) } } #This builds the select clause for both select and insert foreach attr [array names obj] { #Retrieve the attribute column name and remove the alias set column [Entity::GetAttribute $class $attr column] set column [lindex [split $column .] 1] if {[info exists insertClause] == 0} { set insertClause $column } else { set insertClause [cconcat $insertClause ", $column"] } } logEntryLS DbgAll "DbObject::BuildInsert insertClause: $insertClause" logEntryLS DbgEntryExit "Exiting DbObject::BuildInsert" if {[info exist prime_oid] } { set obj(oid) $prime_oid } return $insertClause }
proc DbObject::Query { class objref {where {}} {order {}} } { variable waitInterval global sybmsg upvar $objref obj upvar #0 $class\::this this upvar #0 $class\::attr attr logEntryLS DbgEntryExit "Entering DbObject::Query" #filter out any attributes not belonging to this class array set filteredObj [Entity::ArrayCopy $class obj] set oidColumn [Entity::GetAttribute $class oid column] set selectString [DbObject::BuildSelect $class filteredObj] if [string match $selectString {} ] { set sqlString "select $oidColumn" } else { set sqlString "select $oidColumn, $selectString" } set sqlString [cconcat $sqlString " from $this(from)"] set where [DbObject::BuildWhere $class $where] if {[string compare $where {}] != 0} {set sqlString [cconcat $sqlString " where $where"]} if {[string compare $order {}] != 0} {set sqlString [cconcat $sqlString " order by $order"]} logEntryLS DbgMsg "DbObject::Query SqlString: $sqlString" if [catch {DbObject::Sql $sqlString $waitInterval} result] { error "DbObject::Query: $result: $sybmsg(msgtext)" } }
proc DbObject::GetListOf { class attr {where {}} {order {}} } { variable waitInterval upvar #0 $class\::this this logEntryLS DbgEntryExit "Entering DbObject::GetListOf" set valueList {} set selectString [Entity::GetAttribute $class $attr column] set sqlString "select distinct $selectString" set sqlString [cconcat $sqlString " from $this(from)"] set where [DbObject::BuildWhere $class $where] if {[string compare $where {}] != 0} {set sqlString [cconcat $sqlString " where $where"]} if {[string compare $order {}] != 0} {set sqlString [cconcat $sqlString " order by $order"]} logEntryLS DbgMsg "DbObject::Count SqlString: $sqlString" if [catch { DbObject::Sql $sqlString $waitInterval set row [DbObject::Nextrow] set x [llength $row] while {[llength $row] >0} { lappend valueList [lindex $row 0] set row [DbObject::Nextrow] } } result] { error "DbObject::GetListOf: $result: $result" } return $valueList }
proc DbObject::Count { class {where {}} {distinct *} } { variable waitInterval global sybmsg upvar #0 $class\::this this logEntryLS DbgEntryExit "Entering DbObject::Count" if {[string match $distinct *]} { set sqlString "select count(*)" } else { set sqlString "select count(distinct $distinct)" } set sqlString [cconcat $sqlString " from $this(from)"] set where [DbObject::BuildWhere $class $where] if {[string compare $where {}] != 0} {set sqlString [cconcat $sqlString " where $where"]} logEntryLS DbgMsg "DbObject::Count SqlString: $sqlString" if [catch { DbObject::Sql $sqlString $waitInterval set count [DbObject::Nextrow] } result] { error "DbObject::Count: $result: $sybmsg(msgtext)" } return $count }
proc DbObject::Next { class objref } { upvar $objref obj global sybmsg logEntryLS DbgEntryExit "Entering DbObject::Next" #filter out any attributes not belonging to this class array set filteredObj [Entity::ArrayCopy $class obj] #REG_ROW, NO_MORE_ROWS - REG_ROW will be returned when the array is filled set row [DbObject::Nextrow] logEntryLS DbgMsg "Returned Row: $row" switch $sybmsg(nextrow) { REG_ROW { set obj(oid) [lindex $row 0] set i 1 foreach attr [array names filteredObj] { #Skip the oid if {[string compare oid $attr] == 0} {continue} #Check for binary switch -- [Entity::GetAttribute $class $attr type] { Binary { set obj($attr) [join [list 0x [string trim [lindex $row $i]]] {}] } EnumText - EnumInt { set obj($attr) [Entity::EnumValue [Entity::GetAttribute $class $attr enum] [lindex $row $i]] } default { set obj($attr) [lindex $row $i] } } incr i } logEntryLS DbgEntryExit "Exiting DbObject::Next after retrieving oid, $obj(oid) " return 1 } default { logEntryLS DbgEntryExit "Exiting DbObject::Next: No row retrieved" return 0 } } }
proc DbObject::Cancel {} { variable handle variable handleIndex variable numHandles if {$numHandles != 0} { sybcancel $handle($handleIndex) } }
proc DbObject::Insert { class objref } { variable handle variable handleIndex variable waitInterval global sybmsg upvar $objref obj upvar #0 $class\::this this logEntryLS DbgEntryExit "Entering DbObject::Insert" set attrList [DbObject::BuildInsert $class obj] set valueList [DbObject::BuildValue $class obj] set sqlString "insert into [lindex $this(table) 0] ($attrList) values ($valueList)" logEntryLS DbgMsg "DbObject::Insert SqlString: $sqlString" if [ catch { DbObject::Sql $sqlString $waitInterval } result ] { error "DbObject::Insert: $result: $sybmsg(msgtext)" } if {[string compare [Entity::GetAttribute $class oid key] Primary] == 0} { DbObject::Sql "select @@identity" $waitInterval set obj(oid) [lindex [sybnext $handle($handleIndex)] 0] } logEntryLS DbgEntryExit "Exiting DbObject::Insert with oid, $obj(oid)" return $obj(oid) }
proc DbObject::Update {class objref} { variable waitInterval global sybmsg upvar $objref obj upvar #0 $class\::this this logEntryLS DbgEntryExit "Entering DbObject::Update" set setList [DbObject::BuildUpdate $class obj] #If there is no setlist, we are done if {[string compare $setList {}] == 0} { return } set column [Entity::GetAttribute $class oid column] set column [lindex [split $column .] 1] set where "$column = $obj(oid)" set sqlString "update [lindex $this(table) 0] set $setList from $this(table) where $where" puts "inside the Update proc sqlString: $sqlString" logEntryLS DbgMsg "DbObject::Update SqlString: $sqlString" if [ catch { DbObject::Sql $sqlString $waitInterval } result ] { error "DbObject::Update: $result: $sybmsg(msgtext)" } logEntryLS DbgEntryExit "Exiting DbObject::Update" }
proc DbObject::Delete {class objref} { variable waitInterval global sybmsg upvar $objref obj upvar #0 $class\::this this logEntryLS DbgEntryExit "Entering DbObject::Delete" set where "[Entity::GetAttribute $class oid column] = $obj(oid)" set sqlString "delete [lindex $this(table) 0] from $this(table) where $where" logEntryLS DbgMsg "DbObject::Delete SqlString: $sqlString" if [ catch { DbObject::Sql $sqlString $waitInterval} result ] { error "DbObject::Delete: $result: $sybmsg(msgtext)" } logEntryLS DbgEntryExit "Exiting DbObject::Delete" }
proc DbObject::Begin {} { variable waitInterval variable tranflag variable handleIndex global sybmsg logEntryLS DbgEntryExit "Entering DbObject::Begin" if {$tranflag($handleIndex) == 0} { if [catch {DbObject::Sql "begin transaction" $waitInterval} result] { error "DbObject::Begin: $result: $sybmsg(msgtext)" return } } incr tranflag($handleIndex) logEntryLS DbgMsg "Begin Transaction" logEntryLS DbgEntryExit "Exiting DbObject::Begin" }
proc DbObject::Commit {} { variable waitInterval variable tranflag variable handleIndex global sybmsg logEntryLS DbgEntryExit "Entering DbObject::Commit" if {$tranflag($handleIndex) == 1} { if [catch {DbObject::Sql "commit transaction" $waitInterval} result] { error "DbObject::Begin: $result: $sybmsg(msgtext)" return } } if {$tranflag($handleIndex) > 0 } { incr tranflag($handleIndex) -1 } logEntryLS DbgMsg "Commit Transaction" logEntryLS DbgEntryExit "Exiting DbObject::Commit" }
proc DbObject::Rollback {} { variable waitInterval variable tranflag variable handleIndex global sybmsg logEntryLS DbgEntryExit "Entering DbObject::Rollback" if {$tranflag($handleIndex) > 0} { if [catch {DbObject::Sql "rollback transaction" $waitInterval} result] { error "DbObject::Begin: $result: $sybmsg(msgtext)" return } set tranflag($handleIndex) 0 } logEntryLS DbgMsg "Rollback Transaction" logEntryLS DbgEntryExit "Exiting DbObject::Rollback" }
proc DbObject::Sql {sqlString {timeout -1}} { variable handle variable handleIndex variable minSleepInterval global sybmsg logEntryLS DbgEntryExit "Entering DbObject::Sql $sqlString, $timeout" logEntryLS DbgMsg "DbObject::Sql SqlString: $sqlString" set start [clock seconds] set numDeadlocks 0 #A timeout of -1 is equivalent to synchronous #If not -1, it is a timeout in seconds if {[string compare $timeout -1] != 0} { set msInterval [expr $timeout * 1000] } else {set msInterval -1} #Retry on deadlocks until successful while {1} { #Send an asyncronous query if [catch { set rtn [sybsql $handle($handleIndex) $sqlString] #set rtn [sybsql $handle($handleIndex) $sqlString async] #Wait until the results are ready, but take enter the event loop after timeout seconds # while { [string compare [sybpoll $handle $msInterval] $handle] != 0 } { # update ;# catch up on HeartBeats, etc. # logEntryLS Warning "DbObject::Sybsql: Database not responding after $timeout seconds!" # } } result] { #Check for the deadlock msgno and retry switch -exact -- $sybmsg(msgno) { 1205 { incr numDeadlocks logEntryLS DbgMsg "DbObject::Sql: DEADLOCK retry, $numDeadlocks" #Alternate wait times before the retry if {[expr $numDeadlocks % 2]} { sleep $minSleepInterval } else { sleep [expr $minSleepInterval * 2] } continue } default {error "DbObject::Sybsql: $result: $sybmsg(msgtext)"} } } else { logEntryLS DbgEntryExit "Exiting DbObject::Sql with $numDeadlocks DEADLOCK retries" return } } }
proc DbObject::Nextrow {} { variable handle variable handleIndex global sybmsg return [sybnext $handle($handleIndex)] }