REBOL Relational Object-oriented Database
[1/22] from: louisaturk::eudoramail::com at: 20-Jun-2001 4:09
Dear Carl and anyone else that might be able to help,
I am hoping you can spare a few minutes to help me past a few problems. I
am still working on a very simple character-based user interface for your
database functions---see my unfinished work below. Once it is finished, I
may try to put a View GUI on it, but for right now I want to keep it real
simple so I can understand what is going on. I know I could use MySQL and
avoid having to learn this, but I have reasons for wanting a full REBOL
relational object-oriented dbms.
I would really appreciate it if Carl Sassenrath would help with the core
database functions as he is the most knowledgeable about the inner workings
of REBOL, and it is he that provided the original functions that showed me
the great potential of REBOL to do what I need done. I think that many
others will also appreciate his concise, elegant contributions of basic
building components to help us learn basic, practical skills. If Carl
would please take out a few minutes to whip out the remaining needed
function, I would be most thankful. Sorry to impose on him like this, but
ask and ye shall receive.
"Ye have not because ye ask not."
You might want to do the program below, add a few records, and then step
through the menu choices so that you can see what I am talking about in the
following questions:
1. How can a field in a record be edited (not just replaced)? Carl, a
function please.
2. What is the purpose of the 2 in the remove-data function?
3. Every time the insert-data function is called a box opens asking me for
permission to write to disk. How do I prevent this in such a way as to not
open my computer up for possible invasion? (Later: well, its not asking me
now, but I don't know why.)
4. How do I print out a list of all or some of the fields in all of the
records (say for a report)? I am using probe right now so I can at least
see what is in the database, but I need formatted reports.
5. How can this database be related to a second database by say the phone
number field? How can the data from both databases be accessed at once?
6. How can data be exported from a database as a comma separated file.
7. How can data be imported into a database from a comma separated file.
8. What is the maximum number of records practical to search with REBOL on
a computer with 512 MB of RAM? With 20,000 records or so what would
performance be like?
9. How can I get instant action upon pressing a key (say for a menu
choice), without having to also hit the enter key? I hate to have to hit 2
keys when hitting one would work just as well.
Thanks,
Louis
REBOL [Title: "REBOL Relational Object DBMS"
Date: "19 June 2001"
Version: 0.01
File: %db.r
Author: "Carl Sessenrath and Louis A. Turk"
Email: [louisaturk--eudoramail--com]
Language: 'English
Purpose: "A simple but complete relational object-oriented dbms."
Comments: {Carl Sessenrath made these object oriented database
management system functions as an example to teach me,
Louis A. Turk, how to program an object database myself.
Many thanks Carl! The comments about features below are
mostly Carl's, edited by Louis for this script header.
Features:
1. The db works from memory. Load-data brings it into
memory
where the find, remove and other functions operate on
it.
So, you have to load it first, or at least insert-data a
few times to create some data records.
2. You can expand or reduce the record definition without
corrupting or affecting the database.
3. If you expect to grow this database to a large size,
you will want to MAKE HASH! the database when you
load it. make the hash after the database is created
(after the foreach loop).
database: make hash! database
But, don't worry about that until your database gets big.
For a few hundred names, you don't need it.
4. The functions work like this:
rec: find-data [bob--example--com]
print rec/name
print rec/phone
etc.
remove-data [bob--example--com]
save-data ; write back to disk
insert-data [kit--example--com] "Kitty Carson" none
http:/www.a.com
save-data ; write back to disk
5. You can use NONE for any missing value above. You don't
need to do the save-data each time... but you must do it
sooner or later.
6. The data is organized (keyed) by email. To change an
email address, but keep the rest of the record intact,
use the function change-data. Let's examine change-data:
change-data: func [email-old email-new /local record] [
record: find database email-old
if none? record [alert reform ["Missing record:"
email-old] exit]
insert clear first record email-new ; see note below
]
Note: This is subtle, so should be noted... I'm
clearing
out the actual memory string for the old email, then
inserting
the new string into it.
Also, this string is shared. It is used both in the
database
block (as the key) and in the record object
itself. So changing
it here will change it in both places.
Please send any bug reports or features you add to:
[louisaturk--eudoramail--com]
} ; End of Comments
] ;End of Rebol Script Header
;#!/rebol/rebol -cs
;open/allow %data.r [read write]
db-file: %data.r
record: context [name: email: phone: web: none] ;Place the none value in
all the varables.
database: []
load-data: has [data] [
data: load/all db-file
clear database
foreach item data [
item: make record item
repend database [item/email item]
]
]
change-data: func [email-old email-new /local record] [
record: find database email-old
if none? record [alert reform ["Missing record:" email-old] exit]
insert clear first record email-new ; See note in feature #6 above.
]
save-data: has [data] [
data: copy []
foreach [key obj] database [
append/only data third obj
]
save db-file data
]
find-data: func [email] [select database email]
remove-data: func [email] [remove/part find database email 2]
insert-data: func [email' name' phone' web'] [
repend database [
email'
make record [
email: email'
name: name'
phone: phone'
web: web'
]
]
]
{Below is a simple character based user interface for REBOL/Core added by
Louis A. Turk}
load-data
forever [
cls: "^(1B)[J" ; Clear the Screen.
print cls
print "^/ REBOL RELATIONAL OBJECT-ORIENTED DBMS" ; ^/ is a line feed.
print " =====================================^/"
print " 1. Add Record."
print " 2. Edit a Field In a Record."
print " 3. Delete Record."
print " 4. Change Email Address Only."
print " 5. Print Reports."
print " 6. Exit.^/"
choice: ask " Enter the number of your choice: "
if choice = "1" [
forever [
print cls
print " ADD A RECORD"
print " ============^/"
email: ask " Email Address : "
if email = "" [break]
rec: find database email
if not rec = none [
print cls
print " Record is already in database.^/"
ask " Hit Any Key To Continue."
break
]
name: ask " Full Name : "
phone: ask " Area Code & Phone# : "
web: ask " Website URL : "
insert-data email name phone web
save-data
] ; End forever loop for choice 1
] ; End if choice 1
if choice = "2" [
forever [
print cls
print " EDIT A FIELD IN A RECORD"
print " ========================^/"
email-old: ask " Email Address: "
record: find database email-old
if record = none [
print cls
print "^/ Missing record: " email-old
ask "^/ Hit Any Key To Continue."
break
]
record: find-data email-old
print ["^/ 1. Email : " record/email]
print [" 2. Name : " record/name]
print [" 3. Phone : " record/phone]
print [" 4. URL : " record/web "^/"]
choice: ask " Enter number of field to change: "
; I'm using this method of accessing fields since records in
databases
; to be expanded from this one may have 10 to 30 fields with
only
; one usually needing editing.
if choice = "1" [
print cls
print " EDIT EMAIL ADDRESS"
print " ==================^/"
ask "New function needed here to edit the email field."
save-data
choice: "0"
;print ["The email address has been changed to "
email-new "."]
]
if choice = "2" [
print cls
print " CHANGE NAME"
print " ===========^/"
name-old: record/name
ask "New function needed here to edit the name field."
save-data
choice: "0"
]
if choice = "3" [
print cls
print " CHANGE PHONE"
print " ===========^/"
ask "New function needed here to edit the phone number field."
save-data
choice: "0"
]
if choice = "4" [
print cls
print " CHANGE URL"
print " ===========^/"
ask "New function needed here to edit the url field."
save-data
choice: "0"
]
] ; End forever loop
] ; End if choice 2
if choice = "3" [
forever [
print cls
print " DELETE A RECORD"
print " ===============^/"
email-old: ask " Email Address Of Record To Delete: "
if email-old = "" [
break
] ; End if.
record: find database email-old
if none? record [
print cls
print " Record not found.^/"
ask " Hit Any Key To Continue."
break
]
remove-data email-old
save-data
] ; End forever loop for choice 3.
] ; End if choice 3
if choice = "4" [
forever [
print cls
print " CHANGE EMAIL ADDRESS ONLY"
print " ==========================^/"
email-old: ask " Email Address To Be Changed: "
record: find database email-old
if none? record [
print cls
print "Missing record: " email-old
ask "Hit Any Key To Continue."
break
]
email-new: ask " New Email Address: "
change-data email-old email-new
save-data
] ; End forever loop for choice 4
] ; End if choice 4
if choice = "5" [
print cls
print " PRINT FORMATTED REPORTS"
print " =======================^/"
print "I need to know how to print formatted reports, instead of
just using probe as below.^/"
probe database
ask "Press Enter To Continue."
]
if choice = "6" [
break
] ; End if choice 6
if choice > "6" [
print "Error: Choice must be number between 1 and 6.^/"
ask "Hit Any Key To Continue."
] ; End if for choice 6.
] ; End forever loop for main program.
[2/22] from: al:bri:xtra at: 21-Jun-2001 21:59
Louis wrote:
> Dear Carl and anyone else that might be able to help,
>
> I am hoping you can spare a few minutes to help me past a few problems.
I think you'll need to read "Rebol The Official Guide" -- it has a
demonstration database program developed through the book. That should give
you some ideas. Also, reading "Rebol for Dummies" will help if you get
stuck.
I hope that helps!
Andrew Martin
ICQ: 26227169 http://zen.scripterz.org
[3/22] from: louisaturk:eudoramail at: 21-Jun-2001 17:03
Andrew,
Thanks for replying! You can tell I still have a lot to learn about
REBOL. :>) My study of REBOL keeps getting interrupted, as I have many
responsibilities. I really feel out of place among all the pros on this
list. But I see some important uses for REBOL so I keep trying.
Yes, I have both books and have been reading them. I have read the For
Dummies book several times. It has enabled me to write several very useful
programs (thanks Ralph!). It does not, however, say much about relational
object-oriented databases. The Official Guide would be a lot easier for me
to understand if it contained a very simple but working bare bones
Relational Object-oriented Database right at the beginning before going
into so much detail. After running the program and seeing what it does, I
could then more easily understand what the book is explaining. I have
looked for such a program on the CD that came with the book, but so far
haven't found one.
The database functions Carl wrote for me were so concise I was hoping for
one more like them to complete this program. I was hoping for him to write
this function so that I could give him credit for all of the actual
database functions in the program, but if he is too busy to help I can
certainly understand that, and help from anyone will be appreciated.
Most people on this list are probably using MySQL or some other such
database. There doesn't seem to be much interest in a REBOL Relational
Object-oriented Database. Am I being foolish is wanting to learn how to do
this? Or is it just that I shouldn't bother the list when the information
is already available in The Official Guide? Or am I just asking too many
questions in one post?
Louis
At 09:59 PM 6/21/2001 +1200, you wrote:
[4/22] from: gjones05:mail:orion at: 21-Jun-2001 17:22
Hi, Louis,
All questions are welcomed, in my opinion, but your was asking for a lot in one
chunk. ;-) Furthermore, it is possible that you might have offended some of us
lowly list members by asking for responses only from the "The Creator" (of
REBOL, that is ;-). The following may be of some help, but the answers are a
bit terse. Feel free to ask for further clarification if it makes no sense.
From: "Dr. Louis A. Turk"
> 1. How can a field in a record be edited (not just replaced)? Carl, a
> function please.
***old version***
change-data: func [email-old email-new /local record] [
record: find database email-old
if none? record [alert reform ["Missing record:" email-old] exit]
insert clear first record email-new ; See note in feature #6 above.
]
***new version
change-data: func [email-old email-new /local record] [
record: find database email-old
if none? record [alert reform ["Missing record:" email-old] exit]
record/1: email-new ; See note in feature #6 above.
]
> 2. What is the purpose of the 2 in the remove-data function?
The 'find returns an index to the record. The refinement /part on 'remove says
to remove from the index position to the length of 2, which removes the email
address and the record.
> 3. Every time the insert-data function is called a box opens asking me for
> permission to write to disk. How do I prevent this in such a way as to not
> open my computer up for possible invasion? (Later: well, its not asking me
> now, but I don't know why.)
REBOL security defaults to allowing local file read operations, but it requests
permission to write a file locally. When the program needs to write to a local
file, it will request permission. If you click "yes", then only the single
write operation will occur. If you click "Allow All" then it will no longer ask
each time. If your program and datafile are in a *non-shared* directory on your
computer, then there is no risk of someone tapping into your computer. What
constitutes a non-shared directory depends on the platform. There are different
ways to change the default levels, depending on how the script will be used.
> 4. How do I print out a list of all or some of the fields in all of the
> records (say for a report)? I am using probe right now so I can at least
> see what is in the database, but I need formatted reports.
forskip database 2 [
print ["Email: " database/2/email]
print ["Name: " database/2/name]
print ["Phone: " database/2/phone]
print ["Web URL: " database/2/web]
]
> 5. How can this database be related to a second database by say the phone
> number field? How can the data from both databases be accessed at once?
Say the second database has a structure like phone and type (meaning "Home",
Work
, etc), then assume the phone number is stored as a string (do to local
differences). Complete phone numbers are unique by definition, so a second
database could be structured as a simple block with alternating values of
numbers and types, like:
["01-713-555-1212" "Work" "01-713-555-2121" "Home"]
A simple 'select using the phone number field will return the phone location
type on this table (just like you used email address to look up a full record on
the first).
Using related tables does sort of violate the idea behind an object oriented
database, though. :-) REBOL is flexible enough to allow this type of
information to be further embedded within a record, but might add a bit of
complexity though, depending on how it is implemented.
> 6. How can data be exported from a database as a comma separated file.
write %/path/to/my-csv-file "" ;to get fresh file
emit: func [blk] [repend blk]
forskip database 2 [
csv-line: make string! 1000
emit [{"} database/2/email {","} database/2/name {","} database/2/phone
{","} database/2/web {"^/}]
write/append %/path/to/my-csv-file csv-line
]
> 7. How can data be imported into a database from a comma separated file.
foreach line read/lines %/path/to/my-csv-file [
foreach [email name phone web] parse line none [
email: to-email trim email
trim name
trim phone
web: to-url trim web
;;;;;then insert fields into a database
]
]
> 8. What is the maximum number of records practical to search with REBOL on
> a computer with 512 MB of RAM? With 20,000 records or so what would
> performance be like?
Assuming a somewhat fast processor (500 MHZ) and the database block is made into
a hash, performance should be quite tolerable for a single user up into this
range of size.
> 9. How can I get instant action upon pressing a key (say for a menu
> choice), without having to also hit the enter key? I hate to have to hit 2
> keys when hitting one would work just as well.
I don't know this one!!
Hope this helps.
--Scott Jones
[5/22] from: gjones05:mail:orion at: 21-Jun-2001 18:19
From: "GS Jones"
> > 6. How can data be exported from a database as a comma separated file.
> write %/path/to/my-csv-file "" ;to get fresh file
<<quoted lines omitted: 5>>
> write/append %/path/to/my-csv-file csv-line
> ]
Whoops. Error...
emit: func [blk] [repend blk]
should be ...
emit: func [blk] [repend csv-line blk]
Sorry.
--Scott Jones
[6/22] from: gchiu:compkarori at: 22-Jun-2001 15:48
> Relational Object-oriented Database
Um, what exactly is a relational objected oriented database?
--
Graham Chiu
[7/22] from: louisaturk:eudoramail at: 22-Jun-2001 1:24
Graham,
What I think one is (at least what I meant when I wrote that title):
Relational = having two or more databases joined together by a key field
(or fields) so that the proper data can be pulled from the two (or more)
databases as though they were one.
Object-oriented=the concept of bundling data and functions together in a
container called an object so that the code can be easily reused (and
extended) and so that two different objects may contain symbols that
contain identical names with conflict.
I am not a professional programmer. If I am wrong please let me know.
Louis
At 03:48 PM 6/22/2001 +1200, you wrote:
[8/22] from: m:converso:bitinia at: 22-Jun-2001 8:57
Louis,
1.- I agree completely with you !
2.- I'm a proud owner of the two books too
2.1.- but the two books lacks of certain very basic steps, if it is true
that Rebol can be a language for (serious) beginners
thanks a lot
MAurizio
Dr. Louis A. Turk
wrote:
[9/22] from: louisaturk:eudoramail at: 22-Jun-2001 4:01
Scott or whoever is online,
Is there an argument missing in the emit function below?
csv: ask "Name of CSV File to Write to: "
csv-file: to-file csv
write csv-file "" ;to get fresh file
emit: func [blk] [repend blk]
forskip database 2 [
csv-line: make string! 1000
emit [{"} database/2/email {","} database/2/name {","}
database/2/phone
{","} database/2/web {"^/}]
write/append csv-file csv-line
]
I get the following erro message:
** Script Error: repend is missing its value argument
** Where: emit
** Near: repend blk
At 05:22 PM 6/21/2001 -0500, you wrote:
[10/22] from: philb:upnaway at: 22-Jun-2001 16:21
Hi Loius,
The term database is usually meant to be more than 1 table, which is what confused me
about your question. For example I have used an oracle database that consists of hundreds
of tables.
Cant remember the exact definition of Relaional but I think we get the idea of what you
were talking about.
I agree with your definition of Object Oriented.
Cheers Phil
-- Original Message --
Graham,
What I think one is (at least what I meant when I wrote that title):
Relational = having two or more databases joined together by a key field
(or fields) so that the proper data can be pulled from the two (or more)
databases as though they were one.
Object-oriented=the concept of bundling data and functions together in a
container called an object so that the code can be easily reused (and
extended) and so that two different objects may contain symbols that
contain identical names with conflict.
I am not a professional programmer. If I am wrong please let me know.
Louis
At 03:48 PM 6/22/2001 +1200, you wrote:
[11/22] from: philb:upnaway at: 22-Jun-2001 16:27
Hi Louis,
the help for repend is
>> help repend
USAGE:
REPEND series value /only
DESCRIPTION:
Appends a reduced value to a series and returns the series head.
REPEND is a function value.
ARGUMENTS:
series -- (Type: series port)
value -- (Type: any)
REFINEMENTS:
/only -- Appends a block value as a block
so the repend statment is missing its 2nd value.
emit: func [blk] [repend blk]
Dont know what the function is trying to do though ... so I cant be much help there :-(
Cheers Phil
-- Original Message --
Scott or whoever is online,
Is there an argument missing in the emit function below?
csv: ask "Name of CSV File to Write to: "
csv-file: to-file csv
write csv-file "" ;to get fresh file
emit: func [blk] [repend blk]
forskip database 2 [
csv-line: make string! 1000
emit [{"} database/2/email {","} database/2/name {","}
database/2/phone
{","} database/2/web {"^/}]
write/append csv-file csv-line
]
I get the following erro message:
** Script Error: repend is missing its value argument
** Where: emit
** Near: repend blk
At 05:22 PM 6/21/2001 -0500, you wrote:
[12/22] from: louisaturk:eudoramail at: 22-Jun-2001 4:44
Sorry, I forgot about Scott's second post that made this correction.
Louis
At 04:01 AM 6/22/2001 -0500, you wrote:
[13/22] from: louisaturk:eudoramail at: 22-Jun-2001 4:58
Phil,
Many thanks. Scott, corrected this for me; I just forgot.
What are we doing up so early in the morning? Hey, I haven't gone to bed
yet! If I hit the sack now I can sleep for two hours!
Good night!
Louis
At 04:27 PM 6/22/2001 +0800, you wrote:
[14/22] from: philb:upnaway at: 22-Jun-2001 17:20
Hi Louis,
its still late afternoon/early evening here ....
just goes to show you can get help off the list alomost any time of the day :-) (Except
I wasnt much help :-/ )
Cheers Phil
-- original message --
Phil,
Many thanks. Scott, corrected this for me; I just forgot.
What are we doing up so early in the morning? Hey, I haven't gone to bed
yet! If I hit the sack now I can sleep for two hours!
Good night!
Louis
At 04:27 PM 6/22/2001 +0800, you wrote:
[15/22] from: gjones05:mail:orion at: 22-Jun-2001 13:57
From: "Dr. Louis A. Turk"
> Hi Scott,
>
> I can't get this one to work.
...
Yes, I was partially asleep at the wheel. I had only briefed through some of
the particulars of your implementation, and had forgotten that Carl S. had you
inserting the objects instead of just structured data (like, duh, why where you
calling object-oriented??? I was so busy yesterday evening that I should have
put off answering until I had more time. Sorry!) His technique apparently is
careful to pick the actual "location" that contains the email address, so that
changing it for the "key" field also changes it for the data field (they are the
same location, in essence; how clever of Carl!!).
As far as changing the other fields, here is a sample of one of your selections:
if choice = "2" [
print cls
print " CHANGE NAME"
print " ===========^/"
name-old: record/name
ask "New function needed here to edit the name field."
save-data
choice: "0"
]
record/name currently points to actual value of the current record/name (Hey,
Joel, like that tautology?). One option would be to directly assign a new name
to that field. Like:
record/name: copy ask "Change name to: "
I have no direct way to check this method with your current program, but I
believe that it would work correctly. This method would give the user very
little option to cancel a change. I guess the next statement could check for an
empty string, and, if present, reset the name back to the old name:
name-old: record/name
record/name: copy ask "Change name to: "
if record/name = "" [record/name: name-old] ;no copy command should be needed
here
I suspect that what you may want is a way to make several changes to a record,
and then decide whether to "commit" the changes. This method might prove to be
more robust, and could also update the email info if needed. It would require a
change in the flow of the program as it stands. If this is what you were hoping
for, it may be helpful to see another copy of the program as it exists after
recent updates.
> PS Your print report code and export data code works great. I haven't
> had time to put your import data code into the program yet; I'll let you
> know how it works. Thanks again.
I guess it is a miracle that it works given that I had not looked carefully
enough at the details of the implementation.
Good luck!
--Scott Jones
[16/22] from: gjones05:mail:orion at: 22-Jun-2001 9:42
From: "Dr. Louis A. Turk"
> Scott,
>
> I received your second message at the same time as the first one, but
> forgot. I remembered just a moment or two after sending my argument
> question. Sorry to waste your time that way.
You didn't waste my time. No problem.
> By the way, many thanks for your help. You have really saved me a lot of
time!
I'm glad that it helped some.
--Scott Jones
[17/22] from: louisaturk:eudoramail at: 22-Jun-2001 12:04
Hi Scott,
I can't get this one to work.
At 05:22 PM 6/21/2001 -0500, you wrote:
>change-data: func [email-old email-new /local record] [
> record: find database email-old
> if none? record [alert reform ["Missing record:" email-old] exit]
> record/1: email-new
>]
I think I may not have made clear what I need the function to do: after
finding the record using Carl's find-data function, I need a function that
will pull up whichever field I choose (with its contents), and allow me to
edit (correct typos in) those contents. The program will then write the
change to the file using Carl's save-data function. Perhaps the function
should be named edit-data instead of change-data to make its use clearer.
Louis
PS Your print report code and export data code works great. I haven't
had time to put your import data code into the program yet; I'll let you
know how it works. Thanks again.
[18/22] from: gjones05:mail:orion at: 22-Jun-2001 5:40
From: "Dr. Louis A. Turk"
> Is there an argument missing in the emit function below?
Hi, Louis,
Yes. I caught the mistake an hour after I sent the message and sent a second
message. I have reattached that message below. This emit technique is a slight
variation on Carl's suggestion to me once, so it is almost as good as coming
from "The Creator" (of REBOL, of course;-). I checked that this works, but I
used a diferent dataset so obviously I did more cutting than pasting when I
created the code for your structure. Sorry for the over site.
From: "GS Jones"
> > 6. How can data be exported from a database as a comma separated file.
> write %/path/to/my-csv-file "" ;to get fresh file
<<quoted lines omitted: 5>>
> write/append %/path/to/my-csv-file csv-line
> ]
Whoops. Error...
emit: func [blk] [repend blk]
should be ...
emit: func [blk] [repend csv-line blk]
Sorry.
--Scott Jones
[19/22] from: louisaturk:eudoramail at: 22-Jun-2001 10:17
Scott,
I received your second message at the same time as the first one, but
forgot. I remembered just a moment or two after sending my argument
question. Sorry to waste your time that way.
By the way, many thanks for your help. You have really saved me a lot of time!
Louis
At 05:40 AM 6/22/2001 -0500, you wrote:
[20/22] from: louisaturk:eudoramail at: 26-Jun-2001 4:42
Dear Carl,
At 08:38 PM 6/25/2001 -0700, you wrote:
>Louis,
>
>Sorry about the delay in getting back to you.
>Let me take a look... if I can find some time
>in the next few days. Feel free to nag me.
>
>-Carl
Many, many thanks. I am stuck! (Holger, Jeff, Scott, and others have
also given me a lot of help (some of which I obviously haven't completely
understood or implemented properly. So help from them (or anyone else)
would also be appreciated.)
The latest version of my program is below. I am having problems in the
following areas.
1. The edit-data function isn't working---possibly because I don't
understand how to use it.
2. The kbhit function usually works, but sometimes gives the following error:
Enter the # of Choice (or ESC to Exit): ** Script Error: choice has no v
alue
** Where: forever
** Near: choice = kbhit
if choice
>>
Usually I get that error when the program first starts. If I then type
kbhit from the console, and hit a key, it will work properly, and I can
restart the program and it will work.
3. Print Reports does print the data to the screen, but it also causes
loss of all the data! For your own testing purposes, after typing in some
data save it to a CSV file using choice 6 from the main menu. Then if you
lose it using choice 5 you can then read it back in using choice 7.
The rest of the program seems to be working ok, but testing and bug
reporting from anyone would be greatly apprecitated.
Louis
REBOL [Title: "REBOL Relational Object DBMS"
Date: "19 June 2001"
Version: 0.2
File: %db.r
Author: "Carl Sessenrath and Louis A. Turk"
Email: [louisaturk--eudoramail--com]
Language: 'English
Purpose: "A simple but complete relational object-oriented dbms."
Comments: {Carl Sessenrath made these object oriented database
management system functions as an example to teach me,
Louis A. Turk, how to program an object database myself.
Many thanks Carl! The comments about features below are
mostly Carl's, edited by Louis for this script header.
Features:
1. The db works from memory. Load-data brings it into
memory
where the find, remove and other functions operate on
it.
So, you have to load it first, or at least insert-data a
few times to create some data records.
2. You can expand or reduce the record definition without
corrupting or affecting the database.
3. If you expect to grow this database to a large size,
you will want to MAKE HASH! the database when you
load it. make the hash after the database is created
(after the foreach loop).
database: make hash! database
But, don't worry about that until your database gets big.
For a few hundred names, you don't need it.
4. The functions work like this:
rec: find-data [bob--example--com]
print rec/name
print rec/phone
etc.
remove-data [bob--example--com]
save-data ; write back to disk
insert-data [kit--example--com] "Kitty Carson" none
http:/www.a.com
save-data ; write back to disk
5. You can use NONE for any missing value above. You don't
need to do the save-data each time... but you must do it
sooner or later.
6. The data is organized (keyed) by email. To change an
email address, but keep the rest of the record intact,
use the function change-data. Let's examine change-data:
change-data: func [email-old email-new /local record] [
record: find database email-old
if none? record [alert reform ["Missing record:"
email-old] exit]
insert clear first record email-new ; see note below
]
Note: This is subtle, so should be noted... I'm
clearing
out the actual memory string for the old email, then
inserting
the new string into it.
Also, this string is shared. It is used both in the
database
block (as the key) and in the record object
itself. So changing
it here will change it in both places.
Please send any bug reports or features you add to:
[louisaturk--eudoramail--com]
} ; End of Comments
] ;End of Rebol Script Header
db-file: %data.r
record: context [name: email: phone: web: none] ;Place the none value in
all the varables.
database: []
load-data: has [data] [
data: load/all db-file
clear database
foreach item data [
item: make record item
repend database [item/email item]
]
database: make hash! database
]
change-data: func [email-old email-new /local record] [
record: find database email-old
if none? record [alert reform ["Missing record:" email-old] exit]
insert clear first record email-new ; See note in feature #6 above.
]
save-data: has [data] [
data: copy []
foreach [key obj] database [
append/only data third obj
]
save db-file data
]
find-data: func [email] [select database email]
remove-data: func [email] [remove/part find database email 2]
insert-data: func [email' name' phone' web'] [
repend database [
email'
make record [
email: email'
name: name'
phone: phone'
web: web'
]
]
]
edit-data: func [field index /local record] [
record: find database field
if none? record [alert reform ["Missing record:" field] exit]
record/index: field
]
{Holger and Jeff helped me with this function. I changed it some trying to
learn how to use it.}
kbhit: does [
con: open/direct/binary/no-wait console://
until [wait con]
choice: to-char pick con 1
close con
choice
]
;kbhit: does [
; con: open/direct/binary/no-wait console://
; until [wait con]
; first reduce [to-string copy con close con]
;]
{Below is a simple character based user interface for REBOL/Core added by
Louis A. Turk}
load-data
forever [
cls: "^(1B)[J" ; Clear the Screen.
print cls
print "^/ REBOL RELATIONAL OBJECT-ORIENTED DBMS" ; ^/ is a line feed.
print " =====================================^/"
print " 1. Add Record."
print " 2. Edit a Field In a Record."
print " 3. Delete Record."
print " 4. Change Email Address Only."
print " 5. Print Reports."
print " 6. Export Data To CSV File."
print " 7. Import (APPEND) Data From CSV File."
prin "^/ Enter the # of Choice (or ESC to Exit): "
choice = kbhit
if choice = #"1" [
forever [
print cls
print " ADD A RECORD"
print " ============^/"
email: ask " Email Address : "
if email = "" [break]
rec: find database email
either rec = none [
name: ask " Full Name : "
phone: ask " Area Code & Phone# : "
web: ask " Website URL : "
insert-data email name phone web
save-data
][
ask "^/ Record is already in database. Continue? "
]
] ; End forever loop for choice 1
] ; End if choice 1
if choice = #"2" [
forever [
print cls
print " EDIT A FIELD IN A RECORD"
print " ========================^/"
email-old: ask " Email Address: "
record: find database email-old
if record = none [
print cls
print "^/ Missing record: " email-old
ask "^/ Hit Any Key To Continue."
break
]
record: find-data email-old
print ["^/ 1. Email : " record/email]
print [" 2. Name : " record/name]
print [" 3. Phone : " record/phone]
print [" 4. URL : " record/web "^/"]
prin [" Enter number of field to change: "]
choice: kbhit
; I'm using this method of accessing fields since records in
databases
; to be expanded from this one may have 10 to 30 fields with
only
; one usually needing editing.
if choice = #"1" [
print cls
print " EDIT EMAIL ADDRESS"
print " ==================^/"
edit-data record/email 2
save-data
choice: "0" ; Added this hack to solve a logic problem.
ask "^/The email address has been changed to: " email-new
. Continue? ?
]
if choice = "2" [
print cls
print " CHANGE NAME"
print " ===========^/"
name-old: record/name
ask "New function needed here to edit the name field."
save-data
choice: "0"
]
if choice = "3" [
print cls
print " CHANGE PHONE"
print " ===========^/"
ask "New function needed here to edit the phone number field."
save-data
choice: "0"
]
if choice = "4" [
print cls
print " CHANGE URL"
print " ===========^/"
ask "New function needed here to edit the url field."
save-data
choice: "0"
]
] ; End forever loop
] ; End if choice 2
if choice = #"3" [
forever [
print cls
print " DELETE A RECORD"
print " ===============^/"
email-old: ask " Email Address Of Record To Delete: "
;if email-old = "" [
; break
;] ; End if.
record: find database email-old
if none? record [
print cls
print " Record not found.^/"
ask " Hit Any Key To Continue."
break
]
remove-data email-old
save-data
] ; End forever loop for choice 3.
] ; End if choice 3
if choice = #"4" [
forever [
print cls
print " CHANGE EMAIL ADDRESS ONLY"
print " ==========================^/"
email-old: ask " Email Address To Be Changed: "
if email-old = "" [break]
record: find database email-old
if none? record [
print cls
print "Missing record: " email-old
ask "Hit Any Key To Continue."
break
]
email-new: ask " New Email Address: "
change-data email-old email-new
save-data
] ; End forever loop for choice 4
] ; End if choice 4
{Scott Jones provided the core code for choices 4, 5, and 6 below.}
if choice = #"5" [
print cls
print " PRINT FORMATTED REPORTS"
print " =======================^/"
forskip database 2 [
print ["^/Email: " database/2/email]
print ["Name: " database/2/name]
print ["Phone: " database/2/phone]
print ["Web URL: " database/2/web]
]
prin "^/Continue? "
kbhit
]
if choice = #"6" [
print cls
print " EXPORT DATA TO CSV FILE"
print " =======================^/"
csv: ask "Name of CSV File to Write to: "
either not csv = "" [
csv-file: to-file csv
write csv-file "" ;to get fresh file
emit: func [blk] [repend csv-line blk]
forskip database 2 [
csv-line: make string! 1000
emit [{"} database/2/email {","} database/2/name {","}
database/2/phone
{","} database/2/web {"^/}]
write/append csv-file csv-line
]
prin "^/The file has been written. Continue? "
kbhit
][prin "^/You forget to enter a file name. Continue? " kbhit]
]
if choice = #"7" [
print cls
csv: ask "Name of CSV File to Read From: "
either not csv = "" [
csv-file: to-file csv
open/read csv-file
foreach [email name phone web] parse line none [
line: to-string read/lines csv-file
email: to-email trim email
name: trim name
phone: trim phone
either web = "" [web: none][web: to-url trim web]
if not find-data email [insert-data email name phone web]
save-data
]
prin "^/The Data Has Been Appended to the Database. Continue? "
kbhit
][prin "^/You forget to enter a file name. Continue? " kbhit]
]
] ; End forever loop for main program.
[21/22] from: gjones05:mail:orion at: 26-Jun-2001 8:08
Hi, Louis,
It looks like your database is coming along famously.
LT> The latest version of my program is below. I am having problems in the
LT> following areas.
LT> 1. The edit-data function isn't working---possibly because I don't
LT> understand how to use it.
Possibly because I didn't explain it very well. :-)
LT> 2. The kbhit function usually works, but sometimes gives the following error:
LT> Enter the # of Choice (or ESC to Exit): ** Script Error: choice has no value
LT> ** Where: forever
LT> ** Near: choice = kbhit
LT> if choice
LT> Usually I get that error when the program first starts. If I then type
LT> kbhit from the console, and hit a key, it will work properly, and I can
LT> restart the program and it will work.
Adding a default choice of 0 seemed to fix the problem.
LT> 3. Print Reports does print the data to the screen, but it also causes
LT> loss of all the data! For your own testing purposes, after typing in some
LT> data save it to a CSV file using choice 6 from the main menu. Then if you
LT> lose it using choice 5 you can then read it back in using choice 7.
Forskip has the "side effect" of causing the series pointer to point to the tail.
One must explicitly reset the series pointer back to the head of the series.
Here is what I've come up with this morning. I made a number of changes that
are dcumented within the code below. I think that it is getting close.
Good work, Louis, I think the kbhit makes it easier to use!
--Scott Jones
;;;;;;;;;;;;;;;;;;;;;;;;;
REBOL [
Title: "REBOL Relational Object DBMS"
Date: "26 June 2001"
Version: 0.3
File: %db.r
Author: "Carl Sassenrath and Louis A. Turk"
Email: [louisaturk--eudoramail--com]
Language: 'English
Purpose: "A simple but complete relational object-oriented dbms."
Comments: {Carl Sessenrath made these object oriented database
management system functions as an example to teach me,
Louis A. Turk, how to program an object database myself.
Many thanks Carl! The comments about features below are
mostly Carl's, edited by Louis for this script header.
Features:
1. The db works from memory. Load-data brings it into memory
where the find, remove and other functions operate on it.
So, you have to load it first, or at least insert-data a
few times to create some data records.
2. You can expand or reduce the record definition without
corrupting or affecting the database.
3. If you expect to grow this database to a large size,
you will want to MAKE HASH! the database when you
load it. make the hash after the database is created
(after the foreach loop).
database: make hash! database
But, don't worry about that until your database gets big.
For a few hundred names, you don't need it.
4. The functions work like this:
rec: find-data [bob--example--com]
print rec/name
print rec/phone
etc.
remove-data [bob--example--com]
save-data ; write back to disk
insert-data [kit--example--com] "Kitty Carson" none http:/www.a.com
save-data ; write back to disk
5. You can use NONE for any missing value above. You don't
need to do the save-data each time... but you must do it
sooner or later.
6. The data is organized (keyed) by email. To change an
email address, but keep the rest of the record intact,
use the function change-data. Let's examine change-data:
change-data: func [email-old email-new /local record] [
record: find database email-old
if none? record [alert reform ["Missing record:" email-old] exit]
insert clear first record email-new ; see note below
]
Note: This is subtle, so should be noted... I'm clearing
out the actual memory string for the old email, then inserting
the new string into it.
Also, this string is shared. It is used both in the database
block (as the key) and in the record object itself. So changing
it here will change it in both places.
Please send any bug reports or features you add to:
[louisaturk--eudoramail--com]
26-Jun-2001 G. Scott Jones, M.D. changed the following
1) Added check for existence of data file and check
for empty data file.
2) Commented out hash until fully debugged
3) Changed name of update function for key data from
change-data to update-key-data
4) Added default choice of 0 to avoid initial error
5) Added additional forever loop to allow editing
of multiple fields in edit record section
6) Changed from if construct to switch inside edit record
? whether need to set choice to 0 anymore
7) Changed method for updating no-key fields
8) Added ability to edit key-field email from within edit
record. ? whether need separate email update function.
9) Added checks for empty data on edit/update
10) Reset database to head after forskip (NB: forskip
leaves the series pointer pointing at the tail) in print
section
11) Changed csv import to leave data elements in same
format as "native" data file format (namely, string).
12) Made a few miscellaneous formatting changes, including
recommended REBOL indent
13) Other miscellaneous changes mostly commented in code.
} ; End of Comments
] ;End of Rebol Script Header
db-file: %data.r
record: context [name: email: phone: web: none] ;Place the none value in all the varables.
database: copy []
load-data: has [data] [
if exists? db-file [
data: load/all db-file
clear database
if data [
foreach item data [
item: make record item
repend database [item/email item]
]
]
;database: make hash! database
]
]
update-key-data: func [email-old email-new /local record] [
record: find database email-old
if none? record [alert reform ["Missing record:" email-old] exit]
insert clear first record email-new ; See note in feature #6 above.
]
; ? whether needed
edit-data: func [field index /local record] [
record: find database field
if none? record [alert reform ["Missing record:" field] exit]
record/index: field
]
save-data: has [data] [
data: copy []
foreach [key obj] database [
append/only data third obj
]
save db-file data
]
find-data: func [email] [select database email]
remove-data: func [email] [remove/part find database email 2]
insert-data: func [email' name' phone' web'] [
repend database [
email'
make record [
email: email'
name: name'
phone: phone'
web: web'
]
]
]
{Holger and Jeff helped me with this function. I changed it some trying to
learn how to use it.}
kbhit: does [
con: open/direct/binary/no-wait console://
until [wait con]
choice: to-char pick con 1
close con
choice
]
;kbhit: does [
; con: open/direct/binary/no-wait console://
; until [wait con]
; first reduce [to-string copy con close con]
;]
{Below is a simple character based user interface for REBOL/Core added by
Louis A. Turk}
load-data
choice: 0 ; to avoid initial error
forever [
cls: "^(1B)[J" ; Clear the Screen.
print cls
print "^/ REBOL RELATIONAL OBJECT-ORIENTED DBMS" ; ^/ is a line feed.
print " =====================================^/"
print " 1. Add Record."
print " 2. Edit a Field In a Record."
print " 3. Delete Record."
print " 4. Change Email Address Only."
print " 5. Print Reports."
print " 6. Export Data To CSV File."
print " 7. Import (APPEND) Data From CSV File."
prin "^/ Enter the # of Choice (or ESC to Exit): "
choice = kbhit
;may wish to change this to a switch construct in future
if choice = #"1" [
forever [
print cls
print " ADD A RECORD"
print " ============^/"
email: ask " Email Address: "
if email = "" [break] ;gsj added to avoid empty error
rec: find database email
either rec = none [
name: ask " Full Name: "
phone: ask " Area Code & Phone#: "
web: ask " Website URL: "
insert-data email name phone web
save-data
][
ask "^/ Record is already in database. Continue? "
]
] ; End forever loop for choice 1
] ; End if choice 1
if choice = #"2" [
forever [
print cls
print " EDIT A FIELD IN A RECORD"
print " ========================^/"
email-old: ask " Email Address: "
if email-old = "" [break]
forever [
record: find database email-old
if record = none [
print cls
print "^/ Missing record: " email-old
ask "^/ Hit Any Key To Continue."
break
]
record: find-data email-old
print ["^/ 1. Email : " record/email]
print [" 2. Name : " record/name]
print [" 3. Phone : " record/phone]
print [" 4. URL : " record/web]
print [" 5. Return to Edit Menu" "^/"]
prin [" Enter number of field to change: "]
choice: kbhit
; I'm using this method of accessing fields since records in databases
; to be expanded from this one may have 10 to 30 fields with only
; one usually needing editing.
switch/default choice [
#"1" [
print cls
print " EDIT EMAIL ADDRESS"
print " ==================^/"
email-new: ask rejoin [" New Email Address to replace " email-old ": "]
if email-new <> "" [
update-key-data email-old email-new
save-data
email-old: copy email-new
]
choice: "0" ; Added this hack to solve a logic problem.
]
#"2" [
print cls
print " CHANGE NAME"
print " ===========^/"
new-name: ask rejoin ["Enter name to replace " record/name ": "]
if new-name <> "" [record/name: copy new-name save-data]
choice: "0"
]
#"3" [
print cls
print " CHANGE PHONE"
print " ===========^/"
new-phone: ask rejoin ["Enter phone to replace " record/phone ": "]
if new-phone <> "" [record/phone: copy new-phone save-data]
choice: "0"
]
#"4" [
print cls
print " CHANGE URL"
print " ===========^/"
new-url: ask rejoin ["Enter url to replace " record/web ": "]
if new-url <> "" [record/web: copy new-url save-data]
choice: "0"
]
][break] ;end of switch and switch/default
] ; end of forever for editing a record
] ; End forever loop
] ; End if choice 2
if choice = #"3" [
forever [
print cls
print " DELETE A RECORD"
print " ===============^/"
email-old: ask " Email Address Of Record To Delete: "
;if email-old = "" [
; break
;] ; End if.
record: find database email-old
if none? record [
print cls
print " Record not found.^/"
ask " Hit Any Key To Continue."
break
]
remove-data email-old
save-data
] ; End forever loop for choice 3.
] ; End if choice 3
if choice = #"4" [
forever [
print cls
print " CHANGE EMAIL ADDRESS ONLY"
print " ==========================^/"
email-old: ask " Email Address To Be Changed: "
if email-old = "" [break]
record: find database email-old
if none? record [
print cls
print "Missing record: " email-old
ask "Hit Any Key To Continue."
break
]
email-new: ask rejoin [" New Email Address to replace " email-old ": "]
if email-new <> "" [
update-key-data email-old email-new
save-data
]
] ; End forever loop for choice 4
] ; End if choice 4
{Scott Jones provided the core code for choices 4, 5, and 6 below.}
if choice = #"5" [
print cls
print " PRINT FORMATTED REPORTS"
print " =======================^/"
forskip database 2 [
print ["^/ Email: " database/2/email]
print [" Name: " database/2/name]
print [" Phone: " database/2/phone]
print ["Web URL: " database/2/web]
]
prin "^/Press any key to continue..."
database: head database ;reset following forskip
kbhit
]
if choice = #"6" [
print cls
print " EXPORT DATA TO CSV FILE"
print " =======================^/"
csv: ask "Name of CSV File to Write to: "
either not csv = "" [
csv-file: to-file csv
write csv-file "" ;to get fresh file
emit: func [blk] [repend csv-line blk]
forskip database 2 [
csv-line: make string! 1000
emit [{"} database/2/email {","} database/2/name {","} database/2/phone
{","} database/2/web {"^/}]
write/append csv-file csv-line
]
database: head database ;reset following forskip
prin "^/The file has been written. Continue? "
kbhit
][prin "^/You forget to enter a file name. Continue? " kbhit]
]
if choice = #"7" [
print cls
csv: ask "Name of CSV File to Read From: "
either not csv = "" [
csv-file: to-file csv
data: read/lines csv-file
foreach line data [
foreach [email name phone web] parse line none [
email: trim email
name: trim name
phone: trim phone
web: trim web
if not find-data email [insert-data email name phone web]
save-data
]
]
prin "^/The Data Has Been Appended to the Database. Continue? "
kbhit
][prin "^/You forget to enter a file name. Continue? " kbhit]
]
] ; End forever loop for main program.
[22/22] from: louisaturk:eudoramail at: 26-Jun-2001 20:05
Hi, Scott,
At 08:08 AM 6/26/2001 -0500, you wrote:
>Hi, Louis,
>
>It looks like your database is coming along famously.
Yes, thanks to you doing most of the hard work. I've listed you as the
second author after Carl.
You guys are great teachers---Holger and Jeff also. Thanks!
I am just about ready now to add a few more fields, and put the DBMS to
practical use.
Louis
Notes
- Quoted lines have been omitted from some messages.
View the message alone to see the lines that have been omitted