cvs: pear /DBA DBA.php DBA_Builtin.php DBA_Compat.php DBA_Relational.php DBA_Simple.php DBA_Table.php package.xml /DBA/tests benchmark.php
graph_template simple.php
| From: | Brent Cook | Date: | Thu, 20 Jun 2002 18:26:20 +0000 |
| Subject: | cvs: pear /DBA DBA.php DBA_Builtin.php DBA_Compat.php DBA_Relational.php DBA_Simple.php DBA_Table.php package.xml /DBA/tests benchmark.php graph_template simple.php |
||
| Groups: | php.pear.cvs | ||
| Request: | Send a blank email to pear-cvs+get-4715@lists.php.net to get a copy of this message | ||
busterb Thu Jun 20 14:26:20 2002 EDT
Added files:
/pear/DBA DBA.php DBA_Builtin.php DBA_Compat.php DBA_Relational.php
DBA_Simple.php DBA_Table.php package.xml
/pear/DBA/tests benchmark.php graph_template simple.php
Log:
Initial import of DBA - class for handling and extending DBM-style databases
Index: pear/DBA/DBA.php +++ pear/DBA/DBA.php <?php /* vim: set expandtab tabstop=4 shiftwidth=4: */ // +----------------------------------------------------------------------+ // | PHP Version 4 | // +----------------------------------------------------------------------+ // | Copyright (c) 1997-2002 The PHP Group | // +----------------------------------------------------------------------+ // | This source file is subject to version 2.02 of the PHP license, | // | that is bundled with this package in the file LICENSE, and is | // | available at through the world-wide-web at | // | http://www.php.net/license/2_02.txt. | // | If you did not receive a copy of the PHP license and are unable to | // | obtain it through the world-wide-web, please send a note to | // | license@php.net so we can mail you a copy immediately. | // +----------------------------------------------------------------------+ // | Authors: Brent Cook <busterb@mail.utexas.edu> | // +----------------------------------------------------------------------+ // // $Id: DBA.php,v 1.1 2002/06/20 18:26:19 busterb Exp $ // class DBA { /** * Return a DBA object * * @static * @param string $driver Type of storage object to return * @return object Object DBA storage object */ function create($driver = 'simple') { if (!function_exists('dba_open') || ($driver=='simple')) { require_once '../DBA_Simple.php'; return new DBA_Simple(); } else { require_once 'DBA_Builtin.php'; return new DBA_Builtin($driver); } } } ?> Index: pear/DBA/DBA_Builtin.php +++ pear/DBA/DBA_Builtin.php <?php /* vim: set expandtab tabstop=4 shiftwidth=4: */ // +----------------------------------------------------------------------+ // | PHP Version 4 | // +----------------------------------------------------------------------+ // | Copyright (c) 1997-2002 The PHP Group | // +----------------------------------------------------------------------+ // | This source file is subject to version 2.02 of the PHP license, | // | that is bundled with this package in the file LICENSE, and is | // | available at through the world-wide-web at | // | http://www.php.net/license/2_02.txt. | // | If you did not receive a copy of the PHP license and are unable to | // | obtain it through the world-wide-web, please send a note to | // | license@php.net so we can mail you a copy immediately. | // +----------------------------------------------------------------------+ // | Author: Brent Cook <busterb@mail.utexas.edu> | // +----------------------------------------------------------------------+ // // $Id: DBA_Builtin.php,v 1.1 2002/06/20 18:26:20 busterb Exp $ /** * DBA_Builtin uses the builtin dba functions of PHP as the underlying driver * for a DBA class. Depending on the driver, this can be faster or slower than * the DBA_Simple class. * * @author Brent Cook * @version 0.0.9 * @access public * @package DBA */ class DBA_Builtin { /** * Name of the database * @access private */ var $_dbName; /** * Indicates the current ability for read/write operations * @access private */ var $_writable; /** * Indicates the current ability for read operations * @access private */ var $_readable; /** * Name of the builtin dba driver to use * @access private */ var $_driver = NULL; /** * Indicates the ability of the dba driver to replace values * @access private */ var $_hasReplace; /* Constructor * * @param string $driver dba driver to use */ function DBA_Builtin ($driver = 'gdbm') { $this->_driver = $driver; } /** * Opens a database. * * @param string $dbName The name of a database * @param string $mode The mode in which to open a database. * 'r' opens read-only. * 'w' opens read-write. * 'n' creates a new database and opens read-write. * 'c' creates a new database if the database does not * exist and opens read-write. * @param string $driver dba driver to use * @returns boolean true on success, false on failure */ function open ($dbName='', $mode='r', $driver=NULL) { if (!is_null($driver)) { $this->_driver = $driver; } if (is_null($this->_driver)) { trigger_error('DBA: No dba driver specified'); return false; } if ($this->_driver == 'gdbm') { $this->_hasReplace = false; } else { $this->_hasReplace = true; } if ($dbName == '') { trigger_error('DBA: No database name specified', E_USER_WARNING); return false; } else { $this->_dbName = $dbName; } switch ($mode) { case 'r': // open for reading $this->_writable = false; $this->_readable = true; break; case 'n': case 'c': case 'w': $this->_writable = true; $this->_readable = true; break; default: trigger_error("DBA: Invalid file mode: $mode", E_USER_ERROR); return false; } // open the index file $this->_dba = dba_open($dbName, $mode, $this->_driver); if ($this->_dba === false) { $this->_writable = false; $this->_readable = false; trigger_error("DBA: Could not open database: $dbName" ." with mode $mode"); return false; } return true; // everything worked out } /** * Closes an open database. * * @returns boolean true on success, false on failure */ function close () { if ($this->isOpen()) { $this->_readable = false; $this->_writable = false; dba_close($this->_dba); return true; } else { return trigger_error('DBA: No database was open', E_USER_WARNING); return false; } } /** * Reopens an already open database in read-only or write mode. * If the database is already in the requested mode, then this function * does nothing. * * @param string $mode 'r' for read-only, 'w' for read/write * @returns boolean true on success, false on failure */ function reopen ($mode) { if ($this->isOpen()) { if (($mode == 'r') && $this->isWritable()) { // Reopening as read-only $this->close(); return $this->open($this->_dbName, 'r'); } elseif (($mode == 'w') && (!$this->isWritable)) { // Reopening as read-write $this->close(); return $this->open($this->_dbName, 'w'); } else { return true; } } else { trigger_error('DBA: No database was open', E_USER_WARNING); return false; } } /** * Returns the current read status for the database * * @returns boolean */ function isOpen() { return ($this->_readable || $this->_writable); } /** * Returns the current read status for the database * * @returns boolean */ function isReadable() { return $this->_readable; } /** * Returns the current write status for the database * * @returns boolean */ function isWritable() { return $this->_writable; } /** * Deletes the value at location $key * * @param string $key key to delete * @returns boolean true on success, false on failure */ function delete($key) { if ($this->isWritable()) { if (!dba_delete($key, $this->_dba)) { trigger_error('DBA: cannot delete key: '. $key. ', it does not exist', E_USER_WARNING); return false; } } else { trigger_error('DBA: cannot delete key '. $key. ', DB not writable', E_USER_WARNING); return false; } } /** * Returns the value that is stored at $key. * * @param string $key key to examine * @returns mixed the requested value on success, false on failure */ function fetch($key) { if ($this->isReadable()) { if (dba_exists($key)) { return dba_fetch($key, $this->_dba); } else { trigger_error('DBA: cannot fetch key '.$key. ', it does not exist', E_USER_WARNING); return false; } } else { trigger_error('DBA: cannot fetch '.$key.' on '. $this->_dbName. ', DB not readable', E_USER_WARNING); return false; } } /** * Returns the first key in the database * * @returns mixed string on success, false on failure */ function firstkey() { if ($this->isReadable() && ($this->size() > 0)) { return dba_firstkey($this->_dba); } else { return false; } } /** * Returns the next key in the database, false if there is a problem * * @returns mixed string on success, false on failure */ function nextkey() { if ($this->isReadable()) { return dba_nextkey($this->_dba); } else { return false; } } /** * Inserts a new value at $key. Will not overwrite if the key/value pair * already exist * * @param string $key key to insert * @param string $value value to store * @returns boolean true on success, false on failure */ function insert($key, $value) { if ($this->isWritable()) { if ((!$this->_hasReplace && dba_exists($key, $this->_dba)) || (!dba_insert($key, $value, $this->_dba))) { trigger_error('DBA: cannot insert on key: '. $key. ', it already exists', E_USER_WARNING); return false; } else { return true; } } else { trigger_error('DBA: cannot replace on '. $this->_dbName. ', DB not writable', E_USER_WARNING); return false; } } /** * Inserts a new value at key. If the key/value pair * already exist, overwrites the value * * @param $key string the key to insert * @param $val string the value to store * @returns boolean true on success, false on failure */ function replace($key, $value) { if ($this->isWritable()) { if ($this->_hasReplace) { return dba_replace($key, $value, $this->_dba); } else { $r = true; if (dba_exists($key, $this->_dba)) { $r = dba_delete($key, $this->_dba); } return $r && dba_insert($key, $value, $this->_dba); } } else { trigger_error('DBA: cannot replace on '. $this->_dbName. ', DB not writable', E_USER_WARNING); return false; } } /** * Creates a new database file if one does not exist. If it already exists, * updates the last-updated timestamp on the database * * @param string $dbName the database to create * @param string $driver the dba driver to use * @returns boolean true on success, false on failure */ function create($dbName, $driver='gdbm') { $db = dba_open($dbName, 'n', $driver); if (($db !== false) && dba_close($db)) { return true; } else { trigger_error('DBA: Could not create database: '.$dbName); return false; } } /** * Indicates whether a database with given name exists * * @param string $dbName the database name to check for existence * @returns boolean */ function db_exists($dbName) { return file_exists($dbName); } /** * Check whether key exists * * @param string $key * @returns boolean */ function exists($key) { return ($this->isOpen() && dba_exists($key, $this->_dba)); } /** * Synchronizes an open database to disk */ function sync() { return dba_sync($this->_dba); } /** * Optimizes an open database */ function optimize() { return dba_optimize($this->_dba); } } ?> Index: pear/DBA/DBA_Compat.php +++ pear/DBA/DBA_Compat.php <?php /* vim: set expandtab tabstop=4 shiftwidth=4: */ // +----------------------------------------------------------------------+ // | PHP Version 4 | // +----------------------------------------------------------------------+ // | Copyright (c) 1997-2002 The PHP Group | // +----------------------------------------------------------------------+ // | This source file is subject to version 2.02 of the PHP license, | // | that is bundled with this package in the file LICENSE, and is | // | available at through the world-wide-web at | // | http://www.php.net/license/2_02.txt. | // | If you did not receive a copy of the PHP license and are unable to | // | obtain it through the world-wide-web, please send a note to | // | license@php.net so we can mail you a copy immediately. | // +----------------------------------------------------------------------+ // | Author: Brent Cook <busterb@mail.utexas.edu> | // +----------------------------------------------------------------------+ // // $Id: DBA_Compat.php,v 1.1 2002/06/20 18:26:20 busterb Exp $ /** * dba compatibility layer * @author Brent Cook <busterb@mail.utexas.edu> * @version 0.1 * @see PHP dba Documentation */ require_once ('DBA_simple.php'); if (!function_exists('dba_open')) { function dba_close(&$dba) { $result = $dba->close(); unset($dba); return $result; } function dba_delete($key, &$dba) { return $dba->delete($key); } function dba_exists($key, &$dba) { return $dba->exists($key); } function dba_fetch($key, &$dba) { return $dba->fetch($key); } function dba_firstkey(&$dba) { return $dba->firstkey(); } function dba_insert($key, $value, &$dba) { return $dba->insert($key, $value); } function dba_nextkey(&$dba) { return $dba->nextkey($key); } function dba_open($filename, $mode, $handler) { $dba = new DBA_Simple(); $dba->open($filename, $mode); return $dba; } function dba_optimize() { return $dba->optimize(); } function dba_popen() { return FALSE; } function dba_replace($key, $value, &$dba) { return $dba->replace($key, $value); } function dba_sync() { return $dba->sync(); } } ?> Index: pear/DBA/DBA_Relational.php +++ pear/DBA/DBA_Relational.php <?php /* vim: set expandtab tabstop=4 shiftwidth=4: */ // +----------------------------------------------------------------------+ // | PHP Version 4 | // +----------------------------------------------------------------------+ // | Copyright (c) 1997-2002 The PHP Group | // +----------------------------------------------------------------------+ // | This source file is subject to version 2.02 of the PHP license, | // | that is bundled with this package in the file LICENSE, and is | // | available at through the world-wide-web at | // | http://www.php.net/license/2_02.txt. | // | If you did not receive a copy of the PHP license and are unable to | // | obtain it through the world-wide-web, please send a note to | // | license@php.net so we can mail you a copy immediately. | // +----------------------------------------------------------------------+ // | Author: Brent Cook <busterb@mail.utexas.edu> | // +----------------------------------------------------------------------+ // // $Id: DBA_Relational.php,v 1.1 2002/06/20 18:26:20 busterb Exp $ /** * a relational database manager for DBM-style databases * * @author Brent Cook <busterb@mail.utexas.edu> * @version 0.0.1 */ require_once('DBA_Table'); class DBA_Relational { // table handles var $_tables=array(); // location of table data files var $prefix; /** * Constructor * @param $prefix */ function RoundDB ($prefix = '') { // add trailing slash if not present if (substr($prefix, -1) != '/') $this->prefix = "$prefix/"; else $this->prefix = $prefix; } function close () { foreach ($this->_tables as $table) $table->close(); } function getVersion () { return "RoundDB v0.0.1"; } /** * Opens a database. * @param $tableName * @param $mode */ function openTable ($tableName, $mode = 'r') { if (!isset($this->_tables[$tableName])) { $this->_tables[$tableName] = new DBMTable($this->prefix.$tableName, $mode); if (!$this->_tables[$tableName]->isOpen()) { echo "Table '$tableName' does not exist\n"; unset($this->_tables[$tableName]); return False; } } if ($this->_tables[$tableName]->isOpen()) { if ((($mode == 'r') && $this->_tables[$tableName]->isReadable()) || (($mode == 'w') && $this->_tables[$tableName]->isWritable())) { return True; } else { $this->_tables[$tableName]->close(); } } return $this->_tables[$tableName]->open($this->prefix.$tableName, $mode); } function formatResults($results, $fields=null) { if (is_array($results) && sizeof($results)) { if (is_null($fields)) $fields = array_keys(current($results)); // get the maximum length of each field foreach ($fields as $key=>$field) { $longest[$key] = strlen($field) + 1; foreach ($results as $result) { $resultLen = strlen($result[$field]) + 1; if ($resultLen > $longest[$key]) $longest[$key] = $resultLen; } } // generate separator line foreach ($longest as $length) $separator .= '+-'.str_repeat('-',$length); $separator .= "+\n"; $buffer = $separator; // print fields foreach ($fields as $key=>$field) $buffer .= '| '.str_pad($field, $longest[$key]); $buffer .= "|\n$separator"; // print rows foreach ($results as $result) { foreach ($fields as $key=>$field) $buffer .= '| '.str_pad($result[$field], $longest[$key]); $buffer .= "|\n$separator"; } } return $buffer; } /** * */ function closeTable ($tableName) { if (isset($this->_tables[$tableName])) return $this->_tables[$tableName]->close(); else return False; } function createTable ($tableName, $schema) { // check if this table object exists if (!isset($this->_tables[$tableName])) $this->_tables[$tableName] = new DBMTable(); else return False; // the table object exists, so the table must exist // ask if the table really exists if (!$this->_tables[$tableName]->exists($this->prefix.$tableName)) return $this->_tables[$tableName]->create($this->prefix.$tableName, $schema); else return False; } function isOpen ($tableName) { if (isset($this->_tables[$tableName])) return $this->_tables[$tableName]->isOpen(); else return False; } function dropTable ($tableName) { if ($this->openTable($tableName, 'w')) { unset($this->_tables[$tableName]); return $this->_tables[$tableName]->dropTable($tableName); } else { return False; } } function insertRow ($tableName, $data) { if ($this->openTable($tableName, 'w')) return $this->_tables[$tableName]->insertRow($data); else return False; } function replaceRow ($tableName, $key, $data) { if ($this->openTable($tableName, 'w')) return $this->_tables[$tableName]->replaceRow($key, $data); else return False; } function deleteRow ($tableName, $key) { if ($this->openTable($tableName, 'w')) return $this->_tables[$tableName]->deleteRow($key); else return False; } function fetchRow ($tableName, $key) { if ($this->openTable($tableName, 'r')) return $this->_tables[$tableName]->fetchRow($key); else return False; } function select ($tableName, $query, $rows=null) { if ($this->openTable($tableName, 'r')) { return $this->_tables[$tableName]->select($query, $rows); } else { return False; } } function sort ($tableName, $fields, $order='a', $rows=null) { if ($this->openTable($tableName, 'r')) return $this->_tables[$tableName]->sort($fields, $order, $rows); else return False; } function project ($tableName, $fields, $rows=null) { if ($this->openTable($tableName, 'r')) return $this->_tables[$tableName]->project($fields, $rows); else return False; } function unique ($tableName, $rows=null) { if ($this->openTable($tableName, 'r')) return $this->_tables[$tableName]->unique($rows); else return False; } function finalizeRows($tableName, $rows=null) { if ($this->openTable($tableName, 'r')) return $this->_tables[$tableName]->finalizeRows($rows); else return False; } function _validateTable (&$table, &$rows, &$fields, $altName) { // validate query by checking for existence of fields if (is_string($table) && ($this->openTable($table, 'r'))) { $rows = $this->_tables[$table]->getRows(); $fields = $this->_tables[$table]->getFieldNames(); return True; } elseif (is_array($table) && sizeof($table)) { reset($table); $rows = $table; $fields = array_keys(current($table)); $table = $altName; return True; } $fields = null; $rows = null; return False; } function _parsePHPQuery ($rawQuery, $fieldsA, $fieldsB, $tableA, $tableB) { // add spaces around symbols for strtok to work properly $rawQuery = DBMTable::addSpaces($rawQuery); // begin building the php query for a row $phpQuery = ''; // scan the tokens in the raw query to build a new query // if the token is a field name, use it as a key in $row[] $token = strtok($rawQuery, ' '); while ($token) { // is this token a field name? if ($i = strpos($token, '.')) { $table = substr($token, 0, $i); // trim everything after the '.' $field = substr($token, $i+1); // trim everything before the '.' if (($table == $tableA) && in_array($field, $fieldsA)) { $phpQuery .= "\$rowA['$field']"; } else if (($table == $tableB) && in_array($field, $fieldsB)) { $phpQuery .= "\$rowB['$field']"; } } else { $phpQuery .= $token; } $token = strtok(' '); } return $phpQuery; } function join ($tableA, $tableB, $rawQuery) { // validate tables if (!$this->_validateTable($tableA, $rowsA, $fieldsA, 'A')) return False; if (!$this->_validateTable($tableB, $rowsB, $fieldsB, 'B')) return False; // check for empty tables if (is_null($rowsA) && !is_null($rowsB)) return $rowsB; if (!is_null($rowsA) && is_null($rowsB)) return $rowsA; if (is_null($rowsA) && is_null($rowsB)) return array(); // build the join operation with nested loops $PHPJoin = 'foreach ($rowsA as $rowA) foreach ($rowsB as $rowB) if ('. $this->_parsePHPQuery($rawQuery, $fieldsA, $fieldsB, $tableA, $tableB) .') $results[] = array_merge($rowA, $rowB);'; // evaluate the join eval ($PHPJoin); return $results; } } Index: pear/DBA/DBA_Simple.php +++ pear/DBA/DBA_Simple.php <?php /* vim: set expandtab tabstop=4 shiftwidth=4: */ // +----------------------------------------------------------------------+ // | PHP Version 4 | // +----------------------------------------------------------------------+ // | Copyright (c) 1997-2002 The PHP Group | // +----------------------------------------------------------------------+ // | This source file is subject to version 2.02 of the PHP license, | // | that is bundled with this package in the file LICENSE, and is | // | available at through the world-wide-web at | // | http://www.php.net/license/2_02.txt. | // | If you did not receive a copy of the PHP license and are unable to | // | obtain it through the world-wide-web, please send a note to | // | license@php.net so we can mail you a copy immediately. | // +----------------------------------------------------------------------+ // | Author: Brent Cook <busterb@mail.utexas.edu> | // +----------------------------------------------------------------------+ // // $Id: DBA_Simple.php,v 1.1 2002/06/20 18:26:20 busterb Exp $ /** * Location in the index file for a block location * @const DBA_LOC */ define('DBA_LOC',0); /** * Location in the index file for a block size * @const DBA_SIZE */ define('DBA_SIZE',1); /** * Location in the index file for a block value size * @const DBA_VSIZE */ define('DBA_VSIZE',2); /** * Location in the index file for a block key * @const DBA_KEY */ define('DBA_KEY',3); /** * DBA_Simple provides an all-PHP implementation of a DBM-style database. * It uses two files, and index and a data file to manage key/value pairs. * These two files use the suffixes '.dat' and '.idx'. When a database is * opened, only the index file is read. The index file contains pointers * to locations within the data file, which are used to retreive values. * * The class uses a concept of blocks for data storage. When the first value * is inserted, a new block is created by appending to the data file. If that * value is deleted, it remains in the data file, but is marked as empty in * the index file. A list of available blocks is kept, so when a new value * is inserted, its size is compared to the list of available blocks. If one * is of sufficient size, it is reused and marked as used in the index file. * Blocks can be of any length. * * In updating the index, lines are simply appended to the file after each * operation. So, the index file might have the same block listed multiple time * , just in different states. When the database is closed, it rewrites the * index file, removing and duplicate entries for a single block. The index * reader only uses the last entry for a block from the index file, so if close * is not called for some reason, the index file is still in a valid state. * * The optimize function merely removes duplicated index entries by rewriting * the file, the same as close. * The sync function calls fflush on the data and index files. * * @author Brent Cook * @version 0.0.9 * @access public * @package DBA */ class DBA_Simple { /** * Name of the database * @access private */ var $_dbName; /** * Handle to data file * @access private */ var $_datFP; /** * Handle to index file * @access private */ var $_idxFP; /** * Indicates the current ability for read/write operations * @access private */ var $_writable; /** * Indicates the current ability for read operations * @access private */ var $_readable; /** * Opens a database. * * @param string $dbName The name of a database * @param string $mode The mode in which to open a database. * 'r' opens read-only. * 'w' opens read-write. * 'n' creates a new database and opens read-write. * 'c' creates a new database if the database does not * exist and opens read-write. * @returns boolean true on success, false on failure */ function open($dbName='', $mode='r') { if ($dbName == '') { trigger_error('DBA: No database name specified', E_USER_WARNING); return false; } else { $this->_dbName = $dbName; $dat_name = $dbName.'.dat'; $idx_name = $dbName.'.idx'; } switch ($mode) { case 'r': // open for reading $file_mode = 'rb'; $this->_writable = false; $this->_readable = true; break; case 'n': // create a new database $file_mode = 'w+b'; $this->_writable = true; $this->_readable = true; break; case 'c': // should we create a new database? if (!(file_exists($idx_name) || file_exists($dat_name))) { $file_mode = 'w+b'; $this->_writable = true; $this->_readable = true; break; } // otherwise, we just open for writing case 'w': $file_mode = 'r+b'; $this->_writable = true; $this->_readable = true; break; default: trigger_error("DBA: Invalid file mode: $mode", E_USER_ERROR); return false; } // open the index file $this->_idxFP = @fopen($idx_name, $file_mode); if ($this->_idxFP === false) { $this->_writable = false; $this->_readable = false; trigger_error('DBA: Could not open index file: '.$idx_name. ' with mode '. $file_mode, E_USER_WARNING); return false; } // open the data file $this->_datFP = @fopen($dat_name, $file_mode); if ($this->_datFP === false) { fclose ($this->_idxFP); $this->_writable = false; $this->_readable = false; trigger_error('DBA: Could not open data file: '. $dat_name, E_USER_WARNING); return false; } // get a shared lock if read-only, otherwise get an exclusive lock if ($file_mode == 'r') { flock ($this -> _idxFP, LOCK_SH); flock ($this -> _datFP, LOCK_SH); } else { flock ($this -> _idxFP, LOCK_EX); flock ($this -> _datFP, LOCK_EX); } // we are writing to a new file, so we do not need to read anything if ($file_mode == 'w+') { return true; } // parse the index file $this->_readIdx(); return true; } /** * Closes an open database. * * @returns boolean true on success, false on failure */ function close() { if ($this->isOpen()) { if ($this->isWritable()) { $this->_writeIdx(); } $this->_readable = false; $this->_writable = false; fclose($this->_idxFP); fclose($this->_datFP); return true; } else { return trigger_error('DBA: No database was open', E_USER_WARNING); return false; } } /** * Reopens an already open database in read-only or write mode. * If the database is already in the requested mode, then this function * does nothing. * * @param string $mode 'r' for read-only, 'w' for read/write * @returns boolean true on success, false on failure */ function reopen($mode) { if ($this->isOpen()) { if (($mode == 'r') && $this->isWritable()) { // Reopening as read-only $this->close(); return $this->open($this->_dbName, 'r'); } else { if (($mode == 'w') && (!$this -> _writable)) { // Reopening as read-write $this->close(); return $this->open($this->_dbName, 'w'); } else { return true; } } } else { trigger_error('DBA: No database was open', E_USER_WARNING); return false; } } /** * Returns the current read status for the database * * @returns boolean */ function isOpen() { return ($this->_readable || $this->_writable); } /** * Returns the current read status for the database * * @returns boolean */ function isReadable() { return $this->_readable; } /** * Returns the current write status for the database * * @returns boolean */ function isWritable() { return $this->_writable; } /** * Deletes the value at location $key * * @param string $key key to delete * @returns boolean true on success, false on failure */ function delete($key) { if ($this->isWritable()) { if (isset($this->_usedBlocks[$key])) { $this->_freeUsedBlock($key); return true; } else { trigger_error('DBA: cannot delete key: '. $key. ', it does not exist', E_USER_WARNING); return false; } } else { trigger_error('DBA: cannot delete key '. $key. ', DB not writable', E_USER_WARNING); return false; } } /** * Returns the value that is stored at $key. * * @param string $key key to examine * @returns mixed the requested value on success, false on failure */ function fetch($key) { if ($this->isReadable()) { if (!isset($this->_usedBlocks[$key])) { trigger_error('DBA: cannot fetch key '.$key. ', it does not exist', E_USER_WARNING); return false; } else { fseek($this->_datFP, $this->_usedBlocks[$key][DBA_LOC]); return fread($this->_datFP, $this->_usedBlocks[$key][DBA_VSIZE]); } } else { trigger_error('DBA: cannot fetch '.$key.' on '. $this->_dbName. ', DB not readable', E_USER_WARNING); return false; } } /** * Returns the first key in the database * * @returns mixed string on success, false on failure */ function firstkey() { if ($this->isReadable() && ($this->size() > 0)) { reset($this->_usedBlocks); return key($this->_usedBlocks); } else { return false; } } /** * Returns the next key in the database, false if there is a problem * * @returns mixed string on success, false on failure */ function nextkey() { if ($this->isReadable() &&($this->size() > 0) && next($this->_usedBlocks)) { return key($this->_usedBlocks); } else { return false; } } /** * Returns ths number of keys in the database * * @returns int */ function size() { if (is_array($this->_usedBlocks)) { return sizeof($this->_usedBlocks); } else { return 0; } } /** * Inserts a new value at $key. Will not overwrite if the key/value pair * already exist * * @param string $key key to insert * @param string $value value to store * @returns boolean true on success, false on failure */ function insert($key, $value) { if ($this->exists($key)) { trigger_error('DBA: cannot insert on key: '. $key. ', it already exists', E_USER_WARNING); return false; } else { return $this->replace($key, $value); } } /** * Inserts a new value at key. If the key/value pair * already exist, overwrites the value * * @param $key string the key to insert * @param $val string the value to store * @returns boolean true on success, false on failure */ function replace($key, $value) { // is the database in a usable state? if ($this->isWritable()) { // get how much space we need $vsize = strlen($value); if (!isset($this->_usedBlocks[$key])) { // the value is new $this->_writeNewBlock($key, $value, $vsize); } else { // the value is not new $size = $this->_usedBlocks[$key][DBA_SIZE]; // is the value smaller or equal in size to its block size if ($size >= $vsize) { // move to the block's location in the data file fseek($this->_datFP, $this->_usedBlocks[$key][DBA_LOC]); // write to the data file fwrite($this->_datFP, $value, $vsize); // update internal indecies $this->_usedBlocks[$key][DBA_VSIZE] = $vsize; $this->_writeIdxEntry($loc, $size, $vsize, $key); // the value is larger than its allocated space } else { // free this value's allocated block $this->_freeUsedBlock($key); $this->_writeNewBlock($key, $value, $vsize); } } return true; } else { trigger_error('DBA: cannot replace on '. $this->_dbName. ', DB not writable', E_USER_WARNING); return false; } } /** * Allocates a new block of at least $vsize and writes $key=>$val * to the database * * param string $key * param string $value * param int $vsize * @access private */ function _writeNewBlock($key, $value, $vsize) { // is there is a sufficiently sized block free ? $loc = $this->_getFreeBlock($vsize); if ($loc !== false) { // move to the block's location in the data file fseek($this->_datFP, $loc, SEEK_SET); // write to the data file fwrite($this->_datFP, $value, $vsize); // update internal indecies $size = $this->_freeBlocks[$loc]; unset($this->_freeBlocks[$loc]); $this->_usedBlocks[$key] = array($loc, $size, $vsize); $this->_writeIdxEntry($loc, $size, $vsize, $key); // there is not a sufficiently sized block free } else { // move to the end of the data file fseek($this ->_datFP, 0, SEEK_END); $loc = ftell($this->_datFP); // write to the data file fwrite($this->_datFP, $value, $vsize); // update internal indecies $this->_usedBlocks[$key] = array($loc, $vsize, $vsize); $this->_writeIdxEntry($loc, $vsize, $vsize, $key); } } /** * Returns a block location from the free list * * @access private * @param int $reqsize Requested size * @returns mixed integer on success, false on failure */ function _getFreeBlock($reqsize) { // check if we have any blocks to choose from if (is_array($this->_freeBlocks)) { // iterate through the blocks in blockIndex to find // a free block foreach ($this->_freeBlocks as $loc=>$size) { if ($size >= $reqsize) { return $loc; } } } // no blocks available return false; } /** * Places a used block on the free list, updates indicies accordingly * * @access private * param string $key * @returns mixed */ function _freeUsedBlock($key) { $loc = $this->_usedBlocks[$key][DBA_LOC]; $size = $this->_usedBlocks[$key][DBA_SIZE]; unset($this->_usedBlocks[$key]); $this->_freeBlocks[$loc] = $size; $this->_writeIdxEntry($loc, $size); } /** * Creates a new database file if one does not exist. If it already exists, * updates the last-updated timestamp on the database * * @param string $dbName the database to create * @returns boolean true on success, false on failure */ function create($dbName) { if (@touch($dbName.'.dat') && @touch($dbName.'.idx')) { return true; } else { trigger_error('DBA: Could not create database: '.$dbName); return false; } } /** * Indicates whether a database with given name exists * * @param string $dbName the database name to check for existence * @returns boolean */ function db_exists($dbName) { return (file_exists($dbName.'.dat') && file_exists($dbName.'.idx')); } /** * Check whether key exists * * @param string $key * @returns boolean */ function exists($key) { return ($this->isOpen() && isset($this->_usedBlocks[$key])); } /** * Synchronizes an open database to disk */ function sync() { if ($this->isWritable()) { fflush($this->_datFP); fflush($this->_idxFP); } } /** * Optimizes an open database */ function optimize() { if ($this->isWritable()) { $this->_writeIdx(); } } /** * Reads the entries in an index file * Assumes that $this->_idxFP is valid and readable * * @access private */ function _readIdx() { // clear out old data if a previous database was opened $this->_usedBlocks = array(); $this->_freeBlocks = array(); $usedBlocks = array(); // temporary used index $key = ''; // reset key while (fscanf($this->_idxFP, '%u|%u|%u|%s', $loc, $size, $vsize, $key)){ // is this an free block? if ($key == '') { // check if this block had been previously marked as used if (isset($usedBlocks[$loc])) { unset($this->_usedBlocks[$usedBlocks[$loc]]); unset($usedBlocks[$loc]); } $this->_freeBlocks[$loc] = $size; } else { // check if this block had been previously marked as free if (isset($this->_freeBlocks[$loc])) { unset($this->_freeBlocks[$loc]); } $this->_usedBlocks[$key] = array($loc, $size, $vsize); $usedBlocks[$loc] = $key; } $key = ''; // reset key for the next iteration } } /** * Rewrites the index file, removing free entries * Assumes that $this->_idxFP is valid and writable * * @access private */ function _writeIdx () { // clear the index ftruncate($this->_idxFP, 0); // move the file pointer to the beginning; ftruncate does not do this fseek($this->_idxFP, 0); // write the free blocks if (isset($this->_freeBlocks)) { foreach ($this->_freeBlocks as $loc=>$size) { $this->_writeIdxEntry($loc,$size); } } // write the used blocks if (isset($this->_usedBlocks)) { foreach ($this->_usedBlocks as $key=>$block) { $this->_writeIdxEntry($block[DBA_LOC], $block[DBA_SIZE], $block[DBA_VSIZE], $key); } } fflush($this->_idxFP); } /** * Writes a used block entry to an index file * @access private * @returns boolean */ function _writeIdxEntry($loc, $size, $vsize=NULL, $key=NULL) { if (is_null($vsize)) { // write a free block entry fputs($this->_idxFP, "$loc|$size\n"); } else { // write a used block entry fputs($this->_idxFP, "$loc|$size|$vsize|$key\n"); } } } ?> Index: pear/DBA/DBA_Table.php +++ pear/DBA/DBA_Table.php <?php /* vim: set expandtab tabstop=4 shiftwidth=4: */ // +----------------------------------------------------------------------+ // | PHP Version 4 | // +----------------------------------------------------------------------+ // | Copyright (c) 1997-2002 The PHP Group | // +----------------------------------------------------------------------+ // | This source file is subject to version 2.02 of the PHP license, | // | that is bundled with this package in the file LICENSE, and is | // | available at through the world-wide-web at | // | http://www.php.net/license/2_02.txt. | // | If you did not receive a copy of the PHP license and are unable to | // | obtain it through the world-wide-web, please send a note to | // | license@php.net so we can mail you a copy immediately. | // +----------------------------------------------------------------------+ // | Authors: Brent Cook <busterb@mail.utexas.edu> | // +----------------------------------------------------------------------+ // // $Id: DBA_Table.php,v 1.1 2002/06/20 18:26:20 busterb Exp $ // define ('DBA_TABLE_META', '__meta__'); /** * DBA Table * This class provides a simple, single-table database system. * It uses a DBA class as the storage driver. * * @author Brent Cook <busterb@mail.utexas.edu> * @version 0.0.2 */ class DBA_Table { /** * DBA object handle * @access private */ var $_dba; /** * Describes the types of fields in a table * @access private */ var $_fieldSchema; /** * @access private */ var $_dateFormat = 'D M j G:i:s T Y'; /** * Constructor * @param object $dba dba object to use for storage, you need this sometime */ function DBATable ($dba = NULL) { // set the internal dba object if (!is_null($dba)) { $this->_dba &= $dba; } } /** * Opens a table * * @param string $tableName name of the table to open * @param char $mode mode to open the table; one of r,w,c,n * @param object $dba dba object to use for storage, you need this * @returns boolean FALSE on error, TRUE on success */ function open ($tableName, $mode = 'r', $dba = NULL) { // set the internal dba object if (!is_null($dba)) { $this->_dba &= $dba; } if (!($this->_dba->open($tableName, $mode))) { return FALSE; } // fetch the field descriptor at the key, DBA_TABLE_META if ($fieldString = $this->_dba->fetch(DBA_TABLE_META)) { // unpack the field descriptor into a PHP structure $this->_fieldSchema = $this->_unpackFieldSchema($fieldString); return TRUE; } else { trigger_error('DBA: Table is missing field descriptor at key, '. DBA_TABLE_META, E_USER_WARNING); return FALSE; } } /** * Closes a table * @returns boolean FALSE on error, TRUE on success */ function close () { if ($this->_dba->isWritable()) { // pack up the field structure and store it back in the table $fieldString = $this->_packFieldSchema($this->_fieldSchema); $this->_dba->replace(DBA_TABLE_META, $fieldString); return ($this->_dba->close()); } return TRUE; } /** * Creates a new table. Note, this closes any open table if $dba is not * specified * * @param string $tableName name of the table to create * @param array $fieldSchema field schema for the table * @param object $dba dba object to use */ function create ($tableName, $fieldSchema, $dba=NULL) { // pack the fieldSchema $fieldString = $this->_packFieldSchema($fieldSchema); if (is_null($dba)) { if (is_object($this->_dba)) { // close any open table, since this opens a new database with // the same internal dba object $r = $this->close(); $r = $r && $this->_dba->open($tableName, 'n'); $r = $r && $this->_dba->insert(DBA_TABLE_META, $fieldString); $r = $r && $this->_dba->close(); } else { trigger_error("DBA: Could not create $tableName, no dba object". "specified", E_USER_WARNING); return FALSE; } else { $r = $dba->open($tableName, 'n'); $r = $r && $dba->insert(DBA_TABLE_META, $fieldString); $r = $r && $dba->close(); } // return the result of the creation operations return $r; } /** * Check whether key exists * * @param $key string * @returns boolean */ function exists ($tableName) { return $this->_dba->db_exists($tableName); } /** * Returns the current read status for the database * @returns boolean */ function isOpen () { return $this->_dba->isOpen(); } /** * Returns the current read status for the database * @returns boolean */ function isReadable () { return $this->_dba->isReadable(); } /** * Returns the current write status for the database * @returns boolean */ function isWritable () { return $this->_dba->isWritable(); } /** * Returns whether a field exists in the current table's schema * @returns boolean */ function fieldExists($fieldName) { return ($this->isOpen() && isset($this->_fieldSchema[$fieldName])); } /** * Aquire an exclusive lock on the table * returns @boolean */ function lockTableEx () { return ($this->_dba->reopen('w')); } /** * Aquire a shared lock on the table * returns @boolean */ function lockTableSh ($table_name) { return ($this->_dba->reopen('r')); } /** * DBA_Table keeps an internal row index (key) * This function returns the highest row index * * @access private * @returns mixed a number or FALSE if there are no keys */ function _findMaxKey() { $maxKey = 0; $key = $this->_dba->firstkey(); while ($key) { $key = $this->_dba->nextkey($key); if (is_numeric ($key) && ($key > $maxKey)) { $maxKey = $key; } } return $maxKey; } /** * Returns a unique key to be used as a row index * * @access private * @returns integer a new key function _getUniqueKey() { // find the maxKey if necessary if (!isset ($this->_maxKey)) { $this->_maxKey = $this->_findMaxKey(); } // check if this is the first key if ($this->_maxKey === FALSE) { $this->_maxKey = 0; } else { $this->_maxKey++; } return $this->_maxKey; } /** * Returns a string for a raw field */ function _packField($field, $value) { switch ($this->_fieldSchema[$field]['type']) { case 'set': if (is_string ($value)) { $value = explode (',',$value); } if (is_array($value)) { $c_value = array(); foreach ($value as $element) { if (is_string ($element)) { $c_element = array_search ($element, $this->_fieldSchema[$field]['domain']); if (is_null ($c_element)) { } else { $c_value[] = $c_element; } } } $c_value = implode (',',$c_value); } break; case 'enum': if (is_string ($value)) { $c_value = array_search ($value, $this->_fieldSchema[$field]['domain']); if (!is_null ($c_value)) { $c_value = strval ($c_value); } } break; case 'timestamp': if (is_numeric ($value)) { $c_value = strval ($value); } else { if (is_string ($value)) { $c_value = strtotime ($value); if ($c_value != -1) { $c_value = strval ($c_value); } else { $c_value = 0; } } } break; case 'boolean': case 'bool': if (is_bool ($value)) { $c_value = strval ($value); } else { if (is_string ($value)) { // convert a 'boolean' string into a string boolean $c_value = strval(in_array(strtolower($str) ,array('t','true','y','yes','1')); } } break; case 'text': if (is_string ($value)) { $c_value = strval ($value); } break; case 'varchar': if (is_string ($value)) { if ($this->_fieldSchema[$field]['size']) { $c_value = rtrim (substr ($value, 0, $this->_fieldSchema[$field]['size'])); } else { $c_value = rtrim ($value); } } break; case 'integer': case 'int': case 'float': case 'numeric': if (is_numeric ($value)) { $c_value = strval ($value); } break; } return $c_value; } /** * */ function _unpackField($field, $value) { switch ($this->_fieldSchema[$field]['type']) { case 'set': $c_value = array(); $value = explode (',',$value); if (is_array($value)) { foreach ($value as $element) { $c_value[] = $this->_fieldSchema[$field]['domain'][$element]; } } return $c_value; case 'enum': return $this->_fieldSchema[$field]['domain'][$value]; case 'bool': if ($value == '1') return TRUE; else return FALSE; case 'timestamp': case 'integer': case 'float': case 'numeric': case 'varchar': return $value; } } function _finalizeField($field, $value) { switch ($this->_fieldSchema[$field]['type']) { case 'set': $buffer = ''; foreach ($value as $element) { $buffer .= "$element, "; } return substr($buffer,0 ,-2); case 'bool': if ($value) return "TRUE"; return "FALSE"; case 'timestamp': if ($format = $this->_fieldSchema[$field]['format']) { return date($format, $value); } else { return date($this->_dateFormat, $value); } default: return $value; } } /** * Returns a string for a field structure * The following is the grammar for each element * ENUM => name;type=enum;domain=[element1,...] * SET => name;type=set;domain=[element1,...] * TIMESTAMP => name;type=timestamp;format=<string>;init=<num> * BOOL => name;type=bool;init=[TRUE, FALSE] * TEXT => name;type=text;init=<string> * VARCHAR => name;varchar;size=<num>;init=<string> * NUMERIC => name;int;size=<num>;init=<string> */ function _packFieldSchema ($fieldSchema) { foreach ($fieldSchema as $fieldName => $fieldMeta) { $buffer = $fieldName; foreach ($fieldMeta as $attribute => $value) { $attribute = strtolower($attribute); $buffer .= ';'.$attribute.'='; switch ($attribute) { case 'domain': $buffer .= implode(',',$value); break; case 'type': $buffer .= strtolower($value); break; case 'autoincrement': if (isset($fieldMeta['floor'])) { $buffer .= $value.';floor=0'; break; } case 'autodecrement': if (!isset($fieldMeta['ceiling'])) { $buffer .= $value.';ceiling=0'; break; } default: $buffer .= $value; } } $fields[] = $buffer; } return $this->_packRawRow($fields); } /** * Unpacks a raw string into the table's field structure, * as created by _packFieldSchema */ function _unpackFieldSchema ($rawFieldString) { $rawFields = $this->_unpackRawRow($rawFieldString); foreach ($rawFields as $rawField) { $rawMeta = explode(';',$rawField); $name = array_shift($rawMeta); foreach ($rawMeta as $rawAttribute) { list($attribute,$rawValue) = explode('=',$rawAttribute); if ($attribute == 'domain') { $value = explode(',',$rawValue); } else { $value = $rawValue; } $fields[$name][$attribute] = $value; } } return $fields; } function _packRow ($data) { $buffer = array(); $i = 0; foreach ($this->_fieldSchema as $fieldName => $fieldMeta) { if (isset ($data[$fieldName])) { $c_value = $this -> _packField($fieldName, $data[$fieldName]); } elseif (isset ($data[$i])) { $c_value = $this -> _packField($fieldName, $data[$i]); } else { if ($fieldMeta['autoincrement']) { $c_value = $this->_fieldSchema[$fieldName]['ceiling']++; } else { if ($fieldMeta['autodecrement']) { $c_value = $this->_fieldSchema[$fieldName]['floor']--; } else { $c_value = $this->_packField($fieldName, $fieldMeta['default']); } } } $buffer[] = $c_value; ++$i; } return $this->_packRawRow($buffer); } function _unpackRow ($packedData) { $data = $this->_unpackRawRow($packedData); $i = 0; foreach ($this->_fieldSchema as $fieldName => $fieldMeta) { $buffer[$fieldName] = $this->_unpackField($fieldName, $data[$i]); $i++; } return $buffer; } function _packRawRow ($unpackedData) { return implode('|', $unpackedData); } function _unpackRawRow ($packedData) { return explode('|', $packedData); } function insertRow ($data) { if ($this->isOpen()) { $key = $this->_getUniqueKey(); if ($this->_dba->insert($key, $this->_packRow($data))) return $key; else return FALSE; } } function replaceRow ($key, $data) { if ($this->isOpen()) { return $this->_dba->replace($key, $this->_packRow($data)); } } function deleteRow ($key) { return $this->_dba->delete($key); } function getRow ($key) { return $this->_unpackRow($this->_dba->fetch($key)); } function finalizeRows ($rows=null) { if ($this->_dba->isOpen()) { if (is_null($rows)) { $rows = $this->getRows(); } foreach ($rows as $key=>$row) { foreach ($row as $field=>$data) { $row[$field] = $this->_finalizeField($field, $row[$field]); } $rows[$key] = $row; } return $rows; } } function getRows ($rowKeys=null) { $rows = array(); if ($this->_dba->isOpen()) { $key = $this->_dba->firstkey(); while ($key) { if ($key != DBA_TABLE_META) { if (is_null($rowIDs)) { $rows[$key] = $this->_unpackRow($this->_dba->fetch($key)); } else { if (in_array($key, $rowKeys)) { $rows[$key] = $this->_unpackRow($this->_dba->fetch($key)); } } } $key = $this->_dba->nextkey($key); } } return $rows; } function _addSpaces ($string) { foreach (array('(',')','==','!=','>','<','<=','>=') as $symbol) { $string = str_replace($symbol, " $symbol ", $string); } return $string; } function _parsePHPQuery ($rawQuery, $fieldTokens) { // add spaces around symbols for strtok to work properly $rawQuery = $this->_addSpaces($rawQuery); // begin building the php query for a row $phpQuery = ''; // scan the tokens in the raw query to build a new query // if the token is a field name, use it as a key in $row[] $token = strtok($rawQuery, ' '); while ($token) { // is this token a field name? if (in_array($token, $fieldTokens)) { $phpQuery .= "\$row['$token']"; } else { $phpQuery .= $token; } $token = strtok(' '); } return $phpQuery; } function select ($rawQuery, $rows=null) { if ($this->_dba->isOpen()) { // get a list of valid field names $fieldTokens = array_keys($this->_fieldSchema); // if we haven't passed any rows to select from, use the whole table if ($rows==null) $rows = $this->getRows(); // handle the special case of requesting all rows if ($rawQuery == '*') return $rows; // convert the query into a php statement $PHPSelect = 'foreach ($rows as $key=>$row) if ('. $this->_parsePHPQuery($rawQuery, $fieldTokens). ') $results[$key] = $row;'; // perform the select $results = array(); eval ($PHPSelect); return $results; } } function _sortCmpA ($a, $b) { foreach ($this->_sortFields as $field) { if ($a[$field] < $b[$field]) return -1; if ($a[$field] > $b[$field]) return 1; } return 0; } function _sortCmpD ($a, $b) { foreach ($this->_sortFields as $field) { if ($a[$field] < $b[$field]) return 1; if ($a[$field] > $b[$field]) return -1; } return 0; } function _parseFieldString ($fieldString) { $fields = array(); $token = strtok($fieldString, ' ,'); while ($token) { $fields[] = $token; $token = strtok(' ,'); } return $fields; } function sort ($fields, $order='a', $rows=null) { if ($this->_dba->isOpen()) { if (is_string($fields)) { // parse the sort string to produce an array of sort fields $this->_sortFields = $this->_parseFieldString($fields); } else { if (is_array($fields)) { // we already have an array of sort fields $this->_sortFields = $fields; } } // if we haven't passed any rows to select from, use the whole table if (is_null($rows)) $rows = $this->getRows(); if ($order=='a') uasort($rows, array($this, '_sortCmpA')); else uasort($rows, array($this, '_sortCmpD')); return $rows; } } function project ($fields, $rows=null) { if ($this->_dba->isOpen()) { $projectFields = array(); if (is_string($fields)) { $projectFields = $this->_parseFieldString($fields); } else { if (is_array($fields)) { // we already have an array of fields $projectFields = $fields; } } if (is_null($rows)) $rows = $this->getRows(); foreach ($rows as $key=>$row) { foreach ($projectFields as $field) { $projectedRows[$key][$field] = $row[$field]; } } return $projectedRows; } } function cmpRows ($a, $b) { $equal = TRUE; foreach ($a as $field=>$value) if ($value != $b[$field]) $equal = FALSE; return $equal; } function unique ($rows=null) { if ($this->_dba->isOpen()) { if (is_null($rows)) $rows = $this->getRows(); $results = array(); foreach ($rows as $key=>$row) { if (!isset($current) || ($current != $row)) { $results[$key] = $row; $current=$row; } } return $results; } } } Index: pear/DBA/package.xml +++ pear/DBA/package.xml <?xml version="1.0" encoding="ISO-8859-1"?> <!DOCTYPE package SYSTEM "../package.dtd"> <!-- do not use the "Type" attribute here, that one is only for generated package.xml files --> <package> <name>DBA</name> <summary>Berkely-style Database Class</summary> <description> Implements a DBM-style database using either PHP's DBA functions or a simple DBM class written in PHP itself. </description> <maintainers> <maintainer> <user>busterb</user> <name>Brent Cook</name> <email>busterb@mail.utexas.edu</email> <role>maintainer</role> </maintainer> </maintainers> <release> <license>PHP</license> <version>0.0.9</version> <date>2002-06-20</date> <state>alpha</state> <notes> Pre-initial release; this stuff is still in the works. Made lots of headway documenting and cleaning code in the driver classes. Now, they are fully PEAR compliant except for error handling, which will remain very simple for performance reasons (these are drivers, after all ;) </notes> <filelist> <dir name="/DBA" base="/DBA"> <file role="php">DBM.php</file> <file role="php">DBM_Simple.php</file> <file role="php">DBM_Compat.php</file> <file role="php">DBM_Builtin.php</file> <dir name="tests" role="test"> <file>benchmark.php</file> <file>simple.php</file> <file>graph_template</file> </dir> </dir> </filelist> </release> </package> Index: pear/DBA/tests/benchmark.php +++ pear/DBA/tests/benchmark.php <?php include 'PEAR.php'; include '../DBA.php'; $testDataArray = array ('1111', '22222222', '333333333333', '4444444444444444', '55555555555555555555', '666666666666666666666666', '7777777777777777777777777777', '88888888888888888888888888888888', '999999999999999999999999999999999999'); $maxDataIndex = sizeof ($testDataArray)-1; $maxTestKeys = array(1600, 3200, 6400, 12800, 25600); $maxTestKeys = array(1600, 3200, 6400, 12800, 25600); $transactionsInterval = 2000; $maxTransactions = $transactionsInterval * 8; $driver = 'db3'; $testDB = DBA::create($driver); function getmicrotime(){ list($usec, $sec) = explode(" ",microtime()); return ((float)$usec + (float)$sec); } if (!copy('graph_template', "./data/{$driver}_graph")) { print ("failed to copy the graph template file\n"); } $graph_fp = fopen("./data/{$driver}_graph", 'a+'); fwrite($graph_fp, "set out \"$driver.ps\"\n plot \\\n"); fflush($graph_fp); foreach ($maxTestKeys as $maxTestKey) { $dat_fp = fopen("./data/{$driver}_{$maxTestKey}.dat", 'w'); for ($transactions=$transactionsInterval; $transactions <= $maxTransactions; $transactions+=$transactionsInterval) { $result = $testDB->open('benchmark_db', 'n'); if (PEAR::isError($result)) { echo $result->getMessage()."\n"; } else { $actualTransactions = 0; // begin stopwatch $start = getmicrotime(); for ($i=0; $i<$transactions; ++$i) { $testKey = rand (0, $maxTestKey); $testData = $testDataArray[rand(0, $maxDataIndex)]; // switch (2) { switch (rand(0, 3)) { case 0: $result = @$testDB->insert($testKey, $testData); break; case 1: $result = @$testDB->delete($testKey); break; case 2: $result = @$testDB->replace($testKey, $testData); break; case 3: $result = @$testDB->fetch($testKey); } if ($result) { ++$actualTransactions; } } $testDB->close(); } // end stopwatch $stop = getmicrotime(); $line = $actualTransactions.' '.($stop - $start)."\n"; echo "Keys: $maxTestKey Transactions: $line"; fwrite($dat_fp, $line); } fclose($dat_fp); fwrite($graph_fp, "\"{$driver}_{$maxTestKey}.dat\" using 1:2 title '$driver, $maxTestKey keys' with lines,\\\n"); fflush($graph_fp); } fseek($graph_fp, -3, SEEK_END); fwrite($graph_fp, " \n"); fclose($graph_fp); ?> Index: pear/DBA/tests/simple.php +++ pear/DBA/tests/simple.php <?php // test functionality of the simple dba layer include 'PEAR.php'; include '../DBA_Simple.php'; $testDataArray = array ('1', '22', '333', '4444', '55555', '6666666', '7777777', '88888888', '999999999'); $maxDataIndex = sizeof ($testDataArray)-1; $testDB = new DBA_Simple(); if (PEAR::isError($error=$testDB->open('test', 'c'))) { echo $error->getMessage()."\n"; } else { for ($i=0; $i<1000; ++$i) { $testKey = rand (0, 99); $testData = $testDataArray[rand(0, 3)]; switch (rand(0, 3)) { case 0: if (!$testDB->exists($testKey)) { $result = $testDB->insert($testKey, $testData); } break; case 1: if ($testDB->exists($testKey)) { $result = $testDB->delete($testKey); } break; case 2: $result = $testDB->replace($testKey, $testData); break; case 3: if ($testDB->exists($testKey)) { $result = $testDB->fetch($testKey); } } if (PEAR::isError($result)) { echo $result->getMessage()."\n"; } } $testDB->close(); } $testDB->open('test', 'r'); $key = $testDB->firstkey(); while ($key !== FALSE) { echo "$key = ".$testDB->fetch($key)."\n"; $key = $testDB->nextkey($key); } $testDB->close(); ?>
Index: pear/DBA/DBA.php +++ pear/DBA/DBA.php <?php /* vim: set expandtab tabstop=4 shiftwidth=4: */ // +----------------------------------------------------------------------+ // | PHP Version 4 | // +----------------------------------------------------------------------+ // | Copyright (c) 1997-2002 The PHP Group | // +----------------------------------------------------------------------+ // | This source file is subject to version 2.02 of the PHP license, | // | that is bundled with this package in the file LICENSE, and is | // | available at through the world-wide-web at | // | http://www.php.net/license/2_02.txt. | // | If you did not receive a copy of the PHP license and are unable to | // | obtain it through the world-wide-web, please send a note to | // | license@php.net so we can mail you a copy immediately. | // +----------------------------------------------------------------------+ // | Authors: Brent Cook <busterb@mail.utexas.edu> | // +----------------------------------------------------------------------+ // // $Id: DBA.php,v 1.1 2002/06/20 18:26:19 busterb Exp $ // class DBA { /** * Return a DBA object * * @static * @param string $driver Type of storage object to return * @return object Object DBA storage object */ function create($driver = 'simple') { if (!function_exists('dba_open') || ($driver=='simple')) { require_once '../DBA_Simple.php'; return new DBA_Simple(); } else { require_once 'DBA_Builtin.php'; return new DBA_Builtin($driver); } } } ?> Index: pear/DBA/DBA_Builtin.php +++ pear/DBA/DBA_Builtin.php <?php /* vim: set expandtab tabstop=4 shiftwidth=4: */ // +----------------------------------------------------------------------+ // | PHP Version 4 | // +----------------------------------------------------------------------+ // | Copyright (c) 1997-2002 The PHP Group | // +----------------------------------------------------------------------+ // | This source file is subject to version 2.02 of the PHP license, | // | that is bundled with this package in the file LICENSE, and is | // | available at through the world-wide-web at | // | http://www.php.net/license/2_02.txt. | // | If you did not receive a copy of the PHP license and are unable to | // | obtain it through the world-wide-web, please send a note to | // | license@php.net so we can mail you a copy immediately. | // +----------------------------------------------------------------------+ // | Author: Brent Cook <busterb@mail.utexas.edu> | // +----------------------------------------------------------------------+ // // $Id: DBA_Builtin.php,v 1.1 2002/06/20 18:26:20 busterb Exp $ /** * DBA_Builtin uses the builtin dba functions of PHP as the underlying driver * for a DBA class. Depending on the driver, this can be faster or slower than * the DBA_Simple class. * * @author Brent Cook * @version 0.0.9 * @access public * @package DBA */ class DBA_Builtin { /** * Name of the database * @access private */ var $_dbName; /** * Indicates the current ability for read/write operations * @access private */ var $_writable; /** * Indicates the current ability for read operations * @access private */ var $_readable; /** * Name of the builtin dba driver to use * @access private */ var $_driver = NULL; /** * Indicates the ability of the dba driver to replace values * @access private */ var $_hasReplace; /* Constructor * * @param string $driver dba driver to use */ function DBA_Builtin ($driver = 'gdbm') { $this->_driver = $driver; } /** * Opens a database. * * @param string $dbName The name of a database * @param string $mode The mode in which to open a database. * 'r' opens read-only. * 'w' opens read-write. * 'n' creates a new database and opens read-write. * 'c' creates a new database if the database does not * exist and opens read-write. * @param string $driver dba driver to use * @returns boolean true on success, false on failure */ function open ($dbName='', $mode='r', $driver=NULL) { if (!is_null($driver)) { $this->_driver = $driver; } if (is_null($this->_driver)) { trigger_error('DBA: No dba driver specified'); return false; } if ($this->_driver == 'gdbm') { $this->_hasReplace = false; } else { $this->_hasReplace = true; } if ($dbName == '') { trigger_error('DBA: No database name specified', E_USER_WARNING); return false; } else { $this->_dbName = $dbName; } switch ($mode) { case 'r': // open for reading $this->_writable = false; $this->_readable = true; break; case 'n': case 'c': case 'w': $this->_writable = true; $this->_readable = true; break; default: trigger_error("DBA: Invalid file mode: $mode", E_USER_ERROR); return false; } // open the index file $this->_dba = dba_open($dbName, $mode, $this->_driver); if ($this->_dba === false) { $this->_writable = false; $this->_readable = false; trigger_error("DBA: Could not open database: $dbName" ." with mode $mode"); return false; } return true; // everything worked out } /** * Closes an open database. * * @returns boolean true on success, false on failure */ function close () { if ($this->isOpen()) { $this->_readable = false; $this->_writable = false; dba_close($this->_dba); return true; } else { return trigger_error('DBA: No database was open', E_USER_WARNING); return false; } } /** * Reopens an already open database in read-only or write mode. * If the database is already in the requested mode, then this function * does nothing. * * @param string $mode 'r' for read-only, 'w' for read/write * @returns boolean true on success, false on failure */ function reopen ($mode) { if ($this->isOpen()) { if (($mode == 'r') && $this->isWritable()) { // Reopening as read-only $this->close(); return $this->open($this->_dbName, 'r'); } elseif (($mode == 'w') && (!$this->isWritable)) { // Reopening as read-write $this->close(); return $this->open($this->_dbName, 'w'); } else { return true; } } else { trigger_error('DBA: No database was open', E_USER_WARNING); return false; } } /** * Returns the current read status for the database * * @returns boolean */ function isOpen() { return ($this->_readable || $this->_writable); } /** * Returns the current read status for the database * * @returns boolean */ function isReadable() { return $this->_readable; } /** * Returns the current write status for the database * * @returns boolean */ function isWritable() { return $this->_writable; } /** * Deletes the value at location $key * * @param string $key key to delete * @returns boolean true on success, false on failure */ function delete($key) { if ($this->isWritable()) { if (!dba_delete($key, $this->_dba)) { trigger_error('DBA: cannot delete key: '. $key. ', it does not exist', E_USER_WARNING); return false; } } else { trigger_error('DBA: cannot delete key '. $key. ', DB not writable', E_USER_WARNING); return false; } } /** * Returns the value that is stored at $key. * * @param string $key key to examine * @returns mixed the requested value on success, false on failure */ function fetch($key) { if ($this->isReadable()) { if (dba_exists($key)) { return dba_fetch($key, $this->_dba); } else { trigger_error('DBA: cannot fetch key '.$key. ', it does not exist', E_USER_WARNING); return false; } } else { trigger_error('DBA: cannot fetch '.$key.' on '. $this->_dbName. ', DB not readable', E_USER_WARNING); return false; } } /** * Returns the first key in the database * * @returns mixed string on success, false on failure */ function firstkey() { if ($this->isReadable() && ($this->size() > 0)) { return dba_firstkey($this->_dba); } else { return false; } } /** * Returns the next key in the database, false if there is a problem * * @returns mixed string on success, false on failure */ function nextkey() { if ($this->isReadable()) { return dba_nextkey($this->_dba); } else { return false; } } /** * Inserts a new value at $key. Will not overwrite if the key/value pair * already exist * * @param string $key key to insert * @param string $value value to store * @returns boolean true on success, false on failure */ function insert($key, $value) { if ($this->isWritable()) { if ((!$this->_hasReplace && dba_exists($key, $this->_dba)) || (!dba_insert($key, $value, $this->_dba))) { trigger_error('DBA: cannot insert on key: '. $key. ', it already exists', E_USER_WARNING); return false; } else { return true; } } else { trigger_error('DBA: cannot replace on '. $this->_dbName. ', DB not writable', E_USER_WARNING); return false; } } /** * Inserts a new value at key. If the key/value pair * already exist, overwrites the value * * @param $key string the key to insert * @param $val string the value to store * @returns boolean true on success, false on failure */ function replace($key, $value) { if ($this->isWritable()) { if ($this->_hasReplace) { return dba_replace($key, $value, $this->_dba); } else { $r = true; if (dba_exists($key, $this->_dba)) { $r = dba_delete($key, $this->_dba); } return $r && dba_insert($key, $value, $this->_dba); } } else { trigger_error('DBA: cannot replace on '. $this->_dbName. ', DB not writable', E_USER_WARNING); return false; } } /** * Creates a new database file if one does not exist. If it already exists, * updates the last-updated timestamp on the database * * @param string $dbName the database to create * @param string $driver the dba driver to use * @returns boolean true on success, false on failure */ function create($dbName, $driver='gdbm') { $db = dba_open($dbName, 'n', $driver); if (($db !== false) && dba_close($db)) { return true; } else { trigger_error('DBA: Could not create database: '.$dbName); return false; } } /** * Indicates whether a database with given name exists * * @param string $dbName the database name to check for existence * @returns boolean */ function db_exists($dbName) { return file_exists($dbName); } /** * Check whether key exists * * @param string $key * @returns boolean */ function exists($key) { return ($this->isOpen() && dba_exists($key, $this->_dba)); } /** * Synchronizes an open database to disk */ function sync() { return dba_sync($this->_dba); } /** * Optimizes an open database */ function optimize() { return dba_optimize($this->_dba); } } ?> Index: pear/DBA/DBA_Compat.php +++ pear/DBA/DBA_Compat.php <?php /* vim: set expandtab tabstop=4 shiftwidth=4: */ // +----------------------------------------------------------------------+ // | PHP Version 4 | // +----------------------------------------------------------------------+ // | Copyright (c) 1997-2002 The PHP Group | // +----------------------------------------------------------------------+ // | This source file is subject to version 2.02 of the PHP license, | // | that is bundled with this package in the file LICENSE, and is | // | available at through the world-wide-web at | // | http://www.php.net/license/2_02.txt. | // | If you did not receive a copy of the PHP license and are unable to | // | obtain it through the world-wide-web, please send a note to | // | license@php.net so we can mail you a copy immediately. | // +----------------------------------------------------------------------+ // | Author: Brent Cook <busterb@mail.utexas.edu> | // +----------------------------------------------------------------------+ // // $Id: DBA_Compat.php,v 1.1 2002/06/20 18:26:20 busterb Exp $ /** * dba compatibility layer * @author Brent Cook <busterb@mail.utexas.edu> * @version 0.1 * @see PHP dba Documentation */ require_once ('DBA_simple.php'); if (!function_exists('dba_open')) { function dba_close(&$dba) { $result = $dba->close(); unset($dba); return $result; } function dba_delete($key, &$dba) { return $dba->delete($key); } function dba_exists($key, &$dba) { return $dba->exists($key); } function dba_fetch($key, &$dba) { return $dba->fetch($key); } function dba_firstkey(&$dba) { return $dba->firstkey(); } function dba_insert($key, $value, &$dba) { return $dba->insert($key, $value); } function dba_nextkey(&$dba) { return $dba->nextkey($key); } function dba_open($filename, $mode, $handler) { $dba = new DBA_Simple(); $dba->open($filename, $mode); return $dba; } function dba_optimize() { return $dba->optimize(); } function dba_popen() { return FALSE; } function dba_replace($key, $value, &$dba) { return $dba->replace($key, $value); } function dba_sync() { return $dba->sync(); } } ?> Index: pear/DBA/DBA_Relational.php +++ pear/DBA/DBA_Relational.php <?php /* vim: set expandtab tabstop=4 shiftwidth=4: */ // +----------------------------------------------------------------------+ // | PHP Version 4 | // +----------------------------------------------------------------------+ // | Copyright (c) 1997-2002 The PHP Group | // +----------------------------------------------------------------------+ // | This source file is subject to version 2.02 of the PHP license, | // | that is bundled with this package in the file LICENSE, and is | // | available at through the world-wide-web at | // | http://www.php.net/license/2_02.txt. | // | If you did not receive a copy of the PHP license and are unable to | // | obtain it through the world-wide-web, please send a note to | // | license@php.net so we can mail you a copy immediately. | // +----------------------------------------------------------------------+ // | Author: Brent Cook <busterb@mail.utexas.edu> | // +----------------------------------------------------------------------+ // // $Id: DBA_Relational.php,v 1.1 2002/06/20 18:26:20 busterb Exp $ /** * a relational database manager for DBM-style databases * * @author Brent Cook <busterb@mail.utexas.edu> * @version 0.0.1 */ require_once('DBA_Table'); class DBA_Relational { // table handles var $_tables=array(); // location of table data files var $prefix; /** * Constructor * @param $prefix */ function RoundDB ($prefix = '') { // add trailing slash if not present if (substr($prefix, -1) != '/') $this->prefix = "$prefix/"; else $this->prefix = $prefix; } function close () { foreach ($this->_tables as $table) $table->close(); } function getVersion () { return "RoundDB v0.0.1"; } /** * Opens a database. * @param $tableName * @param $mode */ function openTable ($tableName, $mode = 'r') { if (!isset($this->_tables[$tableName])) { $this->_tables[$tableName] = new DBMTable($this->prefix.$tableName, $mode); if (!$this->_tables[$tableName]->isOpen()) { echo "Table '$tableName' does not exist\n"; unset($this->_tables[$tableName]); return False; } } if ($this->_tables[$tableName]->isOpen()) { if ((($mode == 'r') && $this->_tables[$tableName]->isReadable()) || (($mode == 'w') && $this->_tables[$tableName]->isWritable())) { return True; } else { $this->_tables[$tableName]->close(); } } return $this->_tables[$tableName]->open($this->prefix.$tableName, $mode); } function formatResults($results, $fields=null) { if (is_array($results) && sizeof($results)) { if (is_null($fields)) $fields = array_keys(current($results)); // get the maximum length of each field foreach ($fields as $key=>$field) { $longest[$key] = strlen($field) + 1; foreach ($results as $result) { $resultLen = strlen($result[$field]) + 1; if ($resultLen > $longest[$key]) $longest[$key] = $resultLen; } } // generate separator line foreach ($longest as $length) $separator .= '+-'.str_repeat('-',$length); $separator .= "+\n"; $buffer = $separator; // print fields foreach ($fields as $key=>$field) $buffer .= '| '.str_pad($field, $longest[$key]); $buffer .= "|\n$separator"; // print rows foreach ($results as $result) { foreach ($fields as $key=>$field) $buffer .= '| '.str_pad($result[$field], $longest[$key]); $buffer .= "|\n$separator"; } } return $buffer; } /** * */ function closeTable ($tableName) { if (isset($this->_tables[$tableName])) return $this->_tables[$tableName]->close(); else return False; } function createTable ($tableName, $schema) { // check if this table object exists if (!isset($this->_tables[$tableName])) $this->_tables[$tableName] = new DBMTable(); else return False; // the table object exists, so the table must exist // ask if the table really exists if (!$this->_tables[$tableName]->exists($this->prefix.$tableName)) return $this->_tables[$tableName]->create($this->prefix.$tableName, $schema); else return False; } function isOpen ($tableName) { if (isset($this->_tables[$tableName])) return $this->_tables[$tableName]->isOpen(); else return False; } function dropTable ($tableName) { if ($this->openTable($tableName, 'w')) { unset($this->_tables[$tableName]); return $this->_tables[$tableName]->dropTable($tableName); } else { return False; } } function insertRow ($tableName, $data) { if ($this->openTable($tableName, 'w')) return $this->_tables[$tableName]->insertRow($data); else return False; } function replaceRow ($tableName, $key, $data) { if ($this->openTable($tableName, 'w')) return $this->_tables[$tableName]->replaceRow($key, $data); else return False; } function deleteRow ($tableName, $key) { if ($this->openTable($tableName, 'w')) return $this->_tables[$tableName]->deleteRow($key); else return False; } function fetchRow ($tableName, $key) { if ($this->openTable($tableName, 'r')) return $this->_tables[$tableName]->fetchRow($key); else return False; } function select ($tableName, $query, $rows=null) { if ($this->openTable($tableName, 'r')) { return $this->_tables[$tableName]->select($query, $rows); } else { return False; } } function sort ($tableName, $fields, $order='a', $rows=null) { if ($this->openTable($tableName, 'r')) return $this->_tables[$tableName]->sort($fields, $order, $rows); else return False; } function project ($tableName, $fields, $rows=null) { if ($this->openTable($tableName, 'r')) return $this->_tables[$tableName]->project($fields, $rows); else return False; } function unique ($tableName, $rows=null) { if ($this->openTable($tableName, 'r')) return $this->_tables[$tableName]->unique($rows); else return False; } function finalizeRows($tableName, $rows=null) { if ($this->openTable($tableName, 'r')) return $this->_tables[$tableName]->finalizeRows($rows); else return False; } function _validateTable (&$table, &$rows, &$fields, $altName) { // validate query by checking for existence of fields if (is_string($table) && ($this->openTable($table, 'r'))) { $rows = $this->_tables[$table]->getRows(); $fields = $this->_tables[$table]->getFieldNames(); return True; } elseif (is_array($table) && sizeof($table)) { reset($table); $rows = $table; $fields = array_keys(current($table)); $table = $altName; return True; } $fields = null; $rows = null; return False; } function _parsePHPQuery ($rawQuery, $fieldsA, $fieldsB, $tableA, $tableB) { // add spaces around symbols for strtok to work properly $rawQuery = DBMTable::addSpaces($rawQuery); // begin building the php query for a row $phpQuery = ''; // scan the tokens in the raw query to build a new query // if the token is a field name, use it as a key in $row[] $token = strtok($rawQuery, ' '); while ($token) { // is this token a field name? if ($i = strpos($token, '.')) { $table = substr($token, 0, $i); // trim everything after the '.' $field = substr($token, $i+1); // trim everything before the '.' if (($table == $tableA) && in_array($field, $fieldsA)) { $phpQuery .= "\$rowA['$field']"; } else if (($table == $tableB) && in_array($field, $fieldsB)) { $phpQuery .= "\$rowB['$field']"; } } else { $phpQuery .= $token; } $token = strtok(' '); } return $phpQuery; } function join ($tableA, $tableB, $rawQuery) { // validate tables if (!$this->_validateTable($tableA, $rowsA, $fieldsA, 'A')) return False; if (!$this->_validateTable($tableB, $rowsB, $fieldsB, 'B')) return False; // check for empty tables if (is_null($rowsA) && !is_null($rowsB)) return $rowsB; if (!is_null($rowsA) && is_null($rowsB)) return $rowsA; if (is_null($rowsA) && is_null($rowsB)) return array(); // build the join operation with nested loops $PHPJoin = 'foreach ($rowsA as $rowA) foreach ($rowsB as $rowB) if ('. $this->_parsePHPQuery($rawQuery, $fieldsA, $fieldsB, $tableA, $tableB) .') $results[] = array_merge($rowA, $rowB);'; // evaluate the join eval ($PHPJoin); return $results; } } Index: pear/DBA/DBA_Simple.php +++ pear/DBA/DBA_Simple.php <?php /* vim: set expandtab tabstop=4 shiftwidth=4: */ // +----------------------------------------------------------------------+ // | PHP Version 4 | // +----------------------------------------------------------------------+ // | Copyright (c) 1997-2002 The PHP Group | // +----------------------------------------------------------------------+ // | This source file is subject to version 2.02 of the PHP license, | // | that is bundled with this package in the file LICENSE, and is | // | available at through the world-wide-web at | // | http://www.php.net/license/2_02.txt. | // | If you did not receive a copy of the PHP license and are unable to | // | obtain it through the world-wide-web, please send a note to | // | license@php.net so we can mail you a copy immediately. | // +----------------------------------------------------------------------+ // | Author: Brent Cook <busterb@mail.utexas.edu> | // +----------------------------------------------------------------------+ // // $Id: DBA_Simple.php,v 1.1 2002/06/20 18:26:20 busterb Exp $ /** * Location in the index file for a block location * @const DBA_LOC */ define('DBA_LOC',0); /** * Location in the index file for a block size * @const DBA_SIZE */ define('DBA_SIZE',1); /** * Location in the index file for a block value size * @const DBA_VSIZE */ define('DBA_VSIZE',2); /** * Location in the index file for a block key * @const DBA_KEY */ define('DBA_KEY',3); /** * DBA_Simple provides an all-PHP implementation of a DBM-style database. * It uses two files, and index and a data file to manage key/value pairs. * These two files use the suffixes '.dat' and '.idx'. When a database is * opened, only the index file is read. The index file contains pointers * to locations within the data file, which are used to retreive values. * * The class uses a concept of blocks for data storage. When the first value * is inserted, a new block is created by appending to the data file. If that * value is deleted, it remains in the data file, but is marked as empty in * the index file. A list of available blocks is kept, so when a new value * is inserted, its size is compared to the list of available blocks. If one * is of sufficient size, it is reused and marked as used in the index file. * Blocks can be of any length. * * In updating the index, lines are simply appended to the file after each * operation. So, the index file might have the same block listed multiple time * , just in different states. When the database is closed, it rewrites the * index file, removing and duplicate entries for a single block. The index * reader only uses the last entry for a block from the index file, so if close * is not called for some reason, the index file is still in a valid state. * * The optimize function merely removes duplicated index entries by rewriting * the file, the same as close. * The sync function calls fflush on the data and index files. * * @author Brent Cook * @version 0.0.9 * @access public * @package DBA */ class DBA_Simple { /** * Name of the database * @access private */ var $_dbName; /** * Handle to data file * @access private */ var $_datFP; /** * Handle to index file * @access private */ var $_idxFP; /** * Indicates the current ability for read/write operations * @access private */ var $_writable; /** * Indicates the current ability for read operations * @access private */ var $_readable; /** * Opens a database. * * @param string $dbName The name of a database * @param string $mode The mode in which to open a database. * 'r' opens read-only. * 'w' opens read-write. * 'n' creates a new database and opens read-write. * 'c' creates a new database if the database does not * exist and opens read-write. * @returns boolean true on success, false on failure */ function open($dbName='', $mode='r') { if ($dbName == '') { trigger_error('DBA: No database name specified', E_USER_WARNING); return false; } else { $this->_dbName = $dbName; $dat_name = $dbName.'.dat'; $idx_name = $dbName.'.idx'; } switch ($mode) { case 'r': // open for reading $file_mode = 'rb'; $this->_writable = false; $this->_readable = true; break; case 'n': // create a new database $file_mode = 'w+b'; $this->_writable = true; $this->_readable = true; break; case 'c': // should we create a new database? if (!(file_exists($idx_name) || file_exists($dat_name))) { $file_mode = 'w+b'; $this->_writable = true; $this->_readable = true; break; } // otherwise, we just open for writing case 'w': $file_mode = 'r+b'; $this->_writable = true; $this->_readable = true; break; default: trigger_error("DBA: Invalid file mode: $mode", E_USER_ERROR); return false; } // open the index file $this->_idxFP = @fopen($idx_name, $file_mode); if ($this->_idxFP === false) { $this->_writable = false; $this->_readable = false; trigger_error('DBA: Could not open index file: '.$idx_name. ' with mode '. $file_mode, E_USER_WARNING); return false; } // open the data file $this->_datFP = @fopen($dat_name, $file_mode); if ($this->_datFP === false) { fclose ($this->_idxFP); $this->_writable = false; $this->_readable = false; trigger_error('DBA: Could not open data file: '. $dat_name, E_USER_WARNING); return false; } // get a shared lock if read-only, otherwise get an exclusive lock if ($file_mode == 'r') { flock ($this -> _idxFP, LOCK_SH); flock ($this -> _datFP, LOCK_SH); } else { flock ($this -> _idxFP, LOCK_EX); flock ($this -> _datFP, LOCK_EX); } // we are writing to a new file, so we do not need to read anything if ($file_mode == 'w+') { return true; } // parse the index file $this->_readIdx(); return true; } /** * Closes an open database. * * @returns boolean true on success, false on failure */ function close() { if ($this->isOpen()) { if ($this->isWritable()) { $this->_writeIdx(); } $this->_readable = false; $this->_writable = false; fclose($this->_idxFP); fclose($this->_datFP); return true; } else { return trigger_error('DBA: No database was open', E_USER_WARNING); return false; } } /** * Reopens an already open database in read-only or write mode. * If the database is already in the requested mode, then this function * does nothing. * * @param string $mode 'r' for read-only, 'w' for read/write * @returns boolean true on success, false on failure */ function reopen($mode) { if ($this->isOpen()) { if (($mode == 'r') && $this->isWritable()) { // Reopening as read-only $this->close(); return $this->open($this->_dbName, 'r'); } else { if (($mode == 'w') && (!$this -> _writable)) { // Reopening as read-write $this->close(); return $this->open($this->_dbName, 'w'); } else { return true; } } } else { trigger_error('DBA: No database was open', E_USER_WARNING); return false; } } /** * Returns the current read status for the database * * @returns boolean */ function isOpen() { return ($this->_readable || $this->_writable); } /** * Returns the current read status for the database * * @returns boolean */ function isReadable() { return $this->_readable; } /** * Returns the current write status for the database * * @returns boolean */ function isWritable() { return $this->_writable; } /** * Deletes the value at location $key * * @param string $key key to delete * @returns boolean true on success, false on failure */ function delete($key) { if ($this->isWritable()) { if (isset($this->_usedBlocks[$key])) { $this->_freeUsedBlock($key); return true; } else { trigger_error('DBA: cannot delete key: '. $key. ', it does not exist', E_USER_WARNING); return false; } } else { trigger_error('DBA: cannot delete key '. $key. ', DB not writable', E_USER_WARNING); return false; } } /** * Returns the value that is stored at $key. * * @param string $key key to examine * @returns mixed the requested value on success, false on failure */ function fetch($key) { if ($this->isReadable()) { if (!isset($this->_usedBlocks[$key])) { trigger_error('DBA: cannot fetch key '.$key. ', it does not exist', E_USER_WARNING); return false; } else { fseek($this->_datFP, $this->_usedBlocks[$key][DBA_LOC]); return fread($this->_datFP, $this->_usedBlocks[$key][DBA_VSIZE]); } } else { trigger_error('DBA: cannot fetch '.$key.' on '. $this->_dbName. ', DB not readable', E_USER_WARNING); return false; } } /** * Returns the first key in the database * * @returns mixed string on success, false on failure */ function firstkey() { if ($this->isReadable() && ($this->size() > 0)) { reset($this->_usedBlocks); return key($this->_usedBlocks); } else { return false; } } /** * Returns the next key in the database, false if there is a problem * * @returns mixed string on success, false on failure */ function nextkey() { if ($this->isReadable() &&($this->size() > 0) && next($this->_usedBlocks)) { return key($this->_usedBlocks); } else { return false; } } /** * Returns ths number of keys in the database * * @returns int */ function size() { if (is_array($this->_usedBlocks)) { return sizeof($this->_usedBlocks); } else { return 0; } } /** * Inserts a new value at $key. Will not overwrite if the key/value pair * already exist * * @param string $key key to insert * @param string $value value to store * @returns boolean true on success, false on failure */ function insert($key, $value) { if ($this->exists($key)) { trigger_error('DBA: cannot insert on key: '. $key. ', it already exists', E_USER_WARNING); return false; } else { return $this->replace($key, $value); } } /** * Inserts a new value at key. If the key/value pair * already exist, overwrites the value * * @param $key string the key to insert * @param $val string the value to store * @returns boolean true on success, false on failure */ function replace($key, $value) { // is the database in a usable state? if ($this->isWritable()) { // get how much space we need $vsize = strlen($value); if (!isset($this->_usedBlocks[$key])) { // the value is new $this->_writeNewBlock($key, $value, $vsize); } else { // the value is not new $size = $this->_usedBlocks[$key][DBA_SIZE]; // is the value smaller or equal in size to its block size if ($size >= $vsize) { // move to the block's location in the data file fseek($this->_datFP, $this->_usedBlocks[$key][DBA_LOC]); // write to the data file fwrite($this->_datFP, $value, $vsize); // update internal indecies $this->_usedBlocks[$key][DBA_VSIZE] = $vsize; $this->_writeIdxEntry($loc, $size, $vsize, $key); // the value is larger than its allocated space } else { // free this value's allocated block $this->_freeUsedBlock($key); $this->_writeNewBlock($key, $value, $vsize); } } return true; } else { trigger_error('DBA: cannot replace on '. $this->_dbName. ', DB not writable', E_USER_WARNING); return false; } } /** * Allocates a new block of at least $vsize and writes $key=>$val * to the database * * param string $key * param string $value * param int $vsize * @access private */ function _writeNewBlock($key, $value, $vsize) { // is there is a sufficiently sized block free ? $loc = $this->_getFreeBlock($vsize); if ($loc !== false) { // move to the block's location in the data file fseek($this->_datFP, $loc, SEEK_SET); // write to the data file fwrite($this->_datFP, $value, $vsize); // update internal indecies $size = $this->_freeBlocks[$loc]; unset($this->_freeBlocks[$loc]); $this->_usedBlocks[$key] = array($loc, $size, $vsize); $this->_writeIdxEntry($loc, $size, $vsize, $key); // there is not a sufficiently sized block free } else { // move to the end of the data file fseek($this ->_datFP, 0, SEEK_END); $loc = ftell($this->_datFP); // write to the data file fwrite($this->_datFP, $value, $vsize); // update internal indecies $this->_usedBlocks[$key] = array($loc, $vsize, $vsize); $this->_writeIdxEntry($loc, $vsize, $vsize, $key); } } /** * Returns a block location from the free list * * @access private * @param int $reqsize Requested size * @returns mixed integer on success, false on failure */ function _getFreeBlock($reqsize) { // check if we have any blocks to choose from if (is_array($this->_freeBlocks)) { // iterate through the blocks in blockIndex to find // a free block foreach ($this->_freeBlocks as $loc=>$size) { if ($size >= $reqsize) { return $loc; } } } // no blocks available return false; } /** * Places a used block on the free list, updates indicies accordingly * * @access private * param string $key * @returns mixed */ function _freeUsedBlock($key) { $loc = $this->_usedBlocks[$key][DBA_LOC]; $size = $this->_usedBlocks[$key][DBA_SIZE]; unset($this->_usedBlocks[$key]); $this->_freeBlocks[$loc] = $size; $this->_writeIdxEntry($loc, $size); } /** * Creates a new database file if one does not exist. If it already exists, * updates the last-updated timestamp on the database * * @param string $dbName the database to create * @returns boolean true on success, false on failure */ function create($dbName) { if (@touch($dbName.'.dat') && @touch($dbName.'.idx')) { return true; } else { trigger_error('DBA: Could not create database: '.$dbName); return false; } } /** * Indicates whether a database with given name exists * * @param string $dbName the database name to check for existence * @returns boolean */ function db_exists($dbName) { return (file_exists($dbName.'.dat') && file_exists($dbName.'.idx')); } /** * Check whether key exists * * @param string $key * @returns boolean */ function exists($key) { return ($this->isOpen() && isset($this->_usedBlocks[$key])); } /** * Synchronizes an open database to disk */ function sync() { if ($this->isWritable()) { fflush($this->_datFP); fflush($this->_idxFP); } } /** * Optimizes an open database */ function optimize() { if ($this->isWritable()) { $this->_writeIdx(); } } /** * Reads the entries in an index file * Assumes that $this->_idxFP is valid and readable * * @access private */ function _readIdx() { // clear out old data if a previous database was opened $this->_usedBlocks = array(); $this->_freeBlocks = array(); $usedBlocks = array(); // temporary used index $key = ''; // reset key while (fscanf($this->_idxFP, '%u|%u|%u|%s', $loc, $size, $vsize, $key)){ // is this an free block? if ($key == '') { // check if this block had been previously marked as used if (isset($usedBlocks[$loc])) { unset($this->_usedBlocks[$usedBlocks[$loc]]); unset($usedBlocks[$loc]); } $this->_freeBlocks[$loc] = $size; } else { // check if this block had been previously marked as free if (isset($this->_freeBlocks[$loc])) { unset($this->_freeBlocks[$loc]); } $this->_usedBlocks[$key] = array($loc, $size, $vsize); $usedBlocks[$loc] = $key; } $key = ''; // reset key for the next iteration } } /** * Rewrites the index file, removing free entries * Assumes that $this->_idxFP is valid and writable * * @access private */ function _writeIdx () { // clear the index ftruncate($this->_idxFP, 0); // move the file pointer to the beginning; ftruncate does not do this fseek($this->_idxFP, 0); // write the free blocks if (isset($this->_freeBlocks)) { foreach ($this->_freeBlocks as $loc=>$size) { $this->_writeIdxEntry($loc,$size); } } // write the used blocks if (isset($this->_usedBlocks)) { foreach ($this->_usedBlocks as $key=>$block) { $this->_writeIdxEntry($block[DBA_LOC], $block[DBA_SIZE], $block[DBA_VSIZE], $key); } } fflush($this->_idxFP); } /** * Writes a used block entry to an index file * @access private * @returns boolean */ function _writeIdxEntry($loc, $size, $vsize=NULL, $key=NULL) { if (is_null($vsize)) { // write a free block entry fputs($this->_idxFP, "$loc|$size\n"); } else { // write a used block entry fputs($this->_idxFP, "$loc|$size|$vsize|$key\n"); } } } ?> Index: pear/DBA/DBA_Table.php +++ pear/DBA/DBA_Table.php <?php /* vim: set expandtab tabstop=4 shiftwidth=4: */ // +----------------------------------------------------------------------+ // | PHP Version 4 | // +----------------------------------------------------------------------+ // | Copyright (c) 1997-2002 The PHP Group | // +----------------------------------------------------------------------+ // | This source file is subject to version 2.02 of the PHP license, | // | that is bundled with this package in the file LICENSE, and is | // | available at through the world-wide-web at | // | http://www.php.net/license/2_02.txt. | // | If you did not receive a copy of the PHP license and are unable to | // | obtain it through the world-wide-web, please send a note to | // | license@php.net so we can mail you a copy immediately. | // +----------------------------------------------------------------------+ // | Authors: Brent Cook <busterb@mail.utexas.edu> | // +----------------------------------------------------------------------+ // // $Id: DBA_Table.php,v 1.1 2002/06/20 18:26:20 busterb Exp $ // define ('DBA_TABLE_META', '__meta__'); /** * DBA Table * This class provides a simple, single-table database system. * It uses a DBA class as the storage driver. * * @author Brent Cook <busterb@mail.utexas.edu> * @version 0.0.2 */ class DBA_Table { /** * DBA object handle * @access private */ var $_dba; /** * Describes the types of fields in a table * @access private */ var $_fieldSchema; /** * @access private */ var $_dateFormat = 'D M j G:i:s T Y'; /** * Constructor * @param object $dba dba object to use for storage, you need this sometime */ function DBATable ($dba = NULL) { // set the internal dba object if (!is_null($dba)) { $this->_dba &= $dba; } } /** * Opens a table * * @param string $tableName name of the table to open * @param char $mode mode to open the table; one of r,w,c,n * @param object $dba dba object to use for storage, you need this * @returns boolean FALSE on error, TRUE on success */ function open ($tableName, $mode = 'r', $dba = NULL) { // set the internal dba object if (!is_null($dba)) { $this->_dba &= $dba; } if (!($this->_dba->open($tableName, $mode))) { return FALSE; } // fetch the field descriptor at the key, DBA_TABLE_META if ($fieldString = $this->_dba->fetch(DBA_TABLE_META)) { // unpack the field descriptor into a PHP structure $this->_fieldSchema = $this->_unpackFieldSchema($fieldString); return TRUE; } else { trigger_error('DBA: Table is missing field descriptor at key, '. DBA_TABLE_META, E_USER_WARNING); return FALSE; } } /** * Closes a table * @returns boolean FALSE on error, TRUE on success */ function close () { if ($this->_dba->isWritable()) { // pack up the field structure and store it back in the table $fieldString = $this->_packFieldSchema($this->_fieldSchema); $this->_dba->replace(DBA_TABLE_META, $fieldString); return ($this->_dba->close()); } return TRUE; } /** * Creates a new table. Note, this closes any open table if $dba is not * specified * * @param string $tableName name of the table to create * @param array $fieldSchema field schema for the table * @param object $dba dba object to use */ function create ($tableName, $fieldSchema, $dba=NULL) { // pack the fieldSchema $fieldString = $this->_packFieldSchema($fieldSchema); if (is_null($dba)) { if (is_object($this->_dba)) { // close any open table, since this opens a new database with // the same internal dba object $r = $this->close(); $r = $r && $this->_dba->open($tableName, 'n'); $r = $r && $this->_dba->insert(DBA_TABLE_META, $fieldString); $r = $r && $this->_dba->close(); } else { trigger_error("DBA: Could not create $tableName, no dba object". "specified", E_USER_WARNING); return FALSE; } else { $r = $dba->open($tableName, 'n'); $r = $r && $dba->insert(DBA_TABLE_META, $fieldString); $r = $r && $dba->close(); } // return the result of the creation operations return $r; } /** * Check whether key exists * * @param $key string * @returns boolean */ function exists ($tableName) { return $this->_dba->db_exists($tableName); } /** * Returns the current read status for the database * @returns boolean */ function isOpen () { return $this->_dba->isOpen(); } /** * Returns the current read status for the database * @returns boolean */ function isReadable () { return $this->_dba->isReadable(); } /** * Returns the current write status for the database * @returns boolean */ function isWritable () { return $this->_dba->isWritable(); } /** * Returns whether a field exists in the current table's schema * @returns boolean */ function fieldExists($fieldName) { return ($this->isOpen() && isset($this->_fieldSchema[$fieldName])); } /** * Aquire an exclusive lock on the table * returns @boolean */ function lockTableEx () { return ($this->_dba->reopen('w')); } /** * Aquire a shared lock on the table * returns @boolean */ function lockTableSh ($table_name) { return ($this->_dba->reopen('r')); } /** * DBA_Table keeps an internal row index (key) * This function returns the highest row index * * @access private * @returns mixed a number or FALSE if there are no keys */ function _findMaxKey() { $maxKey = 0; $key = $this->_dba->firstkey(); while ($key) { $key = $this->_dba->nextkey($key); if (is_numeric ($key) && ($key > $maxKey)) { $maxKey = $key; } } return $maxKey; } /** * Returns a unique key to be used as a row index * * @access private * @returns integer a new key function _getUniqueKey() { // find the maxKey if necessary if (!isset ($this->_maxKey)) { $this->_maxKey = $this->_findMaxKey(); } // check if this is the first key if ($this->_maxKey === FALSE) { $this->_maxKey = 0; } else { $this->_maxKey++; } return $this->_maxKey; } /** * Returns a string for a raw field */ function _packField($field, $value) { switch ($this->_fieldSchema[$field]['type']) { case 'set': if (is_string ($value)) { $value = explode (',',$value); } if (is_array($value)) { $c_value = array(); foreach ($value as $element) { if (is_string ($element)) { $c_element = array_search ($element, $this->_fieldSchema[$field]['domain']); if (is_null ($c_element)) { } else { $c_value[] = $c_element; } } } $c_value = implode (',',$c_value); } break; case 'enum': if (is_string ($value)) { $c_value = array_search ($value, $this->_fieldSchema[$field]['domain']); if (!is_null ($c_value)) { $c_value = strval ($c_value); } } break; case 'timestamp': if (is_numeric ($value)) { $c_value = strval ($value); } else { if (is_string ($value)) { $c_value = strtotime ($value); if ($c_value != -1) { $c_value = strval ($c_value); } else { $c_value = 0; } } } break; case 'boolean': case 'bool': if (is_bool ($value)) { $c_value = strval ($value); } else { if (is_string ($value)) { // convert a 'boolean' string into a string boolean $c_value = strval(in_array(strtolower($str) ,array('t','true','y','yes','1')); } } break; case 'text': if (is_string ($value)) { $c_value = strval ($value); } break; case 'varchar': if (is_string ($value)) { if ($this->_fieldSchema[$field]['size']) { $c_value = rtrim (substr ($value, 0, $this->_fieldSchema[$field]['size'])); } else { $c_value = rtrim ($value); } } break; case 'integer': case 'int': case 'float': case 'numeric': if (is_numeric ($value)) { $c_value = strval ($value); } break; } return $c_value; } /** * */ function _unpackField($field, $value) { switch ($this->_fieldSchema[$field]['type']) { case 'set': $c_value = array(); $value = explode (',',$value); if (is_array($value)) { foreach ($value as $element) { $c_value[] = $this->_fieldSchema[$field]['domain'][$element]; } } return $c_value; case 'enum': return $this->_fieldSchema[$field]['domain'][$value]; case 'bool': if ($value == '1') return TRUE; else return FALSE; case 'timestamp': case 'integer': case 'float': case 'numeric': case 'varchar': return $value; } } function _finalizeField($field, $value) { switch ($this->_fieldSchema[$field]['type']) { case 'set': $buffer = ''; foreach ($value as $element) { $buffer .= "$element, "; } return substr($buffer,0 ,-2); case 'bool': if ($value) return "TRUE"; return "FALSE"; case 'timestamp': if ($format = $this->_fieldSchema[$field]['format']) { return date($format, $value); } else { return date($this->_dateFormat, $value); } default: return $value; } } /** * Returns a string for a field structure * The following is the grammar for each element * ENUM => name;type=enum;domain=[element1,...] * SET => name;type=set;domain=[element1,...] * TIMESTAMP => name;type=timestamp;format=<string>;init=<num> * BOOL => name;type=bool;init=[TRUE, FALSE] * TEXT => name;type=text;init=<string> * VARCHAR => name;varchar;size=<num>;init=<string> * NUMERIC => name;int;size=<num>;init=<string> */ function _packFieldSchema ($fieldSchema) { foreach ($fieldSchema as $fieldName => $fieldMeta) { $buffer = $fieldName; foreach ($fieldMeta as $attribute => $value) { $attribute = strtolower($attribute); $buffer .= ';'.$attribute.'='; switch ($attribute) { case 'domain': $buffer .= implode(',',$value); break; case 'type': $buffer .= strtolower($value); break; case 'autoincrement': if (isset($fieldMeta['floor'])) { $buffer .= $value.';floor=0'; break; } case 'autodecrement': if (!isset($fieldMeta['ceiling'])) { $buffer .= $value.';ceiling=0'; break; } default: $buffer .= $value; } } $fields[] = $buffer; } return $this->_packRawRow($fields); } /** * Unpacks a raw string into the table's field structure, * as created by _packFieldSchema */ function _unpackFieldSchema ($rawFieldString) { $rawFields = $this->_unpackRawRow($rawFieldString); foreach ($rawFields as $rawField) { $rawMeta = explode(';',$rawField); $name = array_shift($rawMeta); foreach ($rawMeta as $rawAttribute) { list($attribute,$rawValue) = explode('=',$rawAttribute); if ($attribute == 'domain') { $value = explode(',',$rawValue); } else { $value = $rawValue; } $fields[$name][$attribute] = $value; } } return $fields; } function _packRow ($data) { $buffer = array(); $i = 0; foreach ($this->_fieldSchema as $fieldName => $fieldMeta) { if (isset ($data[$fieldName])) { $c_value = $this -> _packField($fieldName, $data[$fieldName]); } elseif (isset ($data[$i])) { $c_value = $this -> _packField($fieldName, $data[$i]); } else { if ($fieldMeta['autoincrement']) { $c_value = $this->_fieldSchema[$fieldName]['ceiling']++; } else { if ($fieldMeta['autodecrement']) { $c_value = $this->_fieldSchema[$fieldName]['floor']--; } else { $c_value = $this->_packField($fieldName, $fieldMeta['default']); } } } $buffer[] = $c_value; ++$i; } return $this->_packRawRow($buffer); } function _unpackRow ($packedData) { $data = $this->_unpackRawRow($packedData); $i = 0; foreach ($this->_fieldSchema as $fieldName => $fieldMeta) { $buffer[$fieldName] = $this->_unpackField($fieldName, $data[$i]); $i++; } return $buffer; } function _packRawRow ($unpackedData) { return implode('|', $unpackedData); } function _unpackRawRow ($packedData) { return explode('|', $packedData); } function insertRow ($data) { if ($this->isOpen()) { $key = $this->_getUniqueKey(); if ($this->_dba->insert($key, $this->_packRow($data))) return $key; else return FALSE; } } function replaceRow ($key, $data) { if ($this->isOpen()) { return $this->_dba->replace($key, $this->_packRow($data)); } } function deleteRow ($key) { return $this->_dba->delete($key); } function getRow ($key) { return $this->_unpackRow($this->_dba->fetch($key)); } function finalizeRows ($rows=null) { if ($this->_dba->isOpen()) { if (is_null($rows)) { $rows = $this->getRows(); } foreach ($rows as $key=>$row) { foreach ($row as $field=>$data) { $row[$field] = $this->_finalizeField($field, $row[$field]); } $rows[$key] = $row; } return $rows; } } function getRows ($rowKeys=null) { $rows = array(); if ($this->_dba->isOpen()) { $key = $this->_dba->firstkey(); while ($key) { if ($key != DBA_TABLE_META) { if (is_null($rowIDs)) { $rows[$key] = $this->_unpackRow($this->_dba->fetch($key)); } else { if (in_array($key, $rowKeys)) { $rows[$key] = $this->_unpackRow($this->_dba->fetch($key)); } } } $key = $this->_dba->nextkey($key); } } return $rows; } function _addSpaces ($string) { foreach (array('(',')','==','!=','>','<','<=','>=') as $symbol) { $string = str_replace($symbol, " $symbol ", $string); } return $string; } function _parsePHPQuery ($rawQuery, $fieldTokens) { // add spaces around symbols for strtok to work properly $rawQuery = $this->_addSpaces($rawQuery); // begin building the php query for a row $phpQuery = ''; // scan the tokens in the raw query to build a new query // if the token is a field name, use it as a key in $row[] $token = strtok($rawQuery, ' '); while ($token) { // is this token a field name? if (in_array($token, $fieldTokens)) { $phpQuery .= "\$row['$token']"; } else { $phpQuery .= $token; } $token = strtok(' '); } return $phpQuery; } function select ($rawQuery, $rows=null) { if ($this->_dba->isOpen()) { // get a list of valid field names $fieldTokens = array_keys($this->_fieldSchema); // if we haven't passed any rows to select from, use the whole table if ($rows==null) $rows = $this->getRows(); // handle the special case of requesting all rows if ($rawQuery == '*') return $rows; // convert the query into a php statement $PHPSelect = 'foreach ($rows as $key=>$row) if ('. $this->_parsePHPQuery($rawQuery, $fieldTokens). ') $results[$key] = $row;'; // perform the select $results = array(); eval ($PHPSelect); return $results; } } function _sortCmpA ($a, $b) { foreach ($this->_sortFields as $field) { if ($a[$field] < $b[$field]) return -1; if ($a[$field] > $b[$field]) return 1; } return 0; } function _sortCmpD ($a, $b) { foreach ($this->_sortFields as $field) { if ($a[$field] < $b[$field]) return 1; if ($a[$field] > $b[$field]) return -1; } return 0; } function _parseFieldString ($fieldString) { $fields = array(); $token = strtok($fieldString, ' ,'); while ($token) { $fields[] = $token; $token = strtok(' ,'); } return $fields; } function sort ($fields, $order='a', $rows=null) { if ($this->_dba->isOpen()) { if (is_string($fields)) { // parse the sort string to produce an array of sort fields $this->_sortFields = $this->_parseFieldString($fields); } else { if (is_array($fields)) { // we already have an array of sort fields $this->_sortFields = $fields; } } // if we haven't passed any rows to select from, use the whole table if (is_null($rows)) $rows = $this->getRows(); if ($order=='a') uasort($rows, array($this, '_sortCmpA')); else uasort($rows, array($this, '_sortCmpD')); return $rows; } } function project ($fields, $rows=null) { if ($this->_dba->isOpen()) { $projectFields = array(); if (is_string($fields)) { $projectFields = $this->_parseFieldString($fields); } else { if (is_array($fields)) { // we already have an array of fields $projectFields = $fields; } } if (is_null($rows)) $rows = $this->getRows(); foreach ($rows as $key=>$row) { foreach ($projectFields as $field) { $projectedRows[$key][$field] = $row[$field]; } } return $projectedRows; } } function cmpRows ($a, $b) { $equal = TRUE; foreach ($a as $field=>$value) if ($value != $b[$field]) $equal = FALSE; return $equal; } function unique ($rows=null) { if ($this->_dba->isOpen()) { if (is_null($rows)) $rows = $this->getRows(); $results = array(); foreach ($rows as $key=>$row) { if (!isset($current) || ($current != $row)) { $results[$key] = $row; $current=$row; } } return $results; } } } Index: pear/DBA/package.xml +++ pear/DBA/package.xml <?xml version="1.0" encoding="ISO-8859-1"?> <!DOCTYPE package SYSTEM "../package.dtd"> <!-- do not use the "Type" attribute here, that one is only for generated package.xml files --> <package> <name>DBA</name> <summary>Berkely-style Database Class</summary> <description> Implements a DBM-style database using either PHP's DBA functions or a simple DBM class written in PHP itself. </description> <maintainers> <maintainer> <user>busterb</user> <name>Brent Cook</name> <email>busterb@mail.utexas.edu</email> <role>maintainer</role> </maintainer> </maintainers> <release> <license>PHP</license> <version>0.0.9</version> <date>2002-06-20</date> <state>alpha</state> <notes> Pre-initial release; this stuff is still in the works. Made lots of headway documenting and cleaning code in the driver classes. Now, they are fully PEAR compliant except for error handling, which will remain very simple for performance reasons (these are drivers, after all ;) </notes> <filelist> <dir name="/DBA" base="/DBA"> <file role="php">DBM.php</file> <file role="php">DBM_Simple.php</file> <file role="php">DBM_Compat.php</file> <file role="php">DBM_Builtin.php</file> <dir name="tests" role="test"> <file>benchmark.php</file> <file>simple.php</file> <file>graph_template</file> </dir> </dir> </filelist> </release> </package> Index: pear/DBA/tests/benchmark.php +++ pear/DBA/tests/benchmark.php <?php include 'PEAR.php'; include '../DBA.php'; $testDataArray = array ('1111', '22222222', '333333333333', '4444444444444444', '55555555555555555555', '666666666666666666666666', '7777777777777777777777777777', '88888888888888888888888888888888', '999999999999999999999999999999999999'); $maxDataIndex = sizeof ($testDataArray)-1; $maxTestKeys = array(1600, 3200, 6400, 12800, 25600); $maxTestKeys = array(1600, 3200, 6400, 12800, 25600); $transactionsInterval = 2000; $maxTransactions = $transactionsInterval * 8; $driver = 'db3'; $testDB = DBA::create($driver); function getmicrotime(){ list($usec, $sec) = explode(" ",microtime()); return ((float)$usec + (float)$sec); } if (!copy('graph_template', "./data/{$driver}_graph")) { print ("failed to copy the graph template file\n"); } $graph_fp = fopen("./data/{$driver}_graph", 'a+'); fwrite($graph_fp, "set out \"$driver.ps\"\n plot \\\n"); fflush($graph_fp); foreach ($maxTestKeys as $maxTestKey) { $dat_fp = fopen("./data/{$driver}_{$maxTestKey}.dat", 'w'); for ($transactions=$transactionsInterval; $transactions <= $maxTransactions; $transactions+=$transactionsInterval) { $result = $testDB->open('benchmark_db', 'n'); if (PEAR::isError($result)) { echo $result->getMessage()."\n"; } else { $actualTransactions = 0; // begin stopwatch $start = getmicrotime(); for ($i=0; $i<$transactions; ++$i) { $testKey = rand (0, $maxTestKey); $testData = $testDataArray[rand(0, $maxDataIndex)]; // switch (2) { switch (rand(0, 3)) { case 0: $result = @$testDB->insert($testKey, $testData); break; case 1: $result = @$testDB->delete($testKey); break; case 2: $result = @$testDB->replace($testKey, $testData); break; case 3: $result = @$testDB->fetch($testKey); } if ($result) { ++$actualTransactions; } } $testDB->close(); } // end stopwatch $stop = getmicrotime(); $line = $actualTransactions.' '.($stop - $start)."\n"; echo "Keys: $maxTestKey Transactions: $line"; fwrite($dat_fp, $line); } fclose($dat_fp); fwrite($graph_fp, "\"{$driver}_{$maxTestKey}.dat\" using 1:2 title '$driver, $maxTestKey keys' with lines,\\\n"); fflush($graph_fp); } fseek($graph_fp, -3, SEEK_END); fwrite($graph_fp, " \n"); fclose($graph_fp); ?> Index: pear/DBA/tests/simple.php +++ pear/DBA/tests/simple.php <?php // test functionality of the simple dba layer include 'PEAR.php'; include '../DBA_Simple.php'; $testDataArray = array ('1', '22', '333', '4444', '55555', '6666666', '7777777', '88888888', '999999999'); $maxDataIndex = sizeof ($testDataArray)-1; $testDB = new DBA_Simple(); if (PEAR::isError($error=$testDB->open('test', 'c'))) { echo $error->getMessage()."\n"; } else { for ($i=0; $i<1000; ++$i) { $testKey = rand (0, 99); $testData = $testDataArray[rand(0, 3)]; switch (rand(0, 3)) { case 0: if (!$testDB->exists($testKey)) { $result = $testDB->insert($testKey, $testData); } break; case 1: if ($testDB->exists($testKey)) { $result = $testDB->delete($testKey); } break; case 2: $result = $testDB->replace($testKey, $testData); break; case 3: if ($testDB->exists($testKey)) { $result = $testDB->fetch($testKey); } } if (PEAR::isError($result)) { echo $result->getMessage()."\n"; } } $testDB->close(); } $testDB->open('test', 'r'); $key = $testDB->firstkey(); while ($key !== FALSE) { echo "$key = ".$testDB->fetch($key)."\n"; $key = $testDB->nextkey($key); } $testDB->close(); ?>