A Short Database Demo

The need to include a simple database in a current project pushed me into investigating the RealDataBase in the new RealBasic2.0. Dan's article in last's month's magazine got me started. The RealBasic Developer's Guide helped also. But, none of these really laid out something that could be picked up and watched, as it executed. As I flailed around, I found that working with a database was hard. But, not because its hard; instead, its because the terminology is so unfamiliar.

So, I set out to create a little application that would allow me to create a very simple database. And, I did a second one to read it. I separated the two functions to make it absolutely simple. I wanted to focus on the database, not menus, windows, and such. I really wanted to learn what the strange terms meant and how things functioned.

Creating a Database

So, lets start with the app that creates the database. I called it DbaseMaker. It has a window with one StaticText and one button. The button's Action sub contains the single instruction: Quit. This allows the window to stay open, showing the StaticText until the button is clicked.

The window's Open event is where all the action is. Much was taken out of the on-line reference and pieced together into something that works. Here is the listing:

Sub Open()
  dim dbFile as FolderItem
  dim r as DatabaseRecord

  dbFile = GetFolderItem("DBaseTest.rdb")
  If (dbFile  nil) AND (dbFile.exists = false) then
    db = NewRealDatabase(dbFile)
    If db  nil then
      db.SQLExecute("create table invoice(id integer not null, Cust_ID integer not null, Amount varchar (25), Date varchar (25), primary key (id))")
      r=new DatabaseRecord
      r.Column("id")= "04"
      r.Column("Cust_ID") = "02"
      r.Column("Amount") = "$9.98"
      r.Column("Date") = "10/22/98"
      db.InsertRecord("invoice",r)
      r.Column("id")= "17"
      r.Column("Cust_ID") = "33"
      r.Column("Amount") = "$1356.78"
      r.Column("Date") = "112/30/99"
      db.InsertRecord("invoice",r)
      StaticText1.text = "DBaseTest.rdb has been saved."
    else
      StaticText1.text = "DBaseTest.rdb could not be created for some reason"
    End if //db  nil
  Else //dbFile
    StaticText1.text = "DBaseTest.rdb is already present. Please remove it for most reliable operation."
  End if //dbFile
End Sub

And here is what happens in that subroutine. First, it gets a FolderItem, dbFile, for DBaseTest.rdb. This is the name of the file that is going to contain the database. I wanted to find out if it was already present or not. If the dbFile is nil or if its exists property is true (the latter happens when the file is already present), skip the database creation and report that fact in the StaticText at the end of the subroutine. But, if dbFIle isn't nil and it does not already exist, then proceed by creating a new database using db = NewRealDatabase(dbFile). If that instruction returns nil something is wrong and it should not proceed. But, if its NOT nil, then we proceed by putting stuff into the database.

The instruction db.SQLExecute("create table invoice(...)") was the first really alien thing we encounter. It creates a "table" named "invoice". A table, in database-speak, is a collection of data that shares a common arrangement. In this case, the invoice contains "records". Each record contains the information about a sales transaction. Every transaction is recorded in one record. Every transaction includes an "id" number. The idea is to have a unique number for each transaction/record. Each transaction has a customer number, "Cust_ID". This might have been obtained by looking up in another database or another part of this database (not yet created); here, we are going to use arbitrary numbers. There is also an "amount", and a "date". A real invoice would certainly have more, such as what was purchased, but this will do for our experiment. You can think of these as "columns" in the database table named "invoice" while the records are "rows". Be aware, howwever, that these are abstractions and have little to do with how things are stored in the database.

Lets look a little more closely at how the table columns are specified in the create table invoice(...) part. Each of the columns has to be specified as part of this SQL command. The first is id integer not null. This says that the id number must be an integer and that there must be something in it for the record to be acceptable to the database. The second is Cust_ID integer not null; it looks just like the previous one. The third will probably look very strange to BASIC programmers: Amount varchar (25). In spite of its appearance, this says that the Amount column contains a string that can be up to 25 characters long. The same is true for date. The last item may also look a bit strange; it tells which of the columns is guaranteed to be unique for purposes of extracting information. Take a look at the section in the Developer's Guide on the Create Table Statement in the explanation of Structured Query Language.

Now, we have a database, and we can start putting stuff in. The instruction r=new DatabaseRecord provides space for one record. Put things into the record with statements like r.Column("id")= "04". Columns can be specified in any order; notice that the columns are referenced by the name of the column. Another really strange thing, to me, anyway, is the form of the data. Its a string even though the id column was specified to be an integer. I have not yet tried to see what would happen if that string contains non-numeric characters. Once the columns of the new record are filled in, db.InsertRecord("invoice",r) sends it to the actual database. This adds a new record; you change records another way. The program adds two records to the empty database, then notifies you via StaticText1 that it has finished.

Reading the Database

