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

Source for file Relational.php

Documentation is available at Relational.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. // | Authors: Brent Cook <busterb@mail.utexas.edu>                        |
  21. // |          Jacob Lee <jacobswell4u@yahoo.com>                          |
  22. // +----------------------------------------------------------------------+
  23. //
  24. // $Id: Relational.php,v 1.10 2003/01/27 04:33:36 busterb Exp $
  25.  
  26. require_once 'PEAR.php';
  27. require_once 'DBA/Table.php';
  28. require_once 'DBA/TempTable.php';
  29.  
  30. /**
  31.  * A relational database manager using DBA_Table as a storage object.
  32.  * DBA_Relational extends DBA_Table by providing uniform access to multiple
  33.  * tables, automatically opening, closing and locking tables as needed by
  34.  * various operations, and provides a join operation.
  35.  *
  36.  * @author  Brent Cook <busterb@mail.utexas.edu>
  37.  * @package DBA
  38.  * @access  public
  39.  * @version 0.19
  40.  */
  41. class DBA_Relational extends PEAR
  42. {
  43.     // {{{ instance variables
  44.     /**
  45.      * Handles to table objects
  46.      * @access private
  47.      * @var array 
  48.      */
  49.     var $_tables=array();
  50.  
  51.     /**
  52.      * Location of table data files
  53.      * @access private
  54.      * @var string 
  55.      */
  56.     var $_home;
  57.  
  58.     /**
  59.      * Table driver to use
  60.      * @access private
  61.      * @var string 
  62.      */
  63.     var $_driver;
  64.  
  65.     // }}}
  66.  
  67.     // {{{ DBA_Relational($home = '', $driver = 'file')
  68.     /**
  69.      * Constructor
  70.      *
  71.      * @param string  $home path where data files are stored
  72.      * @param string  $driver DBA driver to use
  73.      */
  74.     function DBA_Relational($home ''$driver 'file')
  75.     {
  76.         // call the base constructor
  77.         $this->PEAR();
  78.  
  79.         // add trailing slash to home if not present
  80.         if (substr($home-1!= '/'{
  81.             $home $home.'/';
  82.         }
  83.         $this->_home $home;
  84.  
  85.         $this->_driver $driver;
  86.  
  87.         // create the _tables table. this keeps track of the tables to be used
  88.         // in DBA_Relational as well as which driver to use for each.
  89.         if (!$this->tableExists('_tables')) {
  90.             $this->createTable('_tables'
  91.                 array('name'=>array('type'=>'varchar',
  92.                                     'primary_key'=>true,
  93.                                     'size'=>20),
  94.                       'driver'=>array('type'=>'varchar'),
  95.                       'description'=>array('type'=>'text')));
  96.         }
  97.  
  98.         // create the _sequences table. this keeps track of named sequences
  99.         if (!$this->tableExists('_sequences')) {
  100.             $this->createTable('_sequences'
  101.                 array('name'=>array('type'=>'varchar',
  102.                                     'primary_key'=>true,
  103.                                     'size'=>20),
  104.                       'value'=>array('type'=>'integer'),
  105.                       'increment'=>array('type'=>'integer')));
  106.         }
  107.     }
  108.     // }}}
  109.  
  110.     // {{{ raiseError($message)
  111.     function raiseError($message{
  112.         return PEAR::raiseError('DBA_Relational: '.$message);
  113.     }
  114.     // }}}
  115.  
  116.     // {{{ close()
  117.     /**
  118.      * Closes all open tables
  119.      *
  120.      * @access public
  121.      */
  122.     function close()
  123.     {
  124.         if (sizeof($this->_tables)) {
  125.             reset($this->_tables);
  126.             $this->_tables[key($this->_tables)]->close();
  127.             while(next($this->_tables)) {
  128.                 $this->_tables[key($this->_tables)]->close();
  129.             }
  130.         }
  131.     }
  132.     // }}}
  133.  
  134.     // {{{ _DBA_Relational()
  135.     /**
  136.      * PEAR emulated destructor calls close on PHP shutdown
  137.      * @access  private
  138.      */
  139.     function _DBA_Relational()
  140.     {
  141.         $this->close();
  142.     }
  143.     // }}}
  144.  
  145.     // {{{ _openTable($tableName, $mode = 'r')
  146.     /**
  147.      * Opens a table, keeps it in the list of tables. Can also reopen tables
  148.      * to different file modes
  149.      *
  150.      * @access  private
  151.      * @param   string $tableName name of the table to open
  152.      * @param   char   $mode      mode to open the table; one of r,w,c,n
  153.      * @return  object PEAR_Error on failure
  154.      */
  155.     function _openTable($tableName$mode 'r')
  156.     {
  157.         if (!isset($this->_tables[$tableName])) {
  158.             if (!$this->tableExists($tableName)) {
  159.                 return $this->raiseError('table '.$tableName.' does not exist');
  160.             else {
  161.                 $this->_tables[$tableName=new DBA_Table($this->_driver);
  162.             }
  163.         }
  164.  
  165.         if (!$this->_tables[$tableName]->isOpen()) {
  166.             return $this->_tables[$tableName]->open($this->_home.$tableName$mode);
  167.         else {
  168.             if (($mode == 'r'&& !$this->_tables[$tableName]->isReadable()) {
  169.                 // obtain a shared lock on the table
  170.                 return $this->_tables[$tableName]->lockSh();
  171.             elseif (($mode == 'w'&&
  172.                        !$this->_tables[$tableName]->isWritable()){
  173.                 // obtain an exclusive lock on the table
  174.                 return $this->_tables[$tableName]->lockEx();
  175.             }
  176.         }
  177.     }
  178.     // }}}
  179.  
  180.     // {{{ tableExists($tableName)
  181.     /**
  182.      * Returns whether the specified table exists in the db home
  183.      *
  184.      * @param   string $tableName table to check existence of
  185.      * @return  boolean true if the table exists, false if it doesn't
  186.      */
  187.     function tableExists($tableName)
  188.     {
  189.         return DBA::db_exists($this->_home.$tableName$this->_driver);
  190.     }
  191.     // }}}
  192.  
  193.     // {{{ createTable
  194.     /**
  195.      * Creates a new table
  196.      *
  197.      * @access  public
  198.      * @param   string $tableName   name of the table to create
  199.      * @param   array  $schema field schema for the table
  200.      * @param   string $driver driver to use for this table
  201.      * @return  object PEAR_Error on failure
  202.      */
  203.     function createTable($tableName$schema$driver=null)
  204.     {
  205.         if (is_null($driver)) {
  206.             $driver $this->_driver;
  207.         }
  208.         $this->insert('_tables'array($tableName$driver));
  209.         return DBA_Table::create($this->_home.$tableName$schema$driver);
  210.     }
  211.     // }}}
  212.  
  213.     // {{{ dropTable
  214.     /**
  215.      * Deletes a table permanently
  216.      *
  217.      * @access  public
  218.      * @param   string  $tableName name of the table to delete
  219.      * @param   string  $driver driver that created the table
  220.      * @return  object  PEAR_Error on failure
  221.      */
  222.     function dropTable($tableName$driver=null)
  223.     {
  224.         if (is_null($driver)) {
  225.             $driver $this->_driver;
  226.         }
  227.         if (isset($this->_tables[$tableName])) {
  228.             if (PEAR::isError($result $this->_tables[$tableName]->close())) {
  229.                 return $result;
  230.             }
  231.             unset($this->_tables[$tableName]);
  232.         }
  233.  
  234.         return DBA::db_drop($tableName$driver);
  235.     }
  236.     // }}}
  237.     
  238.     // {{{ getSchema
  239.     /**
  240.      * Returns an array with the stored schema for the table
  241.      *
  242.      * @param   string $tableName 
  243.      * @return  array 
  244.      */
  245.     function getSchema($tableName)
  246.     {
  247.         $result $this->_openTable($tableName'w');
  248.         if (PEAR::isError($result)) {
  249.             return $result;
  250.         else {
  251.             return $this->_tables[$tableName]->getSchema();
  252.         }
  253.     }
  254.     // }}}
  255.  
  256.     // {{{ isOpen($tableName)
  257.     /**
  258.      * Returns the current read status for the database
  259.      *
  260.      * @return  boolean 
  261.      */
  262.     function isOpen($tableName)
  263.     {
  264.         if (isset($this->_tables[$tableName])) {
  265.             return $this->_tables[$tableName]->isOpen();
  266.         else {
  267.             return false;
  268.         }
  269.     }
  270.     // }}}
  271.  
  272.     // {{{ insert($tableName, $data)
  273.     /**
  274.      * Inserts a new row in a table
  275.      *
  276.      * @param   string $tableName table on which to operate
  277.      * @param   array  $data assoc array or ordered list of data to insert
  278.      * @return  mixed  PEAR_Error on failure, the row index on success
  279.      */
  280.     function insert($tableName$data)
  281.     {
  282.         $result $this->_openTable($tableName'w');
  283.         if (PEAR::isError($result)) {
  284.             return $result;
  285.         else {
  286.             return $this->_tables[$tableName]->insert($data);
  287.         }
  288.     }
  289.     // }}}
  290.  
  291.     // {{{ replace($tableName, $rawQuery, $data, $rows=null)
  292.     /**
  293.      * Replaces rows that match $rawQuery
  294.      *
  295.      * @access  public
  296.      * @param   string $rawQuery query expression for performing the replace
  297.      * @param   array  $rows subset of rows to choose from
  298.      * @return  object PEAR_Error on failure
  299.      */
  300.     function replace($tableName$rawQuery$data$rows=null)
  301.     {
  302.         $result $this->_openTable($tableName'w');
  303.         if (PEAR::isError($result)) {
  304.             return $result;
  305.         else {
  306.             return $this->_tables[$tableName]->replace($rawQuery$data$rows);
  307.         
  308.     }
  309.     // }}}
  310.  
  311.     // {{{ replaceKey($tableName, $key, $data)
  312.     /**
  313.      * Replaces an existing row in a table, inserts if the row does not exist
  314.      *
  315.      * @access  public
  316.      * @param   string $tableName table on which to operate
  317.      * @param   string $key row id to replace
  318.      * @param   array  $data assoc array or ordered list of data to insert
  319.      * @return  mixed  PEAR_Error on failure, the row index on success
  320.      */
  321.     function replaceKey($tableName$key$data)
  322.     {
  323.         $result $this->_openTable($tableName'w');
  324.         if (PEAR::isError($result)) {
  325.             return $result;
  326.         else {
  327.             return $this->_tables[$tableName]->replaceKey($key$data);
  328.         
  329.     }
  330.     // }}}
  331.  
  332.     // {{{ remove($tableName, $rawQuery, $rows=null)
  333.     /**
  334.      * Removes rows that match $rawQuery with $
  335.      *
  336.      * @access  public
  337.      * @param   string $rawQuery query expression for performing the remove
  338.      * @param   array  $rows subset of rows to choose from
  339.      * @return  object PEAR_Error on failure
  340.      */
  341.     function remove($tableName$rawQuery$rows=null)
  342.     {
  343.         $result $this->_openTable($tableName'w');
  344.         if (PEAR::isError($result)) {
  345.             return $result;
  346.         else {
  347.             return $this->_tables[$tableName]->remove($rawQuery$rows);
  348.         
  349.     }
  350.     // }}}
  351.  
  352.     // {{{ removeKey($tableName, $key)
  353.     /**
  354.      * Remove an existing row in a table
  355.      *
  356.      * @access  public
  357.      * @param   string $tableName table on which to operate
  358.      * @param   string $key row id to remove
  359.      * @return  object PEAR_Error on failure
  360.      */
  361.     function removeKey($tableName$key)
  362.     {
  363.         $result $this->_openTable($tableName'w');
  364.         if (PEAR::isError($result)) {
  365.             return $result;
  366.         else {
  367.             return $this->_tables[$tableName]->remove($key);
  368.         }
  369.     }
  370.     // }}}
  371.  
  372.     // {{{ fetch($tableName, $key)
  373.     /**
  374.      * Fetches an existing row from a table
  375.      *
  376.      * @access  public
  377.      * @param   string $tableName table on which to operate
  378.      * @param   string $key row id to fetch
  379.      * @return  mixed  PEAR_Error on failure, the row array on success
  380.      */
  381.     function fetch($tableName$key)
  382.     {
  383.         $result $this->_openTable($tableName'r');
  384.         if (PEAR::isError($result)) {
  385.             return $result;
  386.         else {
  387.             return $this->_tables[$tableName]->fetch($key);
  388.         }
  389.     }
  390.     // }}}
  391.  
  392.     // {{{ sort($fields, $order='a', $rows)
  393.     /**
  394.      * Sorts rows by field in either ascending or descending order
  395.      * SQL analog: 'select * from rows, order by fields'
  396.      *
  397.      * @access  public
  398.      * @param   mixed  $fields a string with the field name to sort by or an
  399.      *                          array of fields to sort by in order of preference
  400.      * @param   string $order 'a' for ascending, 'd' for descending
  401.      * @param   array  $rows rows to sort, sorts the entire table if not
  402.      *                        specified
  403.      * @return  mixed  PEAR_Error on failure, the row array on success
  404.      */
  405.     function sort($fields$order='a'$rows)
  406.     {
  407.         return DBA_Table::sort($fields$order$rows);
  408.     }
  409.     // }}}
  410.  
  411.     // {{{ project($fields, $rows)
  412.     /**
  413.      * Projects rows by field. This means that a subset of the possible fields
  414.      * are in the resulting rows. The SQL analog is 'select fields from table'
  415.      *
  416.      * @access  public
  417.      * @param   array  $fields fields to project
  418.      * @param   array  $rows rows to project, projects entire table if not
  419.      *                        specified
  420.      * @return  mixed  PEAR_Error on failure, the row array on success
  421.      */
  422.     function project($fields$rows)
  423.     {
  424.         return DBA_Table::project($fields$rows);
  425.     }
  426.     // }}}
  427.  
  428.     // {{{ unique($rows)
  429.     /**
  430.      * Returns the unique rows from a set of rows
  431.      *
  432.      * @access  public
  433.      * @param   array  $rows rows to process, uses entire table if not
  434.      *                      specified
  435.      * @return  mixed  PEAR_Error on failure, the row array on success
  436.      */
  437.     function unique($rows)
  438.     {
  439.         return DBA_Table::unique($rows);
  440.     }
  441.     // }}}
  442.  
  443.     // {{{ finalize($tableName, $rows=null)
  444.     /**
  445.      * Converts the results from any of the row operations to a 'finalized'
  446.      * display-ready form. That means that timestamps, sets and enums are
  447.      * converted into strings. This obviously has some consequences if you plan
  448.      * on chaining the results into another row operation, so don't call this
  449.      * unless it is the final operation.
  450.      *
  451.      * This function does not yet work reliably with the results of a join
  452.      * operation, due to a loss of metadata
  453.      *
  454.      * @access  public
  455.      * @param   string $tableName table on which to operate
  456.      * @param   array  $rows rows to finalize, if none are specified, returns
  457.      *                       the whole table
  458.      * @return  mixed  PEAR_Error on failure, the row array on success
  459.      */
  460.     function finalize($tableName$rows=null)
  461.     {
  462.         $result $this->_openTable($tableName'r');
  463.         if (PEAR::isError($result)) {
  464.             return $result;
  465.         else {
  466.             return $this->_tables[$tableName]->finalize($rows);
  467.         }
  468.     }
  469.     // }}}
  470.  
  471.     // {{{ _validateTable(&$table, &$rows, &$fields, $altName)
  472.     /**
  473.      * Verifies that the fields submitted exist in $table
  474.      * @access private
  475.      */
  476.     function _validateTable(&$table&$rows&$fields$altName)
  477.     {
  478.         // validate query by checking for existence of fields
  479.         if (is_string($table&& !PEAR::isError($this->_openTable($table'r')))
  480.         {
  481.  
  482.             $rows $this->_tables[$table]->getRows();
  483.             $fields $this->_tables[$table]->getFieldNames();
  484.             return true;
  485.  
  486.         elseif (is_array($table&& sizeof($table)) {
  487.             reset($table);
  488.             $rows $table;
  489.             $fields array_keys(current($table));
  490.             $table $altName;
  491.             return true;
  492.         elseif (is_null($table)) {
  493.             $fields = null;
  494.             $rows = null;
  495.             return true;
  496.         else {
  497.             return false;
  498.         }
  499.     }
  500.     // }}}
  501.  
  502.     // {{{ select($tableName, $query, $rows=null)
  503.     /**
  504.      * Performs a select on a table. This means that a subset of rows in a
  505.      * table are filtered and returned based on the query. Accepts any valid
  506.      * expression of the form '(field == field) || (field > 3)', etc. Using the
  507.      * expression '*' returns the entire table
  508.      * SQL analog: 'select * from rows where rawQuery'
  509.      *
  510.      * @access  public
  511.      * @param   string $tableName table on which to operate
  512.      * @param   string $query query expression for performing the select
  513.      * @return  mixed  PEAR_Error on failure, the row array on success
  514.      */
  515.     function select($tableName$query='*'$rows=null)
  516.     {
  517.         $result $this->_openTable($tableName,'r');
  518.         if (PEAR::isError($result)) {
  519.             return $result;
  520.         }
  521.  
  522.         // use the table's select query parser
  523.         return $this->_tables[$tableName]->select($query$rows);
  524.     }
  525.     // }}}
  526.  
  527.     //{{{ &join($tableA,$tableB,$option=array())
  528.     /**
  529.      * Makes a new table joining 2 existing tables
  530.      *
  531.      * @access  public
  532.      * @param   $tableA mixed table name or object to join
  533.      * @param   $tableB mixed table name or object to join
  534.      * @param   string $query query expression for performing the select
  535.      * @param   $type   string 'inner','outer','right','left'
  536.      * @param   array                  $option join options
  537.      * @return  object PEAR_Error on failure, DBA_TempTable on success
  538.      */
  539.     function &joinTables($tableA$tableB$clause=true$type='inner')
  540.     {
  541.         if (!DBA_TempTable::isTempTable($tableA)) {
  542.             // Open table
  543.             $result $this->_openTable($tableA'r');
  544.             if (PEAR::isError($result))
  545.                 return $result;
  546.  
  547.             // Makes a DBA_TempTable object
  548.             $objectA = new DBA_TempTable($this->_tables[$tableA]$option['alias'][0]);
  549.         else {
  550.             $objectA &$tableA;
  551.         }
  552.         
  553.         if (!DBA_TempTable::isTempTable($tableB)) {
  554.             // Open table
  555.             $result $this->_openTable($tableB'r');
  556.             if (PEAR::isError($result))
  557.                 return $result;
  558.  
  559.             // Makes a DBA_TempTable object
  560.             $objectB = new DBA_TempTable($this->_tables[$tableB]$option['alias'][1]);
  561.         else {
  562.             $objectB &$tableB;
  563.         }
  564.  
  565.         if (isset($option['on'])) {
  566.             $onJoin $this->_parsePHPQuery($option['on']);
  567.             if (PEAR::isError($onJoin))
  568.                 return $onJoin;
  569.         else {
  570.             $onJoin = 1;
  571.         }
  572.         if (isset($option['using'])) {
  573.  
  574.             //not supported yet...
  575.         }
  576.  
  577.         $PHPeval '
  578.         // get each row from each table object and make a new row
  579.         $keyA = $objectA->firstRow();
  580.         while($keyA !== false) {
  581.             $rowA = $objectA->getRow($keyA);
  582.             $keyB = $objectB->firstRow();
  583.             while($keyB !== false) {
  584.                 $rowB = $objectB->getRow($keyB);
  585.  
  586.                 //make a new row
  587.                 $row = $rowA+$rowB;
  588.  
  589.                 if ('.$onJoin.') {
  590.                     $rows[] = $row;
  591.                 }
  592.                 $keyB = $objectB->nextRow();
  593.             }
  594.             $keyA = $objectA->nextRow();
  595.         }
  596.  
  597.         // If join type is outer
  598.         if ($type != "inner") {
  599.             // Makes blank rows;
  600.             $blankA = $objectA->blankRow();
  601.             $blankB = $objectB->blankRow();
  602.  
  603.             if ($type != "right") {
  604.                 $keyA = $objectA->firstRow();
  605.                 while ($keyA !== false) {
  606.                     $rowA = $objectA->getRow($keyA);
  607.                     
  608.                     $row = $rowA + $blankB;
  609.  
  610.                     if ('.$onJoin.') {
  611.                         $rows[] = $row;
  612.                     }
  613.                     $keyA = $objectA->nextRow();
  614.                 }
  615.             }
  616.  
  617.             if ($type != "left") {
  618.                 $keyB = $objectB->firstRow();
  619.                 while ($keyB !== false) {
  620.                     $rowB = $objectB->getRow($keyB);
  621.                     
  622.                     $row = $blankA + $rowB;
  623.  
  624.                     if ('.$onJoin.') {
  625.                         $rows[] = $row;
  626.                     }
  627.                     $keyB = $objectB->nextRow();
  628.                 }
  629.             }
  630.         }
  631.         ';
  632.         eval($PHPeval);
  633.  
  634.         // set rows to newly created object
  635.         $tblObject = new DBA_TempTable();
  636.         $tblObject->set($rows);
  637.  
  638.         // return table object
  639.         return $tblObject;
  640.     }
  641.     // }}}
  642.  
  643.     // {{{ join($tableA, $tableB, $rawQuery)
  644.     /**
  645.      * Joins rows between two tables based on a query.
  646.      *
  647.      * @access  public
  648.      * @param   string $tableA   name of table to join
  649.      * @param   string $tableB   name of table to join
  650.      * @param   string $rawQuery expression of how to join tableA and tableB
  651.      * @return  mixed  PEAR_Error on failure, the row array on success
  652.      */
  653.     function join($tableA$tableB$rawQuery)
  654.     {
  655.         return $this->raiseError('TODO: merge new join()');
  656. /*
  657.         // validate tables
  658.         if (!$this->_validateTable($tableA, $rowsA, $fieldsA, 'A')) {
  659.             return $this->raiseError("$tableA not in query");
  660.         }
  661.  
  662.         if (!$this->_validateTable($tableB, $rowsB, $fieldsB, 'B')) {
  663.             return $this->raiseError("$tableA not in query");
  664.         }
  665.  
  666.         // check for empty tables
  667.         if (is_null($rowsA) || is_null($rowsB)) {
  668.             return null;
  669.         }
  670.         
  671.         // TODO Implement merge join, needs secondary indexes on tables
  672.         // build the join operation with nested loops
  673.         $query = $this->_parsePHPQuery($rawQuery, $fieldsA, $fieldsB,
  674.                                        $tableA, $tableB);
  675.         if (PEAR::isError($query)) {
  676.             return $query;
  677.         }
  678.  
  679.         $results = array();
  680.         $PHPJoin = 'foreach ($rowsA as $rowA) foreach ($rowsB as $rowB) if ('.
  681.           $this->_parsePHPQuery($rawQuery, $fieldsA, $fieldsB, $tableA, $tableB)
  682.           .') $results[] = array_merge($rowA, $rowB);';
  683.  
  684.         // evaluate the join
  685.         eval ($PHPJoin);
  686.  
  687.         return $results;
  688. */
  689.     }
  690.     // }}}
  691. }

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