DBA
[ class tree: DBA ] [ index: DBA ] [ all elements ]

Source for file Table.php

Documentation is available at Table.php

  1. <?php
  2. /* vim: set expandtab tabstop=4 shiftwidth=4: */
  3. // +----------------------------------------------------------------------+
  4. // | Copyright (c) 2002-2003 Brent Cook                                        |
  5. // +----------------------------------------------------------------------+
  6. // | This library is free software; you can redistribute it and/or        |
  7. // | modify it under the terms of the GNU Lesser General Public           |
  8. // | License as published by the Free Software Foundation; either         |
  9. // | version 2.1 of the License, or (at your option) any later version.   |
  10. // |                                                                      |
  11. // | This library is distributed in the hope that it will be useful,      |
  12. // | but WITHOUT ANY WARRANTY; without even the implied warranty of       |
  13. // | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU    |
  14. // | Lesser General Public License for more details.                      |
  15. // |                                                                      |
  16. // | You should have received a copy of the GNU Lesser General Public     |
  17. // | License along with this library; if not, write to the Free Software  |
  18. // | Foundation, Inc., 59 Temple Place, Suite 330,Boston,MA 02111-1307 USA|
  19. // +----------------------------------------------------------------------+
  20. //
  21. // $Id: Table.php,v 1.21 2003/01/30 04:16:10 busterb Exp $
  22. //
  23. require_once 'PEAR.php';
  24. require_once 'DBA.php';
  25. require_once 'DBA/Functions.php';
  26.  
  27. if (!function_exists('floatval')) {
  28.     function floatval$strValue {
  29.         $floatValue preg_replace"/[^0-9\.]*/"""$strValue );
  30.         return $floatValue;
  31.     }
  32. }
  33.  
  34. if (!isset($_dba_functions)) {
  35.     $_dba_functions array_flip(get_class_methods('DBA_Functions'));
  36. }
  37.  
  38. if (!isset($_dba_operators)) {
  39.     $_dba_operators array_flip(array('=','<','>','!','(',')','&','|',
  40.                                         '*','/','+','-','%',','));
  41. }
  42.  
  43. if (!isset($_dba_keywords)) {
  44.     $_dba_keywords array_flip(array('and','or','null','false','true'));
  45. }
  46.  
  47. // {{{ constants
  48. /**
  49.  * Reserved key used to store the schema record
  50.  */
  51. define('DBA_SCHEMA_KEY''__schema__');
  52.  
  53. /**
  54.  * Reserved character used to separate fields in a row
  55.  */
  56. define('DBA_FIELD_SEPARATOR''|');
  57. define('DBA_OPTION_SEPARATOR'';');
  58. define('DBA_DOMAIN_SEPARATOR'',');
  59. define('DBA_KEY_SEPARATOR''.');
  60.  
  61. /**
  62.  * Available types:
  63.  *  integer
  64.  *  fixed
  65.  *  float
  66.  *  char
  67.  *  varchar
  68.  *  text
  69.  *  boolean
  70.  *  enum
  71.  *  set
  72.  *  timestamp
  73.  *  function
  74.  */
  75.  
  76. /**
  77.  * Data structure keys:
  78.  * type domain length primary_key auto_increment default default_type not_null
  79.  */
  80. // }}}
  81.  
  82. /**
  83.  * DBA Table
  84.  * This class provides a table storage object.
  85.  * It uses a DBA storage object as returned by DBA::create().
  86.  * This class is used extensively in DBA_Relational as part of
  87.  * a complete, relational database system.
  88.  *
  89.  * Enough functionality exists within this class to create and delete tables,
  90.  * insert, retrieve, remove and validate data based on a table schema, and
  91.  * perform basic database operations such as selects, projects and sorts.
  92.  *
  93.  * @author  Brent Cook <busterb@mail.utexas.edu>
  94.  * @package DBA
  95.  * @access  public
  96.  * @version 0.20
  97.  */
  98. class DBA_Table extends PEAR
  99. {
  100.     // {{{ instance variables
  101.     /**
  102.      * DBA object handle
  103.      * @var     object 
  104.      * @access  private
  105.      */
  106.     var $_dba;
  107.  
  108.     /**
  109.      * Describes the types of fields in a table
  110.      * @var     array 
  111.      * @access  private
  112.      */
  113.     var $_schema;
  114.  
  115.     /**
  116.      * Default date format
  117.      * @var     string 
  118.      * @access  private
  119.      */
  120.     var $_dateFormat 'D M j G:i:s T Y';
  121.  
  122.     /**
  123.      * When no primary key is specified, this is used to generate a unique
  124.      * key; like a row ID
  125.      * @var    int 
  126.      * @access private
  127.      */
  128.     var $_maxKey=null;
  129.  
  130.     /**
  131.      * Field names to use as the primary key. An empty array indicates that
  132.      * the _rowid is to be used as the primary key
  133.      * @var    int 
  134.      * @access private
  135.      */
  136.     var $_primaryKey=array();
  137.  
  138.     /**
  139.      * Name of the DBA driver to use
  140.      * @var    string 
  141.      * @access private
  142.      */
  143.     var $_driver;
  144.  
  145.     /**
  146.      * Name of the Table
  147.      * @var    string 
  148.      * @access private
  149.      */
  150.     var $_tableName;
  151.  
  152.     // }}}
  153.  
  154.     // {{{ DBA_Table($driver = 'file')
  155.     /**
  156.      * Constructor
  157.      *
  158.      * @param string $driver dba driver to use for storage
  159.      */
  160.     function DBA_Table($driver 'file')
  161.     {
  162.         // call the base constructor
  163.         $this->PEAR();
  164.         // initialize the internal dba object
  165.         $this->_dba =DBA::create($driver);
  166.         $this->_driver $driver;
  167.     }
  168.     // }}}
  169.  
  170.     // {{{ _DBA_Table()
  171.     /**
  172.      * PEAR emulated destructor calls close on PHP shutdown
  173.      * @access  private
  174.      */
  175.     function _DBA_Table()
  176.     {
  177.         $this->close();
  178.     }
  179.     // }}}
  180.  
  181.     // {{{ raiseError($message)
  182.     function raiseError($message)
  183.     {
  184.         return PEAR::raiseError('DBA_Table: '.$message);
  185.     }
  186.     // }}}
  187.  
  188.     // {{{ open($tableName, $mode = 'r')
  189.     /**
  190.      * Opens a table
  191.      *
  192.      * @access  public
  193.      * @param   string $tableName name of the table to open
  194.      * @param   char   $mode      mode to open the table; one of r,w,c,n
  195.      * @return  object PEAR_Error on failure
  196.      */
  197.     function open($tableName$mode 'r')
  198.     {
  199.         if (($mode != 'w'&& ($mode != 'r')) {
  200.             return $this->raiseError("table open mode '$mode' is invalid");
  201.         }
  202.  
  203.         $result $this->_dba->open($tableName$mode);
  204.         if (PEAR::isError($result)) {
  205.             return $result;
  206.         }
  207.  
  208.         // fetch the field descriptor at the key, DBA_SCHEMA_KEY
  209.         if (!PEAR::isError($schema $this->_dba->fetch(DBA_SCHEMA_KEY))) {
  210.  
  211.             // unpack the field schema
  212.             $schema $this->_unpackSchema($schema);
  213.             if (PEAR::isError($schema)) {
  214.                 $this->close();
  215.                 return $schema;
  216.             }
  217.             $this->_schema $schema;
  218.  
  219.         else {
  220.             return $this->raiseError('Table is missing field descriptor.'.
  221.                                      'at key, 'DBA_SCHEMA_KEY);
  222.         }
  223.         $this->_tableName $tableName;
  224.         return $this->_schema;
  225.     }
  226.     // }}}
  227.  
  228.     // {{{ close()
  229.     /**
  230.      * Closes a table
  231.      *
  232.      * @access  public
  233.      * @return  object PEAR_Error on failure
  234.      */
  235.     function close()
  236.     {
  237.         if ($this->_dba->isWritable()) {
  238.             // pack up the field structure and store it back in the table
  239.             if (isset($this->_schema)) {
  240.                 $schema $this->_packSchema($this->_schema);
  241.                 if (DBA::isError($schema)) {
  242.                     return $schema;
  243.                 }
  244.                 $this->_dba->replace(DBA_SCHEMA_KEY$schema);
  245.             else {
  246.                 return PEAR::raiseError("No schema, what's the point :P\n");
  247.             }
  248.         }
  249.         $this->_maxKey = null;
  250.         $this->_tableName = null;
  251.         return $this->_dba->close();
  252.     }
  253.     // }}}
  254.  
  255.     // {{{ create($tableName, $schema)
  256.     /**
  257.      * Creates a new table. Note, this is a static function, and operates
  258.      * independently of a table object. It no longer closes an open table.
  259.      *
  260.      * @param   string $tableName   name of the table to create
  261.      * @param   array  $schema field schema for the table
  262.      * @return  object PEAR_Error on failure
  263.      */
  264.     function create($tableName$schema$driver$format='php')
  265.     {
  266.         // validate the schema
  267.         $v_schema DBA_Table::_validateSchema($schema);
  268.         if (PEAR::isError($v_schema)) {
  269.             return $v_schema;
  270.         }
  271.  
  272.         // pack the schema
  273.         $packedSchema DBA_Table::_packSchema($v_schema +
  274.             array('_rowid'=>array('type' => 'integer',
  275.                                 'default' => 0,
  276.                                 'auto_increment' => true),
  277.                 '_timestamp'=>array('type' => 'timestamp',
  278.                                 'default' => 'time()',
  279.                                 'default_type' => 'function')));
  280.  
  281.         if (PEAR::isError($packedSchema)) {
  282.             return $packedSchema;
  283.         }
  284.  
  285.         $dba = DBA::create($driver);
  286.         if (PEAR::isError($dba)) {
  287.             return $dba;
  288.         }
  289.  
  290.         $r $dba->open($tableName'n');
  291.         if (PEAR::isError($r)) {
  292.             return $r;
  293.         }
  294.  
  295.         $r $dba->insert(DBA_SCHEMA_KEY$packedSchema);
  296.         if (PEAR::isError($r)) {
  297.             return $r;
  298.         }
  299.  
  300.         $r $dba->close();
  301.         if (PEAR::isError($r)) {
  302.             return $r;
  303.         }
  304.     }
  305.     // }}}
  306.     
  307.     // {{{ _validateSchema($schema)
  308.     /**
  309.      * Validates a DBA schema
  310.      *
  311.      * @access private
  312.      * @returns the validated schema, PEAR_Error
  313.      */
  314.     function _validateSchema($schema{
  315.         foreach ($schema as $field=>$meta{
  316.  
  317.             if (($field == '_rowid'|| ($field == '_timestamp')) {
  318.                 return $this->raiseError("Cannot use $field as a field name");
  319.             }
  320.  
  321.             if (isset($meta['auto_increment'])) {
  322.                 if ($meta['type'== 'integer'{
  323.                     if (!isset($meta['default'])) {
  324.                         $meta['default'= 0;
  325.                         $meta['default_type''integer';
  326.                     }
  327.                 else {
  328.                     return $this->raiseError('Cannot use autoincrement with a non-integer');
  329.                 }
  330.             }
  331.  
  332.             if (isset($meta['default_type']&& ($meta['default_type'== 'function'&& 
  333.                 ($meta['default'!= 'time()')) {
  334.                     return $this->raiseError($meta['default'].' is not a valid function');
  335.             }
  336.             if (!isset($meta['default_type'])) {
  337.                 $meta['default_type'$meta['type'];
  338.                 $meta['default'"\x00";
  339.             }
  340.  
  341.             $schema[$field$meta;
  342.         }
  343.         return $schema;
  344.     }
  345.     // }}}
  346.  
  347.     // {{{ getSchema()
  348.     /**
  349.      * Returns the stored schema for the table
  350.      *
  351.      * @access  public
  352.      * @return  mixed an array of the form 'fieldname'=>array(fieldmeta) on
  353.      *           success, PEAR_Error on failure
  354.      */
  355.     function getSchema({
  356.         if ($this->isOpen()) {
  357.             return $this->_schema;
  358.         else {
  359.             return $this->raiseError('table not open, no schema available');
  360.         }
  361.     }  
  362.     // }}}
  363.  
  364.     // {{{ tableExists($tableName)
  365.     /**
  366.      * Check whether table exists
  367.      *
  368.      * @access  public
  369.      * @param   string $tableName 
  370.      * @return  boolean true if the table exists, false otherwise
  371.      */
  372.     function tableExists($tableName)
  373.     {
  374.         return DBA::db_exists($tableName$this->_driver);
  375.     }
  376.     // }}}
  377.  
  378.     // {{{ exists($key)
  379.     /**
  380.      * Check if a row with a primary key exists
  381.      *
  382.      * @access  public
  383.      * @param   string $key 
  384.      * @return  boolean true if the row exists
  385.      */
  386.     function exists($key)
  387.     {
  388.         return $this->dba->exists($key);
  389.     }
  390.     // }}}
  391.  
  392.     // {{{ isOpen()
  393.     /**
  394.      * Returns the current open status for the database
  395.      *
  396.      * @returns true if the table exists, false otherwise
  397.      * @return  boolean true if the table is open, false if it is closed
  398.      */
  399.     function isOpen()
  400.     {
  401.         return $this->_dba->isOpen();
  402.     }
  403.     // }}}
  404.  
  405.     // {{{ isReadable()
  406.     /**
  407.      * Returns the current read status for the database
  408.      *
  409.      * @return  boolean true if the table is readable, false otherwise
  410.      */
  411.     function isReadable()
  412.     {
  413.         return $this->_dba->isReadable();
  414.     }
  415.     // }}}
  416.  
  417.     // {{{ isWritable()
  418.     /**
  419.      * Returns the current write status for the database
  420.      *
  421.      * @return  boolean true if the table is writable, false otherwise
  422.      */
  423.     function isWritable()
  424.     {
  425.         return $this->_dba->isWritable();
  426.     }
  427.     // }}}
  428.  
  429.     // {{{ fieldExists($fieldName)
  430.     /**
  431.      * Returns whether a field exists in the current table's schema
  432.      *
  433.      * @return  boolean true if the field exists, false otherwise
  434.      */
  435.     function fieldExists($fieldName)
  436.     {
  437.         return($this->isOpen(&& isset($this->_schema[$fieldName]));
  438.     }
  439.     // }}}
  440.  
  441.     // {{{ lockEx()
  442.     /**
  443.      * Aquire an exclusive lock on the table
  444.      *
  445.      * @return  object PEAR_Error on failure
  446.      */
  447.     function lockEx()
  448.     {
  449.         return $this->_dba->reopen('w');
  450.     }
  451.     // }}}
  452.  
  453.     // {{{ lockSh()
  454.     /**
  455.      * Aquire a shared lock on the table
  456.      *
  457.      * @return  object PEAR_Error on failure
  458.      */
  459.     function lockSh($table_name)
  460.     {
  461.         return $this->_dba->reopen('r');
  462.     }
  463.     // }}}
  464.  
  465.     // {{{ _packField($field, $value)
  466.     /**
  467.      * Returns a string for a raw field
  468.      *
  469.      * @access  private
  470.      * @param   array   $field field to pack
  471.      * @param   string  $value value of this field to pack
  472.      * @return  string  packed version of value, as per $field spec
  473.      */
  474.     function _packField($field$value)
  475.     {
  476.         $c_value = null;
  477.         switch ($this->_schema[$field]['type']{
  478.             case 'set':
  479.                 if (is_string($value)) {
  480.                     $value explode(DBA_DOMAIN_SEPARATOR$value);
  481.                 }
  482.                 if (is_array($value)) {
  483.                     $c_value = array();
  484.                     foreach ($value as $element{
  485.                         if (is_string($element)) {
  486.                             $c_element array_search($element,
  487.                                          $this->_schema[$field]['domain']);
  488.                             // check for both null and false for backwards
  489.                             // compatibility
  490.                             if (!is_null($c_element&& ($c_element!==false)) {
  491.                                 $c_value[$c_element;
  492.                             }
  493.                         }
  494.                     }
  495.                     $c_value implode(DBA_DOMAIN_SEPARATOR,$c_value);
  496.                     break;
  497.                 }
  498.             case 'enum':
  499.                 if (is_string ($value)) {
  500.                     $c_value array_search($value,
  501.                                       $this->_schema[$field]['domain']);
  502.                     if (!is_null($c_value)) {
  503.                         $c_value strval($c_value);
  504.                         break;
  505.                     }
  506.                 }
  507.             case 'timestamp':
  508.                 if (is_numeric($value)) {
  509.                     $c_value strval($value);
  510.                     break;
  511.                 elseif (is_string($value)) {
  512.                     $c_value strtotime($value);
  513.                     if ($c_value != -1{
  514.                         $c_value strval($c_value);
  515.                         break;
  516.                     }
  517.                 }
  518.             case 'boolean':
  519.                 if (is_bool ($value)) {
  520.                     $c_value strval ($value);
  521.                     break;
  522.                 elseif (is_string ($value)) {
  523.                     // convert a 'boolean' string into a string 1 or 0
  524.                     $c_value in_array(strtolower($value),
  525.                                 array('t','true','y','yes','1')) '1':'0';
  526.                     break;
  527.                 }
  528.             case 'text':
  529.                 if (is_scalar($value)) {
  530.                     $c_value str_replace(DBA_FIELD_SEPARATOR,''$value);
  531.                     break;
  532.                 }
  533.             case 'char':
  534.                 if (is_scalar($value)) {
  535.                     $c_value str_replace(DBA_FIELD_SEPARATOR''substr(
  536.                                str_pad($value$this->_schema[$field]['length'])
  537.                                        ,0$this->_schema[$field]['length']));
  538.                     break;
  539.                 }
  540.             case 'varchar':
  541.                 if (is_scalar($value)) {
  542.                     $c_value str_replace(DBA_FIELD_SEPARATOR,''$value);
  543.                     // size is optional for varchars
  544.                     if (isset($this->_schema[$field]['length'])) {
  545.                         $c_value str_pad($c_value,
  546.                                            $this->_schema[$field]['length']);
  547.                     }
  548.                     break;
  549.                 }
  550.             case 'integer': case 'float': case 'fixed':
  551.                 if (is_numeric($value)) {
  552.                     $c_value strval($value);
  553.                     break;
  554.                 }
  555.         }
  556.         return $c_value;
  557.     }
  558.     // }}}
  559.  
  560.     // {{{ _unpackField($field, $value)
  561.     /**
  562.      * Converts a field from its packed representation to its original value
  563.      *
  564.      * @access  private
  565.      * @param   string $field field to convert
  566.      * @return  mixed  string $field packed value
  567.      */
  568.     function _unpackField($field$value)
  569.     {
  570.         switch ($this->_schema[$field]['type']{
  571.             case 'set':
  572.                 $c_value = array();
  573.                 $value explode (DBA_DOMAIN_SEPARATOR,$value);
  574.                 if (is_array($value)) {
  575.                     foreach ($value as $element{
  576.                       $c_value[$this->_schema[$field]['domain'][$element];
  577.                     }
  578.                 }
  579.                 return $c_value;
  580.             case 'enum':
  581.                 return $this->_schema[$field]['domain'][$value];
  582.             case 'boolean':
  583.                 return($value == '1');
  584.             case 'timestamp':
  585.             case 'integer':
  586.                 return intval($value);
  587.             case 'float':
  588.             case 'fixed':
  589.                 return floatval($value);
  590.             case 'char':
  591.             case 'varchar':
  592.                 return rtrim($value);
  593.             case 'text':
  594.                 return $value;
  595.         }
  596.     }
  597.     // }}}
  598.  
  599.     // {{{ _packSchema($schema)
  600.     /**
  601.      * Returns a string for a field structure
  602.      *
  603.      * This function uses the following is the grammar to pack elements:
  604.      * enum      => name;type=enum;domain=[element1,...]
  605.      * set       => name;type=set;domain=[element1,...]
  606.      * timestamp => name;type=timestamp;format=<string>;init=<num>
  607.      * boolean   => name;type=bool;default=[true, false]
  608.      * text      => name;type=text;default=<string>
  609.      * varchar   => name;varchar;size=<num>;default=<string>
  610.      * numeric   => name;[autoincrement];size=<num>;default=<string>
  611.      *
  612.      * @access  private
  613.      * @param   array  $schema schema to pack
  614.      * @return  string the packed schema
  615.      */
  616.     function _packSchema($schema$format 'php')
  617.     {
  618.         switch ($format{
  619.             case 'php':
  620.                 return serialize($schema);
  621.             case 'wddx':
  622.                 if (!function_exists('wddx_serialize_value')) {
  623.                     return $this->raiseError('wddx extension not found!');
  624.                 }
  625.                 return wddx_serialize_value($schema);
  626.             default:
  627.                 return $this->raiseError('Unknown schema format: '.$format);
  628.         }
  629.     }
  630.     // }}}
  631.  
  632.     // {{{ _unpackSchema($rawFieldString)
  633.     /**
  634.      * Unpacks a raw string as created by _packSchema into an array
  635.      * structure for use as $this->_schema
  636.      *
  637.      * @access  private
  638.      * @param   string  $rawFieldString data to be unpacked into the schema
  639.      * @return  array 
  640.      */
  641.     function _unpackSchema($rawSchema)
  642.     {
  643.         if ($rawSchema{0== 'a'{
  644.             $schema unserialize($rawSchema);
  645.         elseif ($rawSchema{0== '<'{
  646.             if (!function_exists('wddx_deserialize')) {
  647.                 return $this->raiseError('wddx extension not found!');
  648.             }
  649.             $schema wddx_deserialize($rawSchema);
  650.         else {
  651.             return $this->raiseError('Unknown schema format');
  652.         }
  653.  
  654.         $primaryKey = array();
  655.         foreach ($schema as $name => $meta{
  656.             if (isset($meta['primary_key'])) {
  657.                 $primaryKey[$name= true;
  658.             }
  659.         }
  660.         if (sizeof($primaryKey)) {
  661.             $this->_primaryKey $primaryKey;
  662.         else {
  663.             $this->_primaryKey = array('_rowid'=>true);
  664.         }
  665.         return $schema;
  666.     }
  667.     // }}}
  668.  
  669.     // {{{ _packRow($data, &$key)
  670.     /**
  671.      * Packs a fields from a raw row into an internal representation suitable
  672.      * for storing in the table. Think of this as a cross-language version of
  673.      * serialize.
  674.      *
  675.      * @access  private
  676.      * @param   array   $data row data to pack, key=>field pairs
  677.      * @return  string 
  678.      */
  679.     function _packRow($data&$key)
  680.     {
  681.         $buffer = array();
  682.         $key = array();
  683.         $i = 0;
  684.         foreach ($this->_schema as $fieldName=>$fieldMeta{
  685.  
  686.             if (isset($data[$fieldName])) {
  687.  
  688.                 // data ordering is explicit, e.g. field=>data
  689.                 $c_value $this->_packField($fieldName$data[$fieldName]);
  690.  
  691.             elseif (isset($data[$i])) {
  692.  
  693.                 // data ordering is implicit, e.g. $i=>data
  694.                 $c_value $this->_packField($fieldName$data[$i]);
  695.  
  696.             elseif (isset($fieldMeta['default'])) {
  697.  
  698.                 if (isset($fieldMeta['auto_increment'])) {
  699.                     // set the default value to 0 if none has been set before
  700.                     if (!isset($this->_schema[$fieldName]['default'])) {
  701.                         $this->_schema[$fieldName]['default'= 0;
  702.                     }
  703.                     // use the autoincrement value
  704.                     $c_value $this->_packField($fieldName,
  705.                                     $this->_schema[$fieldName]['default']++);
  706.                 else {
  707.                     // use the default value
  708.                     if ($fieldMeta['default_type'== 'function'{
  709.                         $c_value $this->_packField($fieldName,
  710.                            eval('return DBA_Functions::'.
  711.                                   $this->_schema[$fieldName]['default'].';'));
  712.                     else {
  713.                         $c_value $this->_packField($fieldName,
  714.                                     $this->_schema[$fieldName]['default']);
  715.                     }
  716.                 }
  717.  
  718.             elseif (isset($fieldMeta['not_null'])) {
  719.  
  720.                 return $this->raiseError("$fieldName cannot be null");
  721.             elseif (!isset($data[$fieldName]|| is_null($data[$fieldName])) {
  722.  
  723.                 $c_value "\x00"// \x00 is the null value placeholder
  724.             else {
  725.                 // when all else fails
  726.                 $c_value = null;
  727.             }
  728.  
  729.             if (isset($this->_primaryKey[$fieldName])) {
  730.                 $key[$c_value;
  731.             }
  732.  
  733.             $buffer[$c_value;
  734.             ++$i;
  735.         }
  736.     if (sizeof($key> 1{
  737.             $key implode(DBA_KEY_SEPARATOR$key);
  738.     else {
  739.             $key $key[0];
  740.         }
  741.         return implode(DBA_FIELD_SEPARATOR$buffer);
  742.     }
  743.     // }}}
  744.  
  745.     // {{{ _unpackRow($packedData)
  746.     /**
  747.      * Unpacks a string into an array containing the data from the original
  748.      * packed row. Think of this as a cross-language version of deserialize.
  749.      *
  750.      * @access  private
  751.      * @param   array packedData row data to unpack
  752.      * @return  array field=>value pairs
  753.      */
  754.     function _unpackRow($packedData)
  755.     {
  756.         $data explode(DBA_FIELD_SEPARATOR$packedData);
  757.         $i = 0;
  758.         foreach ($this->_schema as $fieldName=>$fieldMeta{
  759.             if ($data[$i== "\x00"// it's a placeholder
  760.                 $buffer[$fieldName= null;
  761.             else {
  762.                 $buffer[$fieldName$this->_unpackField($fieldName$data[$i]);
  763.             }
  764.             $i++;
  765.         }
  766.         return $buffer;
  767.     }
  768.     // }}}
  769.  
  770.     // {{{ insert($data)
  771.     /**
  772.      * Inserts a new row in a table
  773.      * 
  774.      * @access  public
  775.      * @param   array $data assoc array or ordered list of data to insert
  776.      * @return  mixed PEAR_Error on failure, the row index on success
  777.      */
  778.     function insert($data)
  779.     {
  780.         if ($this->isWritable()) {
  781.             $packedRow $this->_packRow($data$key);
  782.             if (PEAR::isError($packedRow)) {
  783.                 return $packedRow;
  784.             }
  785.  
  786.             $result $this->_dba->insert($key$packedRow);
  787.             if (PEAR::isError($result)) {
  788.                 return $result;
  789.             else {
  790.                 return $key;
  791.             }
  792.  
  793.         else {
  794.             return $this->raiseError('table not writable');
  795.         }
  796.     }
  797.     // }}}
  798.  
  799.     // {{{ replace($rawQuery, $data, $rows=null)
  800.     /**
  801.      * Replaces rows that match $rawQuery with $data
  802.      *
  803.      * @access  public
  804.      * @param   string $rawQuery query expression for performing the replace
  805.      * @param   array  $rows rows to select on
  806.      * @return  object PEAR_Error on failure
  807.      */
  808.     function replace($rawQuery$data$rows=null)
  809.     {
  810.         $rows =$this->select($rawQuery$rows);
  811.         if (PEAR::isError($rows)) {
  812.             return $rows;
  813.         }
  814.  
  815.         // _packRow will calculate a new primary key for each row;
  816.         // passing a dummy value preserves the current keys
  817.         $packedRow =$this->_packRow($data$dummy);
  818.         if (PEAR::isError($packedRow)) {
  819.             return $packedRow;
  820.         }
  821.         foreach (array_keys($rowsas $rowKey{
  822.             $result $this->_dba->replace($rowKey$packedRow);
  823.             if (PEAR::isError($result)) {
  824.                 return $result;
  825.             }
  826.         }
  827.     }
  828.     // }}}
  829.  
  830.     // {{{ replaceKey($key, $data)
  831.     /**
  832.      * Replaces an existing row in a table, inserts if the row does not exist
  833.      *
  834.      * @access  public
  835.      * @param   string $key row id to replace
  836.      * @param   array  $data assoc array or ordered list of data to insert
  837.      * @return  mixed  PEAR_Error on failure, the row index on success
  838.      */
  839.     function replaceKey($key$data)
  840.     {
  841.         if ($this->isOpen()) {
  842.             // _packRow will calculate a new primary key for each row;
  843.             // passing a dummy value preserves the current keys
  844.             $packedRow =$this->_packRow($data$dummy);
  845.             if (PEAR::isError($packedRow)) {
  846.                 return $packedRow;
  847.             }
  848.             return $this->_dba->replace($key$packedRow);
  849.         else {
  850.             return $this->raiseError('table not open');
  851.         }
  852.     }
  853.     // }}}
  854.  
  855.     // {{{ remove($rawQuery, $rows=null)
  856.     /**
  857.      * Removes existing rows from table that match $rawQuery
  858.      *
  859.      * @access  public
  860.      * @param   string $rawQuery query expression for performing the remove
  861.      * @param   array  $rows rows to select on
  862.      * @return  object PEAR_Error on failure
  863.      */
  864.     function remove($rawQuery$rows=null)
  865.     {
  866.         $removableRows =$this->select($rawQuery$rows);
  867.         if (PEAR::isError($rows)) {
  868.             return $rows;
  869.         }
  870.         foreach (array_keys($removableRowsas $rowKey{
  871.             $result $this->_dba->remove($rowKey);
  872.             if (PEAR::isError($result)) {
  873.                 return $result;
  874.             }
  875.         }
  876.     }
  877.     // }}}
  878.  
  879.     // {{{ removeKey($key)
  880.     /**
  881.      * Removes an existing row from a table, referenced by the row key
  882.      *
  883.      * @access  public
  884.      * @param   string $key row id to remove
  885.      * @return  object PEAR_Error on failure
  886.      */
  887.     function removeKey($key)
  888.     {
  889.         return $this->_dba->remove($key);
  890.     }
  891.     // }}}
  892.  
  893.     // {{{ fetch($key)
  894.     /**
  895.      * Fetches an existing row from a table
  896.      *
  897.      * @access  public
  898.      * @param   string $key row id to fetch
  899.      * @return  mixed  PEAR_Error on failure, the row array on success
  900.      */
  901.     function fetch($key)
  902.     {
  903.         $result $this->_dba->fetch($key);
  904.         if (!PEAR::isError($result)) {
  905.             return $this->_unpackRow($result);
  906.         else {
  907.             return $result;
  908.         }
  909.     }
  910.     // }}}
  911.  
  912.     // {{{ _finalizeField($field, $value)
  913.     /**
  914.      * Converts a field from its native value to a value, that is
  915.      * sets are converted to strings, bools are converted to 'true' and 'false'
  916.      * timestamps are converted to a readable date. No more operations
  917.      * should be performed on a field after this though.
  918.      *
  919.      * @access  private
  920.      * @param   mixed   $field 
  921.      * @param   string  $value 
  922.      * @return  string  the string representation of $field
  923.      */
  924.     function _finalizeField($field$value)
  925.     {
  926.         switch ($this->_schema[$field]['type']{
  927.             case 'set':
  928.                 $buffer '';
  929.                 foreach ($value as $element{
  930.                     $buffer .= "$element";
  931.                 }
  932.                 return substr($buffer,,-2);
  933.             case 'boolean':
  934.                 return $value 'true' 'false';
  935.             case 'timestamp':
  936.                 return date($this->_dateFormat$value);
  937.             default:
  938.                 return $value;
  939.         }
  940.     }
  941.     // }}}
  942.  
  943.     // {{{ finalize($rows=null)
  944.     /**
  945.      * Converts the results from any of the row operations to a 'finalized'
  946.      * display-ready form. That means that timestamps, sets and enums are
  947.      * converted into strings. This obviously has some consequences if you plan
  948.      * on chaining the results into another row operation, so don't call this
  949.      * unless it is the final operation.
  950.      *
  951.      * @access  public
  952.      * @param   array  $rows rows to finalize, if none are specified, returns
  953.      *                       the whole table
  954.      * @return  mixed PEAR_Error on failure, the row array on success
  955.      */
  956.     function finalize($rows=null)
  957.     {
  958.         if (is_null($rows)) {
  959.             if ($this->_dba->isOpen()) {
  960.                 $rows $this->getRows();
  961.             else {
  962.                 return $this->raiseError('table not open / no rows specified');
  963.             }
  964.         }
  965.  
  966.         foreach ($rows as $key=>$row{
  967.             foreach ($row as $field=>$data{
  968.                 $row[$field$this->_finalizeField($field$row[$field]);
  969.             }
  970.             $rows[$key$row;
  971.         }
  972.         return $rows;
  973.     }
  974.     // }}}
  975.  
  976.     // {{{ getRows($rowKeys=null)
  977.     /**
  978.      * Returns the specified rows. A multiple-value version of getRow
  979.      *
  980.      * @access  public
  981.      * @param   array  $rowKeys keys of rows to get, if none are specified,
  982.      *                         returns the whole table
  983.      * @return  mixed PEAR_Error on failure, the row array on success
  984.      */
  985.     function getRows($rowKeys=null)
  986.     {
  987.         if ($this->_dba->isOpen()) {
  988.             $rows = array();
  989.             if (is_null($rowKeys)) {
  990.                 $key $this->_dba->firstkey();
  991.                 while ($key !== false{
  992.                     if ($key !== DBA_SCHEMA_KEY{
  993.                       $rows[$key$this->_unpackRow($this->_dba->fetch($key));
  994.                     }
  995.                     $key $this->_dba->nextkey();
  996.                 }
  997.             else {
  998.                 foreach ($rowKeys as $key{
  999.                     $rows[$key$this->unpackRow($this->_dba->fetch($key));
  1000.                 }
  1001.             }
  1002.             return $rows;
  1003.         else {
  1004.             return $this->raiseError('table not open');
  1005.         }
  1006.     }
  1007.     // }}}
  1008.  
  1009.  
  1010.     // {{{ firstRow()
  1011.     /**
  1012.       * Returns the first row in the table. The built-in cursor for the
  1013.      * internal _dba object is used, so no other operations should be performed
  1014.      * other than nextRow() if iterating through the rows is the desired
  1015.      * operation
  1016.      *
  1017.      * @access public
  1018.      * @return  mixed PEAR_Error on failure, the row array on success
  1019.      *                 or false if there are no elements in the table
  1020.      *                 i.e (firstRow() === false) = table is empty
  1021.      */
  1022.     function firstRow()
  1023.     {
  1024.         if ($this->_dba->isOpen()) {
  1025.             $key $this->_dba->firstkey();
  1026.             return $this->_unpackRow($this->_dba->fetch($key));
  1027.         else {
  1028.             return $this->raiseError('table not open');
  1029.         }
  1030.     }
  1031.     // }}}
  1032.  
  1033.     // {{{ firstRow()
  1034.     /**
  1035.       * Returns the next row in the table after a firstRow or nextRow.
  1036.      *
  1037.      * @access public
  1038.      * @return  mixed PEAR_Error on failure, the row array on success
  1039.      *                 or false if there are no more elements in the table
  1040.      */
  1041.     function nextRow({
  1042.         if ($this->_dba->isOpen()) {
  1043.             $key $this->_dba->nextkey();
  1044.             return $this->_unpackRow($this->_dba->fetch($key));
  1045.         else {
  1046.             return $this->raiseError('table not open');
  1047.         }
  1048.     }
  1049.     // }}}
  1050.  
  1051.     // {{{
  1052.     /**
  1053.      * Returns all keys in the table
  1054.      * Similar to PostgrSQL's row ID's
  1055.      *
  1056.      * @access  public
  1057.      * @return  mixed  array
  1058.      */
  1059.     function getKeys()
  1060.     {
  1061.         if ($this->_dba->isOpen()) {
  1062.             return $this->_dba->getkeys();
  1063.         else {
  1064.             return $this->raiseError('table not open');
  1065.         }
  1066.     }
  1067.     // }}}
  1068.  
  1069.     // {{{ getFieldNames()
  1070.     /**
  1071.      * Returns an array of the defined field names in the table
  1072.      *
  1073.      * @access  public
  1074.      * @return  array the field names in an array
  1075.      */
  1076.     function getFieldNames()
  1077.     {
  1078.         return array_keys($this->_schema);
  1079.     }
  1080.     // }}}
  1081.  
  1082.     // {{{ function _cookToken($token, &$isField)
  1083.     function _cookToken($token&$isField)
  1084.     {
  1085.         global $_dba_functions$_dba_keywords;
  1086.         $isField = false;
  1087.  
  1088.         // a quoted string
  1089.         if ($token{0== '"' || $token{0== "'"
  1090.             || isset($_dba_keywords[$token])) {
  1091.             return $token;
  1092.     
  1093.     // a function
  1094.     elseif (isset($_dba_functions[$token])) {
  1095.             return 'DBA_Function::'.$token;
  1096.  
  1097.     // table field
  1098.         elseif (!is_numeric($token)) {
  1099.             $isField = true;
  1100.             return '$row[\''.$token.'\']';
  1101.  
  1102.     // something else
  1103.         else {
  1104.             return $token;
  1105.         }
  1106.     }
  1107.     // }}}
  1108.  
  1109.     // {{{ function _parsePHPQuery($rawQuery)
  1110.     function _parsePHPQuery($rawQuery&$fields)
  1111.     {
  1112.         global $_dba_functions$_dba_operators;
  1113.  
  1114.         $inQuote = false;
  1115.         $PHPQuery '';
  1116.         $token '';
  1117.         $fields = array();
  1118.  
  1119.         for($i=0; $i strlen($rawQuery)$i++{
  1120.             $c $rawQuery{$i};
  1121.             if ($c == "'" || $c == '"'{
  1122.                 if (!$inQuote{
  1123.                     $inQuote !$inQuote;
  1124.                     $quote $c;
  1125.                 elseif ($c == $quote{
  1126.                     $inQuote !$inQuote;
  1127.                     $quote '';
  1128.                 }
  1129.                 $token .= $c;
  1130.             elseif (isset($_dba_operators[$c]|| $c == ' '{
  1131.                 if (!$inQuote && strlen($token)) {
  1132.                     $PHPQuery .= DBA_Table::_cookToken($token$isField);
  1133.                     if ($isField{
  1134.                         $fields[$token;
  1135.                     }
  1136.                     $PHPQuery .= $c;
  1137.                     $token '';
  1138.                 elseif ($inQuote{
  1139.                     $token .= $c;
  1140.                 else {
  1141.                     $PHPQuery .= $c;
  1142.                 }
  1143.             elseif ($c == "\t" || $c == "\n" || $c == "\r"{
  1144.                 if (!$inQuote && strlen($token)) {
  1145.                     $PHPQuery .= DBA_Table::_cookToken($token$isField);
  1146.                     if ($isField{
  1147.                         $fields[$token;
  1148.                     }
  1149.                     $PHPQuery .= $c;
  1150.                     $token '';
  1151.                 elseif ($inQuote{
  1152.                     $token .= $c;
  1153.                 else {
  1154.                     $PHPQuery .= $c;
  1155.                 }
  1156.             else {
  1157.                 $token .= $c;
  1158.             }
  1159.         }
  1160.  
  1161.         if (strlen($token)) {
  1162.             $PHPQuery .= DBA_Table::_cookToken($token$isField);
  1163.         }
  1164.         return $PHPQuery;
  1165.     }
  1166.     // }}}
  1167.  
  1168.     // {{{ &select($rawQuery, $rows=null)
  1169.     /**
  1170.      * Performs a select on a table. This means that a subset of rows in a
  1171.      * table are filtered and returned based on the query. Accepts any valid
  1172.      * expression of the form '(field == field) || (field > 3)', etc. Using the
  1173.      * expression '*' returns the entire table
  1174.      * SQL analog: 'select * from rows where rawQuery'
  1175.      *
  1176.      * @access  public
  1177.      * @param   string $rawQuery query expression for performing the select
  1178.      * @param   array  $rows rows to select on
  1179.      * @return  mixed  PEAR_Error on failure, the row array on success
  1180.      */
  1181.     function &select($rawQuery$rows=null)
  1182.     {
  1183.         if ($this->_dba->isOpen()) {
  1184.  
  1185.             // if we haven't passed any rows to select from, use the whole table
  1186.             if ($rows==null)
  1187.                 $rows $this->getRows();
  1188.  
  1189.             $rows $this->getRows();
  1190.  
  1191.             // handle the special case of requesting all rows
  1192.             if ($rawQuery == '*'{
  1193.                 return $rows;
  1194.             }
  1195.  
  1196.             // convert the query into a php statement
  1197.             $PHPQuery $this->_parsePHPQuery($rawQuery$fields);
  1198.  
  1199.             // validate field names
  1200.             foreach ($fields as $field{
  1201.                 if (!$this->fieldExists($field)) {
  1202.                     return $this->raiseError($field ' is not a field name');
  1203.                 }
  1204.             }
  1205.  
  1206.             $PHPSelect 'foreach ($rows as $key=>$row) if ('.
  1207.                         $PHPQuery.') $results[$key] = $row;';
  1208.  
  1209.             // perform the select
  1210.             $results = array();
  1211.             echo $PHPSelect;
  1212.             eval ($PHPSelect);
  1213.             return $results;
  1214.  
  1215.         else {
  1216.             return $this->raiseError('table not open');
  1217.         }
  1218.     }
  1219.     // }}}
  1220.  
  1221.     // {{{ _parseFieldString($fieldString, $possibleFields)
  1222.     /**
  1223.      * explodes a string of field names into an array
  1224.      *
  1225.      * @access  private
  1226.      * @param   string  $fieldString field names to explode
  1227.      * @return  array 
  1228.      */
  1229.     function _parseFieldString($fieldString$possibleFields)
  1230.     {
  1231.         $fields = array();
  1232.         $tokens preg_split('/[ \,]/'$fieldString);
  1233.         foreach ($tokens as $token{
  1234.             if (isset($possibleFields[$token])) {
  1235.                 $fields[$token;
  1236.             }
  1237.         }
  1238.         return $fields;
  1239.     }
  1240.     // }}}
  1241.  
  1242.     // {{{ sort($fields, $order, $rows)
  1243.     /**
  1244.      * Sorts rows by field in either ascending or descending order
  1245.      * SQL analog: 'select * from rows, order by fields'
  1246.      *
  1247.      * @access  public
  1248.      * @param   mixed  $fields a string with the field name to sort by or an
  1249.      *                          array of fields to sort by in order of preference
  1250.      * @param   string $order 'a' for ascending, 'd' for descending
  1251.      * @param   array  $rows rows to sort
  1252.      * @return  mixed  PEAR_Error on failure, the row array on success
  1253.      */
  1254.     function sort($fields$order$rows)
  1255.     {
  1256.         global $_dba_sort_fields;
  1257.         if (!function_exists('_dbaSortCmpA')) {
  1258.         // {{{ _dbaSortCmpA($a, $b)
  1259.         /**
  1260.         * Comparison function for sorting ascending order
  1261.         *
  1262.         * @access  private
  1263.         * @return  int
  1264.         */
  1265.         function _dbaSortCmpA($a$b)
  1266.         {
  1267.             global $_dba_sort_fields;
  1268.             foreach ($_dba_sort_fields as $field{
  1269.                 if ($a[$field$b[$field]return -1;
  1270.                 if ($a[$field$b[$field]return 1;
  1271.             }
  1272.             return 0;
  1273.         }
  1274.         // }}}
  1275.  
  1276.         // {{{ _dbaSortCmpD($a, $b)
  1277.         /**
  1278.         * Comparison function for sorting descending order
  1279.         *
  1280.         * @access  private
  1281.         * @return  int
  1282.         */
  1283.         function _dbaSortCmpD($a$b)
  1284.         {
  1285.             global $_dba_sort_fields;
  1286.             foreach ($_dba_sort_fields as $field{
  1287.                 if ($a[$field$b[$field]return 1;
  1288.                 if ($a[$field$b[$field]return -1;
  1289.             }
  1290.             return 0;
  1291.         }
  1292.         // }}}
  1293.         }
  1294.  
  1295.         if (is_array($rows)) {
  1296.             if (is_string($fields)) {
  1297.                 // parse the sort string to produce an array of sort fields
  1298.                 // we pass the sortfields as a member variable because
  1299.                 // uasort does not pass extra parameters to the comparison
  1300.                 // function
  1301.                 $_dba_sort_fields DBA_Table::_parseFieldString($fields,
  1302.                                        reset($rows));
  1303.             else {
  1304.                 if (is_array($fields)) {
  1305.                     // we already have an array of sort fields
  1306.                     $_dba_sort_fieldssortFields $fields;
  1307.                 }
  1308.             }
  1309.  
  1310.             if ($order=='a'{
  1311.                 uasort($rows'_dbaSortCmpA');
  1312.             elseif ($order=='d'{
  1313.                 uasort($rows'_dbaSortCmpD');
  1314.             else {
  1315.                return DBA_Table::raiseError("$order is not a valid sort order");
  1316.             }
  1317.  
  1318.             return $rows;
  1319.         else {
  1320.             return DBA_Table::raiseError('no rows to sort specified');
  1321.         }
  1322.     }
  1323.     // }}}
  1324.  
  1325.     // {{{ project($fields, $rows)
  1326.     /**
  1327.      * Projects rows by field. This means that a subset of the possible fields i
  1328.      * are in the resulting rows. The SQL analog is 'select fields from table'
  1329.      *
  1330.      * @access  public
  1331.      * @param   array  $fields fields to project
  1332.      * @param   array  $rows rows to project
  1333.      * @return  mixed  PEAR_Error on failure, the row array on success
  1334.      */
  1335.     function project($fields$rows)
  1336.     {
  1337.         if (is_array($rows)) {
  1338.             $projectFields = array();
  1339.             $projectRows = array();
  1340.  
  1341.         if (is_string($fields)) {
  1342.                 $projectFields DBA_Table::_parseFieldString($fields,
  1343.                                              reset($rows));
  1344.             else {
  1345.                 if (is_array($fields)) {
  1346.                     // we already have an array of fields
  1347.                     $projectFields $fields;
  1348.                 }
  1349.             }
  1350.  
  1351.             foreach ($rows as $key=>$row{
  1352.                 foreach ($projectFields as $field{
  1353.                     $projectedRows[$key][$field$row[$field];
  1354.                 }
  1355.             }
  1356.             return $projectedRows;
  1357.         else {
  1358.             return DBA_Table::raiseError('no rows to sort specified');
  1359.         }
  1360.     }
  1361.     // }}}
  1362.  
  1363.     // {{{ cmpRows($a, $b)
  1364.     /**
  1365.      * Compares two rows
  1366.      *
  1367.      * @access  public
  1368.      * @param   array  $a row a
  1369.      * @param   array  $b row b
  1370.      * @return  bool   true if they are the same, false if they are not
  1371.      */
  1372.     function cmpRows($a$b)
  1373.     {
  1374.         $equal = true;
  1375.         foreach ($a as $field=>$value{
  1376.             if ($value != $b[$field]{
  1377.                 $equal = false;
  1378.             }
  1379.         }
  1380.         return $equal;
  1381.     }
  1382.     // }}}
  1383.  
  1384.     // {{{ unique($rows)
  1385.     /**
  1386.      * Returns the unique rows from a set of rows
  1387.      * 
  1388.      * @access  public
  1389.      * @param   array  $rows rows to process
  1390.      * @return  mixed  PEAR_Error on failure, the row array on success
  1391.      */
  1392.     function unique($rows)
  1393.     {
  1394.         if (is_array($rows)) {
  1395.             $results = array();
  1396.             foreach ($rows as $key=>$row{
  1397.                 if (!isset($current|| ($current != $row)) {
  1398.                     $results[$key$row;
  1399.                     $current=$row;
  1400.                 }
  1401.             }
  1402.             return $results;
  1403.         else {
  1404.             return DBA_Table::raiseError('no rows to sort specified');
  1405.         }
  1406.     }
  1407.     // }}}
  1408.  
  1409.     // {{{ _getColumn($field, $rows)
  1410.     function _getColumn($field$rows)
  1411.     {
  1412.         $tmp = array();
  1413.         foreach ($rows as $row{
  1414.             if (!is_null($row[$field])) {
  1415.                 $tmp[$row[$field];
  1416.             }
  1417.         }
  1418.         return $tmp;
  1419.     }
  1420.     // }}}
  1421.  
  1422. }

Documentation generated on Mon, 11 Mar 2019 14:59:55 -0400 by phpDocumentor 1.4.4. PEAR Logo Copyright © PHP Group 2004.