The database has been created and has two records. Lets try reading it. I called the app DbaseReader. It has a window with several StaticTexts and two buttons. The "Quit" button's Action sub contains the single instruction Quit. This allows the window to stay open, showing the StaticText until the button is clicked. There is another button captioned "Next". It steps through the records in the database.

This program works very similar to DBaseMaker. There are several differences, howver. One is that there is a class property: cur as DataBaseCursor. Another is that there is a method named ShowRecord. The cursor is a class property so that it can be readily passed from the place where the database is read (the app's Open event handler) to the place where it is displayed, ShowRecord and to the Next button handler.

Lets look first at the Open event handler.

Sub Open()
  dim dbFile as FolderItem
  dim db as Database

  dbFile = GetFolderItem("DBaseTest.rdb")
  if (dbFile  nil) AND (dbFile.exists = True) then
    db = OpenRealDatabase(dbFile)
    if db  nil then
      cur = db.SQLSelect("select * from invoice")
	  ShowRecord("First")
    else //db  nil
      StaticText1.text = "DBaseTest.rdb can't be opened for some reason. "
      StaticText1.text = StaticText1.text + "Try discarding it and running DBaseMaker again."
    End if //db
  else
    StaticText1.text = "DBaseTest.rdb can't be found. Please run DateBaseMaker first!"
  End if //dbFile.exists

Again, a folderitem for DBaseTest.rdb is created. This time, however, the fault case is a nil value for dbFile or that dbFile.exists = FALSE. If neither of these are true, then it is possible to open the file with db = OpenRealDatabase(dbFile). If this is also not nil, the the program can proceed.

Data is retrieved from the database using cur = db.SQLSelect("select * from invoice"). What this means is to make a "cursor" (a datastream, if you will) containing all (that is what the "*" means) of the data from the invoice table. No sorting or anything else has been done; we are just going to view it to verify that we can recover what was stored. ShowRecord displays the first record. The parameter is used to indicate in StaticText which record is being displayed.

Sub ShowRecord(Ordinal as string)

  	dim CrLf as string
  	CrLf = chr(13)
  	#if TargetWin32 then
  	CrLf = CrLf + Chr(10) //add line feed
  	#Endif

    StaticText1.text = Ordinal +" record:"+CrLf
    StaticText1.text = StaticText1.text + cur.idxField(1).Name + " = "
    StaticText1.text = StaticText1.text + cur.idxField(1).StringValue+CrLf
    StaticText1.text = StaticText1.text + cur.idxField(2).Name + " = "
    StaticText1.text = StaticText1.text + cur.idxField(2).StringValue +CrLf
    StaticText1.text = StaticText1.text + cur.idxField(3).Name + " = "
    StaticText1.text = StaticText1.text + cur.idxField(3).StringValue+CrLf
    StaticText1.text = StaticText1.text + cur.idxField(4).Name + " = "
    StaticText1.text = StaticText1.text + cur.idxField(4).StringValue
    cur.moveNext
    If cur.EOF = true then
    	NextButton.enabled = false
    else
    	NextButton.enabled = true
    End if
End Sub

If we look at the ShowRecord subroutine, the first thing we might see is the #If block; it is intended to account for the differences in end-of-line between Mac and Win32. It seems to work as it is; I have not tried leaving it out. It makes the CrLf string to be a carriage-return, then if its Win32, it adds a line-feed. Next, we see that a string containing 5 lines of text are created and transferred to StaticText1.text. The StaticText in this window has to be large enough to show 5 lines of text.

Next, the program just steps through the four columns of the record in the cursor. cur.idxField(1).Name gives the name of the first field of the record as it is arranged in the cursor. cur.idxField(1).StringValue contains the data from that column. The same is true for the 2nd, 3rd, and 4th columns. Finally, cur.moveNext moves to the next record. The next couple of lines may warrant a short explanation. cur.EOF is true when there are no records in the cursor. EOF stands for "end-of-file". If the end has not been reached, the Next button is enabled; if the end of the file has been reached, then the Next button is disabled.

Finally, the Next button handler simply calls ShowRecord. The only time the user can do this is when the first record has been shown in the Open event handler since there is one record, still in the cursor. Once the Next buttton handler has been executed once, the end of the cursor has been reached and the Next button is disabled.

We are at the end of this little database demo/tutorial. Its pretty simple. We didn't sort or modify a database. But, we created one with one table and two records in the table. We then read the table, showing that data could be recovered. This should get most of you up and going. Have a go at it.

This has been contributed by Jim Wagner. He is a professional electrical engineer and embedded-processor programmer (8051 assembly language). His employment has included Tektronix, the College of Oceanography at Oregon State University, and now Kalatel, Inc. in Corvallis, Or. RealBasic is one of many things he does for fun; other fun things include mountain climbing, ham radio, bike riding, and cross-country skiing. He can be reached by e-mail at wagnerj@proaxis.com or ka7ehk@yahoo.com.