OOPish Interface to Tsunami
Posted: Fri Sep 03, 2004 12:52 pm
I have been struggling with DBs - could not get MyDB to work, so went back to Tsunami's horrible interface. Having come to grips with OOP while investigating MyDB, I thought this would be a good way to use Tsunami.
I hope this may be useful to someone. Thanks to blueb, ppjm99 and the.weavster, who will probably recognise their parts of the code. Disclaimer - I am fairly new to PB, so apologies may be necessary and will be immediately forthcoming. I do have a more complex example with gadgets if anyone's interested.
This is the example program to use the include file:
This is the include file, which is totally incomprehensible, but you don't need to understand it to use it!:
I hope this may be useful to someone. Thanks to blueb, ppjm99 and the.weavster, who will probably recognise their parts of the code. Disclaimer - I am fairly new to PB, so apologies may be necessary and will be immediately forthcoming. I do have a more complex example with gadgets if anyone's interested.
This is the example program to use the include file:
Code: Select all
; Author: carolight with help from code from the.weavster and blueb and ppjm99
; Date: 3rd September 2004
; Written in PB 3.90
; The Tsunami database is free and available from
; http://www.trm-ug.com
; To be able to run this example, you need TRM.DLL in your current folder
; This is the example for using the Tsunami OOPish Interface
; Limitations:
; Only 1 field for 1 index (no concatenated fields)
; Only String, Long Integer and Float fields allowed
; Floats have not been perfected to show the exact right float no (inherent difficulty with floats)
; Data Compression is not allowed.
; Tsunami allows much flexibility, as in page size, that are not (yet?) coded in the interface.
; Tsunami allows variable length fields and records. This interface only allows fixed length.
; Indexes are case sensitive
; Quick start to creating your database interface:
; IncludeFile "TRMInterface.pb"
; Open the TRM library with TRMInitialize()
; For each file:
; 1. Create a structure (+ associated variable) with
; all the fields you need. (book.book_Record)
; 2. Create a structure variable using structure Tsunami (BookFile.Tsunami)
; This variable will hold the tsunami interface
; 3. Initialize the interface variable with TRMCreate(Fields Structure, Filename)
; (BookFile.Tsunami = TRMCreate(BookRec, "Books.sdb")
; 4 Set up the data definitions:
; For each field in the structure you need to describe the
; Field Name, Field Type, Field Length, and Index Type
; Call TRMCreateField(FieldName, FieldType, FieldLen, IndexType)
; (BookFile\TRMCreateField("ISBN", #FIELD_STRING, #FldLen_ISBN, INDEX_UNIQUE)
; Once you have done this, you can use all the functions in the Tsunami Interface
; To change the data, use the Fields structure (eg book.book_record)
; To use Functions, use the Interface structure (eg BookFile)
; These are the only constants you need to know:
; #TRM_INDEX_NONE
; #TRM_INDEX_UNIQUE
; #TRM_INDEX_DUPLICATES
; #TRM_FIELD_INTEGER
; #TRM_FIELD_FLOAT
; #TRM_FIELD_STRING
; Functions available:
; TRMCreateField(FieldName.s, FieldType.b, FieldLength.l, Index.l)
; TRMInsert()
; TRMCreate()
; TRMOpen()
; TRMGetFirst()
; TRMGetLast()
; TRMGetNext()
; TRMGetPrev()
; TRMGetEqual()
; TRMDelete()
; TRMUpdate()
; TRMCount()
; TRMSetKeyPath(Index.l)
; TRMClose()
; TRMGetEqualOrGreater()
; TRMGetEqualOrLess()
IncludeFile "TRMInterface.pb"
If TRMInitialize() = 0
MessageRequester("Error", "Library not found")
End
EndIf
Structure book_record
ISBN.s
Title.s
Price.f
QtyAvail.l
dummy.l ; Bug? need to create a last field, otherwise QtyAvail is not updated properly
EndStructure
#FldLen_ISBN = 25
#FldLen_Title = 40
book.book_record
BookFile.Tsunami = TRMCreateInstance(book, "Books.sdb")
BookFile\TRMCreateField("ISBN", #TRM_FIELD_STRING, #FldLen_ISBN, #TRM_INDEX_UNIQUE)
BookFile\TRMCreateField("Title", #TRM_FIELD_STRING, #FldLen_Title, #TRM_INDEX_DUPLICATES)
BookFile\TRMCreateField("Price", #TRM_FIELD_FLOAT, 4, #TRM_INDEX_NONE)
BookFile\TRMCreateField("QtyAvail", #TRM_FIELD_INTEGER, 4, #TRM_INDEX_NONE)
BookFile\TRMCreateField("dummy", #TRM_FIELD_INTEGER, 4, #TRM_INDEX_NONE)
;Open or Create Book File
Result = BookFile\TRMOpen()
If Result = 1 ; not a Tsunami file
Result = BookFile\TRMCreate()
Result = BookFile\TRMOpen()
EndIf
; Put data into the file
book\ISBN = "1234.1234.1234"
book\Title = "How to write PB in 24 minutes"
book\Price = 2.50
book\QtyAvail = 1000
BookFile\TRMInsert()
book\ISBN = "2344.9999.2222."
book\Title = "Programming Explained"
book\Price = 199.00
book\QtyAvail = 40
BookFile\TRMInsert()
BookFile\TRMGetFirst()
Debug book\ISBN
Debug book\Title
Debug StrF(book\Price, 2)
Debug book\QtyAvail
BookFile\TRMGetNext()
Debug book\ISBN
Debug book\Title
Debug StrF(book\Price, 2)
Debug book\QtyAvail
BookFile\TRMClose()
MessageRequester("End", "End of Program")
End
Code: Select all
Global TRMLibNo.l ; Library No. for #FIELD_STRING = 1
Global KeySegCount.l
KeySegCount = 0
Enumeration
#TRM_INDEX_NONE
#TRM_INDEX_UNIQUE
#TRM_INDEX_DUPLICATES
#TRM_FIELD_INTEGER
#TRM_FIELD_FLOAT
#TRM_FIELD_STRING
#TRM_DATA_INSERT
#TRM_DATA_RETRIEVE
EndEnumeration
;Tsunami Constants
#TRM_NO_DUPLICATES = 2
#TRM_BINARY_KEY = 8
; These are the Tsunami Operation Codes used. Only about half the codes are implemented.
; The other codes are listed in the Tsunami manual.
#Trm_Close = 1 ;
#Trm_Count = 17
#Trm_Create = 14
#Trm_Delete = 4
#Trm_GetEqual = 5
#Trm_GetEqualOrGreater = 9
#Trm_GetEqualOrLess = 11
#Trm_GetFirst = 12
#Trm_GetGreater = 8
#Trm_GetLast = 13
#Trm_GetLess = 10
#Trm_GetNext = 6
#Trm_GetPrev = 7
#Trm_Insert = 2
#Trm_Open = 0
#Trm_SetKeyPath = 30
#Trm_Update = 3
Interface Tsunami
TRMCreateField.l(FieldName.s, FieldType.b, FieldLen.l, Index.l)
TRMInsert.l()
TRMCreate.l()
TRMOpen.l()
TRMGetFirst.l()
TRMGetLast.l()
TRMGetNext.l()
TRMGetPrev.l()
TRMGetEqual.l()
TRMDelete.l()
TRMUpdate.l()
TRMCount.l()
TRMSetKeyPath.l(Index.l)
TRMClose.l()
TRMGetEqualOrGreater.l()
TRMGetEqualOrLess.l()
EndInterface
Structure FieldStructure
FieldFileNo.l
FieldName.s
FieldType.b
FieldLen.l
FieldIndex.l
FieldStringData.l
*FieldNext.FieldStructure
*FieldPrev.FieldStructure
EndStructure
Structure Tsunami_Record
vTable.l
Functions.l[SizeOf(Tsunami)/4]
DataBuffer.l ; Pointer to Data Record
DBBuffer.l ; Pointer to memory allocated for data
datalen.l ; Length of DBBuffer
DBhandle.l ; File No
FieldList.l ; Pointer to Fields list
FileName.s
CurrentIndex.l
EndStructure
Structure TsunamiStructure
op.l ; Tsunami operation number
file.l ; Tsunami file handle
dataptr.l ; Address of data buffer
datalen.l ; Length of data buffer
keyptr.l ; Address of key buffer
keylen.l ; Length of key buffer
keyno.l ; Key number
EndStructure
Structure TsunamiKeySegment
keysegname.b[25]
keyno.b
keypos.w
keylen.b
keyflags.b
EndStructure
Structure TsunamiFileDef
PageSize.b
compression.b
Segments.b
segment.TsunamiKeySegment[128]
EndStructure
Global TFD.TsunamiFileDef
Global Tsu.TsunamiStructure
; Declare functions used internally by TRMInterface.pb
Declare TRMProcessFields(*self.Tsunami_Record, action.l)
Declare TRMGet(*self.Tsunami_Record)
Declare TRMGetIndexNo(*self.Tsunami_Record)
Procedure TRMOperation()
Result = CallFunction(TRMLibNo, "trm_udt", @Tsu)
ProcedureReturn Result
EndProcedure
Procedure TRMInitialize()
TRMLibNo = OpenLibrary(#PB_Any, "TRM.DLL")
ProcedureReturn TRMLibNo
EndProcedure
Procedure TRMCreateField(*self.Tsunami_Record, FieldName.s, FieldType.b, FieldLen.l, FieldIndex.l)
*p.FieldStructure = AllocateMemory(SizeOf(FieldStructure))
*p\FieldName = FieldName
*p\FieldType = FieldType
*p\FieldLen = FieldLen
*p\FieldIndex = FieldIndex
If FieldType = #TRM_FIELD_STRING
*p\FieldStringData = AllocateMemory(FieldLen) ; To hold string data
EndIf
*p\FieldNext = -1
*p\FieldPrev = -1
*m.FieldStructure = *self\FieldList ;*self\FieldList holds first field address
If *m > 0
While *m\FieldNext > 0
*m = *m\FieldNext
Wend
*m\FieldNext = *p
*p\FieldPrev = *m
EndIf
If *self\FieldList <= 0
*self\FieldList = *p
EndIf
EndProcedure
Procedure TRMInsert(*self.Tsunami_Record)
TRMProcessFields(*self, #TRM_DATA_INSERT)
Tsu\op = #Trm_Insert
Tsu\file = *self\DBhandle
Tsu\dataptr = *self\DBBuffer
Tsu\datalen = *self\datalen
Result = TRMOperation()
ProcedureReturn Result
EndProcedure
Procedure TRMUpdate(*self.Tsunami_Record)
TRMProcessFields(*self, #TRM_DATA_INSERT)
Tsu\op = #Trm_Update
Tsu\file = *self\DBhandle
Tsu\dataptr = *self\DBBuffer
Tsu\datalen = *self\datalen
Result = TRMOperation()
ProcedureReturn Result
EndProcedure
Procedure TRMOpen(*self.Tsunami_Record)
; set up databuffer
*self\datalen = 0
*m.FieldStructure = *self\FieldList
If *m > 0
While *m\FieldNext > 0
*self\datalen = *self\datalen + *m\FieldLen ; keep running total to allocate enough memory
*m = *m\FieldNext
Wend
EndIf
*self\DBBuffer = AllocateMemory(*self\datalen) ; this will hold data to be updated in DB
*self\CurrentIndex = 1 ; Default Index
Tsu\op = #Trm_Open
FileName.s = *self\FileName
Tsu\keyptr = @FileName
Tsu\keylen = Len(FileName)
Tsu\keyno = 0 ; single user
Result = TRMOperation()
*self\DBhandle = Tsu\file
ProcedureReturn Result
EndProcedure
Procedure TRMClose(*self.Tsunami_Record)
Tsu\op = #Trm_Close
Tsu\file = *self\DBhandle
Result = TRMOperation()
ProcedureReturn Result
EndProcedure
Procedure TRMCreate(*self.Tsunami_Record)
*self\datalen = 0
KeyNum.l = 1
CurrOffset.l = 1
;Set up indices
*m.FieldStructure = *self\FieldList
If *m > 0
While *m\FieldNext > 0
*self\datalen = *self\datalen + *m\FieldLen ; keep running total to allocate enough memory
If *m\FieldIndex <> #TRM_INDEX_NONE
keysegname.s = LSet(*m\FieldName,25)
TFD\segment[KeySegCount]\keysegname = @keysegname
TFD\segment[KeySegCount]\keyno = KeyNum
KeyNum = KeyNum + 1
TFD\segment[KeySegCount]\keypos = CurrOffset
TFD\segment[KeySegCount]\keylen = *m\FieldLen
If *m\FieldType <> #TRM_FIELD_STRING
TFD\segment[KeySegCount]\keyflags = #TRM_BINARY_KEY
EndIf
If *m\FieldIndex = #TRM_INDEX_UNIQUE
TFD\segment[KeySegCount]\keyflags = TFD\segment[KeySegCount]\keyflags | #TRM_NO_DUPLICATES
EndIf
KeySegCount = KeySegCount + 1
CurrOffset = CurrOffset + *m\FieldLen
EndIf
*m = *m\FieldNext
Wend
EndIf
*self\DBBuffer = AllocateMemory(*self\datalen) ; this will hold data to be updated in DB
; Set up File Header
TFD\PageSize = 1
TFD\compression = 1
TFD\Segments = KeySegCount
; now set up Tsu structure
Tsu\op = #Trm_Create
Tsu\file = 0
Tsu\dataptr = TFD
Tsu\datalen = 3 + (30 * TFD\Segments)
FileName.s = *self\FileName
Tsu\keyptr = @FileName
Tsu\keylen = Len(FileName)
Tsu\keyno = 1 ; Overwrite file if it already exists
Result = TRMOperation()
Result = 0
ProcedureReturn Result
EndProcedure
Procedure TRMGetFirst(*self.Tsunami_Record)
Tsu\op = #Trm_GetFirst
Result = TRMGet(*self)
ProcedureReturn Result
EndProcedure
Procedure TRMGetNext(*self.Tsunami_Record)
Tsu\op = #Trm_GetNext
Result = TRMGet(*self)
ProcedureReturn Result
EndProcedure
Procedure TRMGetPrev(*self.Tsunami_Record)
Tsu\op = #Trm_GetPrev
Result = TRMGet(*self)
ProcedureReturn Result
EndProcedure
Procedure TRMGetLast(*self.Tsunami_Record)
Tsu\op = #Trm_GetLast
Result = TRMGet(*self)
ProcedureReturn Result
EndProcedure
Procedure TRMGetEqual(*self.Tsunami_Record)
Tsu\op = #Trm_GetEqual
Tsu\keyptr = *self\DataBuffer
Tsu\keylen = 4
Result = TRMGet(*self)
ProcedureReturn Result
EndProcedure
Procedure TRMGetEqualOrGreater(*self.Tsunami_Record)
Tsu\op = #Trm_GetEqualOrGreater
; set Tsu\keyptr and Tsu\keylen
TRMGetIndexNo(*self)
Result = TRMGet(*self)
ProcedureReturn Result
EndProcedure
Procedure TRMGetEqualOrLess(*self.Tsunami_Record)
Tsu\op = #Trm_GetEqualOrLess
TRMGetIndexNo(*self)
Result = TRMGet(*self)
ProcedureReturn Result
EndProcedure
Procedure TRMDelete(*self.Tsunami_Record)
Tsu\op = #Trm_Delete
Tsu\file = *self\DBhandle
Result = TRMOperation()
ProcedureReturn Result
EndProcedure
Procedure TRMCount(*self.Tsunami_Record)
Tsu\op = #Trm_Count
Tsu\file = *self\DBhandle
Result = TRMOperation()
ProcedureReturn Tsu\keyno
EndProcedure
Procedure TRMSetKeyPath(*self.Tsunami_Record, Index.l)
*self\CurrentIndex = Index
Tsu\op = #Trm_SetKeyPath
Tsu\file = *self\DBhandle
Tsu\keyno = Index
Result = TRMOperation()
ProcedureReturn Result
EndProcedure
Procedure.s TRMErrorText(ErrCode)
Text$ = "Unknown Error"
Select ErrCode
Case 1 : Text$ = "Not A Tsunami File"
Case 2 : Text$ = "I/O Error"
Case 3 : Text$ = "File Not Open"
Case 4 : Text$ = "Key Not Found"
Case 5 : Text$ = "Duplicate Key"
Case 6 : Text$ = "Invalid Key Number"
Case 7 : Text$ = "File Corrupt"
Case 8 : Text$ = "No Current Position"
Case 9 : Text$ = "End Of File"
Case 10 : Text$ = "Invalid Page Size"
Case 11 : Text$ = "Invalid Number Of Key Segments"
Case 12 : Text$ = "Invalid File Definition String"
Case 13 : Text$ = "Invalid Key Segment Postion"
Case 14 : Text$ = "Invalid Key Segment Length"
Case 15 : Text$ = "Inconsistent Key Segment Definitions"
Case 20 : Text$ = "Invalid Record Length"
Case 21 : Text$ = "Invalid Record Pointer"
Case 22 : Text$ = "Lost Record Position"
Case 30 : Text$ = "Access Denied"
Case 31 : Text$ = "File Already Exists"
Case 32 : Text$ = "No More File Handles"
Case 33 : Text$ = "Max Files Open"
Case 40 : Text$ = "Accelerated Access Denied"
Case 41 : Text$ = "Acceleration Cache Error"
Case 46 : Text$ = "Access To File Denied"
Case 50 : Text$ = "Data Buffer Too Small"
Case 51 : Text$ = "Record Deleted Successfuly"
Case 99 : Text$ = "Time Out"
EndSelect
ProcedureReturn Text$
EndProcedure
Procedure TRMCreateInstance(*DataBuffer, FileName.s)
*TsuRec.Tsunami_Record = AllocateMemory(SizeOf(Tsunami_Record))
*TsuRec\vTable = *TsuRec + OffsetOf(Tsunami_Record, Functions)
*TsuRec\Functions[0] = @TRMCreateField()
*TsuRec\Functions[1] = @TRMInsert()
*TsuRec\Functions[2] = @TRMCreate()
*TsuRec\Functions[3] = @TRMOpen()
*TsuRec\Functions[4] = @TRMGetFirst()
*TsuRec\Functions[5] = @TRMGetLast()
*TsuRec\Functions[6] = @TRMGetNext()
*TsuRec\Functions[7] = @TRMGetPrev()
*TsuRec\Functions[8] = @TRMGetEqual()
*TsuRec\Functions[9] = @TRMDelete()
*TsuRec\Functions[10] = @TRMUpdate()
*TsuRec\Functions[11] = @TRMCount()
*TsuRec\Functions[12] = @TRMSetKeyPath()
*TsuRec\Functions[13] = @TRMClose()
*TsuRec\Functions[14] = @TRMGetEqualOrGreater()
*TsuRec\Functions[15] = @TRMGetEqualOrLess()
*TsuRec\DataBuffer = *DataBuffer
*TsuRec\FieldList = -1
*TsuRec\FileName = FileName
*TsuRec\datalen = 0
ProcedureReturn *TsuRec
EndProcedure
Procedure TRMProcessFields(*self.Tsunami_Record, action.l)
*m.FieldStructure = *self\FieldList
CurrOffset.l = 0
DBOffset.l = 0
Repeat
If *m > 0
Select *m\FieldType
Case #TRM_FIELD_INTEGER
If action = #TRM_DATA_INSERT
PokeL(*self\DBBuffer + DBOffset, PeekL(*self\DataBuffer + CurrOffset))
Else
PokeL(*self\DataBuffer + CurrOffset , PeekL(*self\DBBuffer + DBOffset))
EndIf
CurrOffset = CurrOffset + 4
DBOffset = DBOffset + 4
Case #TRM_FIELD_STRING
If action = #TRM_DATA_INSERT
PokeS(*self\DBBuffer + DBOffset, LSet(PeekS(*PeekL(*self\DataBuffer +CurrOffset)), *m\FieldLen))
Else
PokeL(*self\DataBuffer + CurrOffset, *m\FieldStringData)
EndIf
If *m\FieldStringData > 0
FreeMemory(*m\FieldStringData)
EndIf
*m\FieldStringData = AllocateMemory(*m\FieldLen)
PokeS(*m\FieldStringData, LSet(PeekS(*self\DBBuffer + DBOffset), *m\FieldLen))
CurrOffset = CurrOffset + 4
DBOffset = DBOffset + *m\FieldLen
Case #TRM_FIELD_FLOAT
If action = #TRM_DATA_INSERT
PokeF(*self\DBBuffer + DBOffset, PeekF(*self\DataBuffer + CurrOffset))
Else
PokeF(*self\DataBuffer + CurrOffset, PeekF(*self\DBBuffer + DBOffset))
EndIf
CurrOffset = CurrOffset + 4
DBOffset = DBOffset + 4
EndSelect
EndIf
*m = *m\FieldNext
Until *m <= 0
EndProcedure
Procedure TRMGet(*self.Tsunami_Record)
Tsu\file = *self\DBhandle
Tsu\keyno = *self\CurrentIndex
Tsu\dataptr = *self\DBBuffer
Tsu\datalen = *self\datalen
Result = TRMOperation()
If Result = 0
TRMProcessFields(*self, #TRM_DATA_RETRIEVE)
EndIf
ProcedureReturn Result
EndProcedure
Procedure TRMGetIndexNo(*self.Tsunami_Record)
*m.FieldStructure = *self\FieldList
CurrOffset.l = 0
DBOffset.l = 0
CurrentIndex = 0
Repeat
If *m > 0
If *m\FieldIndex <> #TRM_INDEX_NONE
CurrentIndex = CurrentIndex + 1
If CurrentIndex = *self\CurrentIndex
If *m\FieldType = #TRM_FIELD_STRING
Tsu\keyptr = PeekL(*self\DataBuffer + CurrOffset)
Else
Tsu\keyptr = *self\DataBuffer + CurrOffset
EndIf
Tsu\keylen = *m\FieldLen
ProcedureReturn
EndIf
EndIf
CurrOffset = CurrOffset + 4
EndIf
*m = *m\FieldNext
Until *m <= 0
EndProcedure