NAME DBD::Sprite - A DBI driver for Flat Text Files SYNOPSIS use DBI; $dbh = DBI->connect("DBI:Sprite:spritedb",'user','password') or die "Cannot connect: " . $DBI::errstr; $sth = $dbh->prepare("CREATE TABLE a (id INTEGER, name CHAR(10))") or die "Cannot prepare: " . $dbh->errstr(); $sth->execute() or die "Cannot execute: " . $sth->errstr(); $sth->finish(); $dbh->disconnect(); WARNING THIS IS ALPHA SOFTWARE. It is *only* 'Alpha' because this is it's 1st release! DESCRIPTION The DBD::Sprite module is yet another driver for the DBI (Database independent interface for Perl). This one is based on the Sprite "engine" by Shishir Gurdavaram. It differs from DBD::CSV as follows: 1) It creates and works on true "databases" with user-ids and passwords, 2) The database author specifies the field delimiters, record delimiters, user, password, table file path, AND extension for each database. 3) Transactions (commits and rollbacks) are fully supported! 4) Autonumbering and user-defined functions are supported. 5) You don't need any other modules or databases. (NO prerequisites except Perl 5 and the DBI module! 6) It is not necessary to call the "$dbh->quote()" method all the time in your sql. 7) NULL is handled as an empty string. 8) Oracle Sequences are supported! 9) Numeric, Char(#), Varchar(#), and Long/Blob datatypes are supported! 10) Autonumbering (without sequences) is now also supported! See the DBI(3) manpage for details on DBI, the JSprite(3) manpage for details on Sprite plus my extensions. Prerequisites The only system dependent feature that DBD::File uses, is the `flock()' function. Thus the module should run (in theory) on any system with a working `flock()', in particular on all Unix machines and on Windows NT. Under Windows 95 and MacOS the use of `flock()' is disabled, thus the module should still be usable, Unlike other DBI drivers, you don't need an external SQL engine or a running server. All you need is Perl modules Installation Installing this module (and the prerequisites from above) is quite simple. You just fetch the archive, extract it with gzip -cd DBD-Sprite-#.###.tar.gz | tar xf - (this is for Unix users, Windows users would prefer WinZip or something similar) and then enter the following: cd DBD-Sprite-#.### perl Makefile.PL make make test If any tests fail, let me know. Otherwise go on with make install Note that you almost definitely need root or administrator permissions. If you don't have them, read the ExtUtils::MakeMaker man page for details on installing in your own directories. the ExtUtils::MakeMaker manpage. NOTE: You may also need to copy "makesdb.pl" to /usr/local/bin or somewhere in your path. Windows install: If installing in Windows, you must 1st install the DBI module, create a DBD subdirectory in your Perl's path (run "perl -V" to find out what this is), copy "Sprite.pm" to it, then copy the other files ( JSprite.pm, OraSpriteFns.pl, and to_date.pl to the same directory you created the DBD subdirectory in. Then copy the file makesdb.pl to the directory perl itself is in. These directories (in ActivePerl) are: c:\perl\site\lib and c:\perl\bin respectively. Getting started: 1) cd to where you wish to store your database. 2) run makesdb.pl to create your database, ie. Database name: mydb Database user: me User password: mypassword Database path: . Table file extension (default .stb): Record delimiter (default \n): Field delimiter (default ::): This will create a new database text file (mydb.sdb) in the current directory. This ascii file contains the information you enterred above. To add additional user-spaces, simply rerun makesdb.pl with "mydb" as your database name, and enter additional users (name, password, path, extension, and delimiters). For an example, after running "make test", look at the file "test.sdb". When connecting to a Sprite database, Sprite will look in the current directory, then, if specified, the path in the SPRITE_HOME environment variable. The database name, user, and password are used in the "db->connect()" method described below. The "database path" is where your tables will be created and reside. Table files are ascii text files which will have, by default, the extension ".stb" (Sprite table). By default, each record will be written to a single line (separated by \n -- Windows users should probably use "\r\n"). Each field datum will be written without quotes separated by the "field delimiter (default: double-colon). The first line of the table file consists of the a field name, an equal ("=") sign, an asterisk if it is a key field, then the datatype and size. This information is included for each field and separated by the field separator. For an example, after running "make test", look at the file "testtable.stb". 3) write your script to use DBI, ie: #!/usr/bin/perl use DBI; $dbh = DBI->connect('DBI:Sprite:mydb','me','mypassword') || die "Could not connect (".$DBI->err.':'.$DBI->errstr.")!"; ... #CREATE A TABLE, INSERT SOME RECORDS, HAVE SOME FUN! 4) get your application working. 5) rehost your application on a "production" machine and change "Sprite" to a DBI driver for a "real" database! Creating and dropping tables You can create and drop tables with commands like the following: $dbh->do("CREATE TABLE $table (id INTEGER, name CHAR(64))"); $dbh->do("DROP TABLE $table"); Note that currently only the column names will be stored and no other data. Thus all other information including column type (INTEGER or CHAR(x), for example), column attributes (NOT NULL, PRIMARY KEY, ...) will silently be discarded. This may change in a later release. A drop just removes the file without any warning. See the DBI(3) manpage for more details. Table names cannot be arbitrary, due to restrictions of the SQL syntax. I recommend that table names are valid SQL identifiers: The first character is alphabetic, followed by an arbitrary number of alphanumeric characters. If you want to use other files, the file names must start with '/', './' or '../' and they must not contain white space. Inserting, fetching and modifying data The following examples insert some data in a table and fetch it back: First all data in the string: $dbh->do("INSERT INTO $table VALUES (1, 'foobar')"); Note the use of the quote method for escaping the word 'foobar'. Any string must be escaped, even if it doesn't contain binary data. Next an example using parameters: $dbh->do("INSERT INTO $table VALUES (?, ?)", undef, 2, "It's a string!"); To retrieve data, you can use the following: my($query) = "SELECT * FROM $table WHERE id > 1 ORDER BY id"; my($sth) = $dbh->prepare($query); $sth->execute(); while (my $row = $sth->fetchrow_hashref) { print("Found result row: id = ", $row->{'id'}, ", name = ", $row->{'name'}); } $sth->finish(); Again, column binding works: The same example again. my($query) = "SELECT * FROM $table WHERE id > 1 ORDER BY id"; my($sth) = $dbh->prepare($query); $sth->execute(); my($id, $name); $sth->bind_columns(undef, \$id, \$name); while ($sth->fetch) { print("Found result row: id = $id, name = $name\n"); } $sth->finish(); Of course you can even use input parameters. Here's the same example for the third time: my($query) = "SELECT * FROM $table WHERE id = ?"; my($sth) = $dbh->prepare($query); $sth->bind_columns(undef, \$id, \$name); for (my($i) = 1; $i <= 2; $i++) { $sth->execute($id); if ($sth->fetch) { print("Found result row: id = $id, name = $name\n"); } $sth->finish(); } See the DBI(3) manpage for details on these methods. See the SQL::Statement(3) manpage for details on the WHERE clause. Data rows are modified with the UPDATE statement: $dbh->do("UPDATE $table SET id = 3 WHERE id = 1"); Likewise you use the DELETE statement for removing rows: $dbh->do("DELETE FROM $table WHERE id > 1"); fn_register Method takes 2 arguments: Function name and optionally, a package name (default is "main"). $dbh->fn_register ('myfn','mypackage'); -or- use JSprite; JSprite::fn_register ('myfn',__PACKAGE__); Then, you could say in sql: insert into mytable values (myfn(?)) and bind some value to "?", which is passed to "myfn", and the return-value is inserted into the database. You could also say (without binding): insert into mytable values (myfn('mystring')) -or (if the function takes a number)- select field1, field2 from mytable where field3 = myfn(123) Return Value None You can now also set up autonumbering fields without sequences. For example: create table mytable ( id AUTONUMBER, value VARCHAR(40), primary key (id) ) Then, insert records either of these ways: insert into mytable values ('Value for sequence number 1'); insert into mytable values (NULL, 'Value for sequence number 2'); The 1st record automatically gets id set to 1, the 2nd, id set to 2, etc. Attempts to update an "AUTONUMBER" field will return an error. You can also capture parts of current values of fields and update those and or other fields using those values based on Perl pattern matching and capturing, ie.: update MYTABLE set FIELD1 = '$1.$2' where FIELD2 =~ '(\d)(\d+)' This will set FIELD1 to the 1st digit found in FIELD2 followed by a decimal point, followed by any subsequent digits in FIELD2 in the same record! Up to 2 matches for each "where" expression containing "=~" or "!~" may be captured. $1 .. $n correspond to each set of unescaped parenthesis from left to right in the "where" clause. Error handling In the above examples we have never cared about return codes. Of course, this cannot be recommended. Instead we should have written (for example): my($query) = "SELECT * FROM $table WHERE id = ?"; my($sth) = $dbh->prepare($query) or die "prepare: " . $dbh->errstr(); $sth->bind_columns(undef, \$id, \$name) or die "bind_columns: " . $dbh->errstr(); for (my($i) = 1; $i <= 2; $i++) { $sth->execute($id) or die "execute: " . $dbh->errstr(); if ($sth->fetch) { print("Found result row: id = $id, name = $name\n"); } } $sth->finish($id) or die "finish: " . $dbh->errstr(); Obviously this is tedious. Fortunately we have DBI's *RaiseError* attribute: $dbh->{'RaiseError'} = 1; $@ = ''; eval { my($query) = "SELECT * FROM $table WHERE id = ?"; my($sth) = $dbh->prepare($query); $sth->bind_columns(undef, \$id, \$name); for (my($i) = 1; $i <= 2; $i++) { $sth->execute($id); if ($sth->fetch) { print("Found result row: id = $id, name = $name\n"); } } $sth->finish($id); }; if ($@) { die "SQL database error: $@"; } This is not only shorter, it even works when using DBI methods within subroutines. Metadata The following attributes are handled by DBI itself and not by DBD::File, thus they should all work as expected: I have only used the last 3. Active ActiveKids CachedKids CompatMode (Not used) InactiveDestroy Kids PrintError RaiseError Warn The following DBI attributes are handled by DBD::Sprite: AutoCommit Works ChopBlanks Should Work NUM_OF_FIELDS Valid after `$sth->execute' NUM_OF_PARAMS Valid after `$sth->prepare' NAME Valid after `$sth->execute'; undef for Non-Select statements. NULLABLE Not really working. Always returns an array ref of one's, as DBD::Sprite always allows NULL (handled as an empty string). Valid after `$sth->execute'. PRECISION Works SCALE Works LongReadLen works, except setting to zero allows any length of data to be read. LongTruncOk Works These attributes and methods are not supported: bind_param_inout CursorName In addition to the DBI attributes, you can use the following dbh attributes. These attributes are read-only after "connect". sprite_dbdir Path to tables for database. sprite_dbext File extension used on table files in the database. sprite_dbuser Current database user. sprite_dbfdelim Field delimiter string in use for the database. sprite_dbrdelim Record delimiter string in use for the database. The following are Sprite-specific options which can be set when connecting. sprite_CaseTableNames By default, table names are case-insensitive (as they are in Oracle), to make table names case-sensitive (as in MySql), so that one could have two separate tables such as "test" and "TEST", set this option to 1. sprite_Crypt (NEW) "0" by defalt. Specifies that encryption is to be used when storing the data in the flat-file. To use, download "Crypt::CBC", and one or more of "Crypt::DES", "Crypt::IDEA", or "Crypt::Blowfish". You can specify using any of the following formats: sprite_Crypt => 'my key string' Use Blowfish encryption. sprite_Crypt => 'DES;my key string' Use DES encryption. sprite_Crypt => 'encrypt=CBC;IDEA;my key string' Use IDEA encription, but read in table as unencrypted, then write it out encrypted (great for encrypting previously unencrypted tables). sprite_Crypt => 'decrypt=CBC;Blowfish;my key string' use Blowfish encryption, but write out table unencrypted. This allows one to fetch an encrypted table and write it back out unencrypted. sprite_StrictCharComp CHAR fields are always right-padded with spaces to fill out the field. Old (pre 5.17) Sprite behaviour was to require the padding be included in literals used for testing equality in "where" clauses. I discovered that Oracle and some other databases do not require this when testing DBIx-Recordset, so Sprite will automatically right-pad literals when testing for equality. To disable this and force the old behavior, set this option to 1. The following are environment variables specifically recognized by Sprite. SPRITE_HOME Environment variable specifying a path to search for Sprite databases (*.sdb) files. Driver private methods DBI->data_sources() The `data_sources' method returns a list of "databases" (.sdb files) found in the current directory and, if specified, the path in the SPRITE_HOME environment variable. $dbh->tables() This method returns a list of table names specified in the current database. Example: my($dbh) = DBI->connect("DBI:Sprite:mydatabase",'me','mypswd'); my(@list) = $dbh->func('tables'); Other Utilities makesdb.pl This utility lets you build new Sprite databases and later add additional user-spaces to them. Simply cd to the directory where you wish to create / modify a database, and run. It prompts as follows: Database name: Enter a 1-word name for your database. Database user: Enter a 1-word user-name. User password: Enter a 1-word password for this user. Database path: Enter a path (no trailing backslash) to store tables. Table file extension (default .stb): Record delimiter (default \n): Field delimiter (default ::): The last 6 prompts repeat until you do not enter another user-name allowing you to set up multiple users in a single database. Each "user" can have it's own separate tables by specifying different paths, file-extensions, password, and delimiters! You can invoke "makesdb.pl" on an existing database to add new users. You can edit it with vi to remove users, delete the 5 lines starting with the path for that user. The file is all text, except for the password, which is encrypted for your protection! Data restrictions Although DBD::Sprite supports the following datatypes: NUMBER FLOAT DOUBLE INT INTEGER NUM CHAR VARCHAR VARCHAR2 DATE LONG BLOB and MEMO, there are really only 4 basic datatypes (NUMBER, CHAR, VARCHAR, and BLOB). This is because Perl treates everything as simple strings. The first 5 are all treated as "numbers" by Perl for sorting purposes and the rest as strings. This is seen when sorting, ie NUMERIC types sort as 1,5,10,40,200, whereas STRING types sort these as 1,10,200,40,5. CHAR fields are right- padded with spaces when stored. LONG/BLOB-type fields are subject to truncation by the "LongReadLen" attribute value. DBD::Sprite works with the tieDBI module, if "Sprite => 1" lines are added to the "%CAN_BIND" and "%CAN_BINDSELECT" hashes. This should not be necessary, and I will investigate when I have time. TODO Add "BLOB" datatype that will store it's contents in separate files. (Currently all "BLOB/MEMO" data is stored in the same flatfile as the rest of the data making for huge (and slow) tables (Hopefully, next release). Joins The current version of the module works with single table SELECTs only. This will be a trick, since the underlying statement object in JSprite is bound to a single file, I have some ideas and am starting to seriously look into this. Stay tuned! Additional Oracle-ish functions as requested. Whatever Mr. Gurdavaram might wish to add. KNOWN BUGS * The module is using flock() internally. However, this function is not available on platforms. Using flock() is disabled on MacOS and Windows 95: There's no locking at all (perhaps not so important on these operating systems, as they are for single users anyways). * Unique-key violations on updates probably will not be caught if the argument is a function-call or a field-name. * If fields specified in the "order-by" clause are not also fetched, ordering does not work quite right :-( AUTHOR AND COPYRIGHT This module is Copyright (C) 2000, 2001 by Jim Turner Email: jim.turner@lmco.com All rights reserved. You may distribute this module under the terms of either the GNU General Public License or the Artistic License, as specified in the Perl README file. JSprite.pm is a derived work by Jim Turner from Sprite.pm, a module written and copyrighted (c) 1995-1998, by Shishir Gurdavaram (shishir@ora.com). Changes Revision history for Perl extension DBD::Sprite. 0.01 Fri Feb 25 13:00:55 2000 - original version; created by h2xs 1.18 0.02 Fri Mar 03 - Fixed single-quote handling on bound parameters. 0.07 Fri Apr 14 - Refixed test-14 failure (CHAR datatype). Also create and alter table commands should now work properly (save your table 1st! ;) 0.08 Wed May 03 - Fix Windows glob problem for Perl2EXE - Added missing file "OraSpriteFns.pl" to build 0.09 Fri Jun 09 - Fix bug in DELETE which occassionally caused infinite loop. 0.10 Wed Aug 16 - Fix bug involving "like" and field values containing regex- special characters, ie. ".". 0.11 Thu Aug 31 - Fix regex bug in Sprite.pm and add error-checking for SQL - commands wo/space after table-name, ie "update table-name\n...". 0.12 Tue Sep 05 - Add size fields are truncated to to "-519" error message. 0.13 Tue Oct 10 - Fix CaseTableNames attribute to "sprite_CaseTableNames. - Minor docs fix. 0.14 Thu Oct 12 - Added Oracle TO_DATE and CONCAT functions. Fixed bug now allowing more than 1 argument for functions, and minor bug which stripped wild-card chars from function results in "LIKE" expressions. 0.15 Thu Nov 02 - Fix bug that caused question marks in bound parameter values to be treated as additional parameters. 0.16 Thu Dec 21 - Fixed bug which would not allow lower-case versions of the "AND" and "OR" operators. Also made field-names used as right-values return the appropriate value they represent rather than just a literal string of the field name, ie. "update table set FIELD1 = FIELD2" should now work properly instead of setting FIELD1 to "FIELD2" in every record of "table". 0.17 Fri Mar 09 - Fixed bug where types not returned for cursor after update, Fixed bug where table names not returned if space appeared after "table_name"? - Fixed bug in "makesdb.pl" -- was encrypting wrong argument. 0.18 Wed Mar 13 - Changed comparisens of literal data with "CHAR" fields to first pad the literal with appropriate spaces so that eqality tests would pass (to work like Oracle and the way DBIx-Recordset expects). Also added new option "StrictCharComp" to force the old behavior. - Fixed bug that occasionally inserted a "0" instead of "" in numeric fields when inserting NULL. To get the proper "old" behavior, specify a default of zero for each numeric field, ie. "NUMFIELD=NUMBER=0" in the top line of your Sprite table files. - Added some code to check syntax of "where" clauses. (for DBIx- Recordset. - Fixed a couple of other small bugs that DBIx-Recordset tests found. 0.19 Wed Mar 21 - Fixed problem seeing all Windows file-names due to case-insensitivity in Windows. 0.20 Fri Aug 15 - Fixed bug involving autocommit on statements containing "select" in data. Caught by Simon Elliott, Thanks! Also fixed several other minor glitches. - Fixed bug forbidding sequence files in mixed-case directories unless CaseTableNames was set (CaseTableNames only applies to the file-name). Also added code to display "$@" and "$?" in the error details message on -511 errors (could not [over]write file). 0.21 Wed Sep 12 - Fixed AutoCommit to actually work. Caught by Jojo Kamote Escubil, Thanks, Jojo! Also fixed another bug affecting quoted value strings containing question marks (Caught by me!) 0.22 Mon Sep 17 - Removed regex optimization (s///o) which caused record and field separators to not work right when one opened a database, closed that database, then opened a second database with different record and or field separators. Caught by Larry Yudelson, Thanks! 0.25 Fri Oct 26 - Added new regex feature to allow one to capture regex matches and update field values, ie: - Added TO_CHAR function for converting Perl "time" values to printable formats. Has most date and numeric functions in the two-argument form of the Oracle "TO_CHAR" function, which it is designed to emulate. - Added some optimization for mass-updates. - Added many other new Oracle functions (TO_NUMBER, USER, ABS, COS, EXP, FLOOR, MOD, POWER, ROUND, SIGN, SIN, TRUNC, CHR, INITCAP, LTRIM, REPLACE, RTRIM, TRANSLATE, ASCII, LENGTH, SYSDATE. All should now work when selected from "DUAL". 0.26 Mon Oct 29 - Fixed bug introduced in 0.24 where tests failed not finding "makesdb.pl". - Fixed test failure on Windows platforms (I think). - Added still more Oraclish functions (CEIL, EXP, INSTR, INSTRB, LAST_DAY, LPAD, NVL, RPAD, LAST_DAY, and SQRT. Fixed FLOOR. - Added date-handling capability to TRUNC. It should be noted that these functions do almost no error-checking. - Added "AUTONUMBER" datatype to allow autonumbering without sequences. This emulates the way M$-Access(ODBC) does things. 0.27 Fri Nov 15 - Eliminated "reserved" status of special characters \x02 .. \x06 and \n by now using single "reserved" string pattern: "\x02\^#jSpR1tE\x02", where "#" can be any digit 0..9. The purpose of this is to allow for temporarily protecting some strings while performing string searches and substitutions, ie. protecting commas within quoted literal values while splitting the encompassing string on commas as separators. \n has previously been "flattened" out into spaces in order to treat multiline queries as a single line. This is still done, but numerous regices were modified with the "s" option. Please report anything that might be broken by this new release! 0.30 Thu Jan 10 - Fixed bug where specifying a negative number in a "where" clause as an unbound constant, returned all records and set the field in all records to that number (ouch!) - Fixed bug there double-backslashes in data inserted into a table were converted to a single backslash. - Added better "BLOB" support. BLOB/LONG/MEMO fields no longer have default length specified as 5000 and data should no longer be truncated. - ADDED ENCRYPTION OPTION! If the "Crypt::CBC", and "Crypt::IDEA" or "Crypt::DES" are available, you can connect specifying "sprite_Crypt => 'IDEA;any_key_string'" (use "DES" in lieu of "IDEA", if so inclined). To read in existing unencrypted tables and write them out encrypted, use "sprite_Crypt => 'encrypt=IDEA;your_key_string'". To read in encrypted databases and write them back unencrypted, use: "sprite_Crypt => 'decrypt=IDEA;your_key_string'". Note: Any method of encryption that Crypt::CBC supports may be used instead of IDEA or DES, if you have the module installed. SEE ALSO JSprite(3), DBI(3), perl(1) For general information on DBI see http://www.symbolstone.org/technology/perl/DBI