Advertisement
Guest User

class.sqldriver.php

a guest
Dec 16th, 2011
109
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
PHP 71.40 KB | None | 0 0
  1. <?php if (!defined('APPLICATION')) exit();
  2. /*
  3. Copyright 2008, 2009 Vanilla Forums Inc.
  4. This file is part of Garden.
  5. Garden is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
  6. Garden is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  7. You should have received a copy of the GNU General Public License along with Garden.  If not, see <http://www.gnu.org/licenses/>.
  8. Contact Vanilla Forums Inc. at support [at] vanillaforums [dot] com
  9. */
  10.  
  11. /**
  12.  * The Gdn_DatabaseDriver class (equivalent to SqlBuilder from Vanilla 1.x) is used
  13.  * by any given database driver to build and execute database queries.
  14.  *
  15.  * This class is HEAVILY inspired by and, in places, flat out copied from
  16.  * CodeIgniter (http://www.codeigniter.com). My hat is off to them.
  17.  *
  18.  * @author Mark O'Sullivan
  19.  * @copyright 2003 Mark O'Sullivan
  20.  * @license http://www.opensource.org/licenses/gpl-2.0.php GPL
  21.  * @package Garden
  22.  * @version @@GARDEN-VERSION@@
  23.  * @namespace Garden.Database
  24.  */
  25.  
  26. abstract class Gdn_SQLDriver {
  27.    
  28.    /// CONSTRUCTOR ///
  29.    public function __construct() {
  30.       $this->ClassName = get_class($this);
  31.       $this->Reset();
  32.    }
  33.    
  34.    
  35.    /// PROPERTIES ///
  36.    
  37.    
  38.  
  39.    /**
  40.     * An associative array of table alias => table name pairs.
  41.     *
  42.     * @var array
  43.     */
  44.    protected $_AliasMap;
  45.  
  46.    /**
  47.     *
  48.     * @var bool Whether or not to capture (not execute) DML statements.
  49.     */
  50.    public $CaptureModifications = FALSE;
  51.    
  52.    /**
  53.     * The name of the class that has been instantiated.
  54.     *
  55.     * @var string
  56.     */
  57.    public $ClassName;
  58.    
  59.    /**
  60.     * The database connection.
  61.     *
  62.     * @var Gdn_Database The connection and engine information for the database.
  63.     */
  64.    public $Database;
  65.    
  66.    /**
  67.     * The name of the cache key associated with this query.
  68.     *
  69.     * @var string
  70.     */
  71.    protected $_CacheKey = NULL;
  72.    protected $_CacheOperation = NULL;
  73.    
  74.    /**
  75.     * An associative array of information about the database to which the
  76.     * application is connected. Values include: Engine, Version, DatabaseName.
  77.     *
  78.     * @var string
  79.     */
  80.    protected $_DatabaseInfo = array();
  81.  
  82.    /**
  83.     * A boolean value indicating if this is a distinct query.
  84.     *
  85.     * @var boolean
  86.     */
  87.    protected $_Distinct;
  88.  
  89.    /**
  90.     * A collection of tables from which data is being selected.
  91.     *
  92.     * @var array
  93.     */
  94.    protected $_Froms;
  95.  
  96.    /**
  97.     * A collection of group by clauses.
  98.     *
  99.     * @var array
  100.     */
  101.    protected $_GroupBys;
  102.  
  103.    /**
  104.     * A collection of having clauses.
  105.     *
  106.     * @var array
  107.     */
  108.    protected $_Havings;
  109.  
  110.    /**
  111.     * A collection of tables which have been joined to.
  112.     *
  113.     * @var array
  114.     */
  115.    protected $_Joins;
  116.  
  117.    /**
  118.     * The number of records to limit the query to. FALSE by default.
  119.     *
  120.     * @var int
  121.     */
  122.    protected $_Limit;
  123.  
  124.    /**
  125.     * An associative array of parameter_name => parameter_value pairs to be
  126.     * inserted into the prepared $this->_PDOStatement.
  127.     *
  128.     * @var array
  129.     */
  130.    protected $_NamedParameters = array();
  131.    
  132.    /**
  133.     * Whether or not to reset the properties when a query is executed.
  134.     *
  135.     * @var int
  136.     *   0 = The object will reset after query execution.
  137.     *   1 = The object will not reset after the <b>NEXT</b> query execution.
  138.     *   2 = The object will not reset after <b>ALL</b> query executions.
  139.     */
  140.    protected $_NoReset = FALSE;
  141.  
  142.    /**
  143.     * The offset from which data should be returned. FALSE by default.
  144.     *
  145.     * @var int
  146.     */
  147.    protected $_Offset;
  148.    
  149.    /**
  150.     * The number of where groups currently open.
  151.     *
  152.     * @var int
  153.     */
  154.    protected $_OpenWhereGroupCount;
  155.  
  156.    /**
  157.     * Extended options for a statement, usable by the driver.
  158.     *
  159.     * @var array
  160.     */
  161.    protected $_Options = array();
  162.  
  163.    /**
  164.     * A collection of order by statements.
  165.     *
  166.     * @var array
  167.     */
  168.    protected $_OrderBys;
  169.    
  170.    /**
  171.     * A collection of fields that are being selected.
  172.     *
  173.     * @var array
  174.     */
  175.    protected $_Selects;
  176.  
  177.    /**
  178.     * An associative array of Field Name => Value pairs to be saved
  179.     * to the database.
  180.     *
  181.     * @var array
  182.     */
  183.    protected $_Sets;
  184.    
  185.    /**
  186.     * The logical operator used to concatenate where clauses.
  187.     *
  188.     * @var string
  189.     */
  190.    protected $_WhereConcat;
  191.    
  192.    /**
  193.     * The default $_WhereConcat that will be reverted back to after every where clause is appended.
  194.     *
  195.     * @var string
  196.     */
  197.    protected $_WhereConcatDefault;
  198.  
  199.    /**
  200.     * The number of where groups to open.
  201.     *
  202.     * @var int
  203.     */
  204.    protected $_WhereGroupCount;
  205.  
  206.    /**
  207.     * A collection of where clauses.
  208.     *
  209.     * @var array
  210.     */
  211.    protected $_Wheres;
  212.  
  213.  
  214.    /// METHODS ///
  215.    
  216.    /**
  217.     * Removes table aliases from an array of JOIN ($this->_Joins) and GROUP BY
  218.     * ($this->_GroupBys) strings. Returns the $Statements array with prefixes
  219.     * removed.
  220.     *
  221.     * @param array $Statements The string specification of the table. ie.
  222.     * "tbl_User as u" or "user u".
  223.     * @return array the array of filtered statements.
  224.     */
  225.    //protected function _FilterTableAliases($Statements) {
  226.    //   foreach ($Statements as $k => $v) {
  227.    //      foreach ($this->_AliasMap as $Alias => $Table) {
  228.    //         $Statement = preg_replace('/(\w+\.\w+)/', $this->EscapeIdentifier('$0'), $v); // Makes `table.field`
  229.    //         $Statement = str_replace(array($this->Database->DatabasePrefix.$Table, '.'), array($Table, $this->EscapeSql('.')), $Statement);
  230.    //      }
  231.    //      $Statements[$k] = $Statement;
  232.    //   }
  233.    //   return $Statements;
  234.    //}
  235.    
  236.    /**
  237.     * Concat the next where expression with an 'and' operator.
  238.     * <b>Note</b>: Since 'and' is the default operator to begin with this method doesn't usually have to be called,
  239.     * unless Gdn_DatabaseDriver::Or(FALSE) has previously been called.
  240.     *
  241.     * @param boolean $SetDefault Whether or not the 'and' is one time or sets the default operator.
  242.     * @return Gdn_DatabaseDriver $this
  243.     * @see Gdn_DatabaseDriver::OrOp()
  244.     */
  245.    public function AndOp($SetDefault = FALSE) {
  246.       $this->_WhereConcat = 'and';
  247.       if($SetDefault) {
  248.          $this->_WhereConcatDefault = 'and';
  249.       }
  250.      
  251.       return $this;
  252.    }
  253.  
  254.    public function ApplyParameters($Sql, $Parameters = NULL) {
  255.       if (!is_array($Parameters))
  256.          $Parameters = $this->_NamedParameters;
  257.          
  258.       // Sort the parameters so that we don't have clashes.
  259.       krsort($Parameters);
  260.       foreach ($Parameters as $Key => $Value) {
  261.          if (is_null($Value))
  262.             $QValue = 'null';
  263.          else
  264.             $QValue = $this->Database->Connection()->quote($Value);
  265.          $Sql = str_replace($Key, $QValue, $Sql);
  266.       }
  267.       return $Sql;
  268.    }
  269.    
  270.    /**
  271.     * Begin bracketed group in the where clause to group logical expressions together.
  272.     *
  273.     * @return Gdn_DatabaseDriver $this
  274.     */
  275.    public function BeginWhereGroup() {
  276.       $this->_WhereGroupCount++;
  277.       $this->_OpenWhereGroupCount++;
  278.       return $this;
  279.    }
  280.    
  281.    /**
  282.     * Returns a single Condition Expression for use in a 'where' or an 'on' clause.
  283.     *
  284.     * @param string $Field The name of the field on the left hand side of the expression.
  285.     *   If $Field ends with an operator, then it used for the comparison. Otherwise '=' will be used.
  286.     * @param mixed $Value The value on the right side of the expression. This has different behaviour depending on the type.
  287.     *   <b>string</b>: The value will be used. If $EscapeValueSql is true then it will end up in a parameter.
  288.     *   <b>array</b>: DatabaseFunction => Value will be used. if DatabaseFunction contains a "%s" then sprintf will be used.
  289.     *     In this case Value will be assumed to be a string.
  290.     *
  291.     * <b>New Syntax</b>
  292.     * The $Field and Value expressions can begin with special characters to do certain things.
  293.     * <ul>
  294.     * <li><b>=</b>: This means that the argument is a function call.
  295.     *   If you want to pass field reference arguments into the function then enclose them in square brackets.
  296.     *   ex. <code>'=LEFT([u.Name], 4)'</code> will call the LEFT database function on the u.Name column.</li>
  297.     * <li><b>@</b>: This means that the argument is a literal.
  298.     *   This is useful for passing in literal numbers.</li>
  299.     * <li><b>no prefix></b>: This will treat the argument differently depending on the argument.
  300.     *   - <b>$Field</b> - The argument is a column reference.
  301.     *   - <b>$Value</b> - The argument will become a named parameter.
  302.     * </li></ul>
  303.     * @return string The single expression.
  304.     */
  305.    public function ConditionExpr($Field, $Value, $EscapeFieldSql = TRUE, $EscapeValueSql = TRUE) {
  306.       // Change some variables from the old parameter style to the new one.
  307.       // THIS PART OF THE FUNCTION SHOULD EVENTUALLY BE REMOVED.
  308.       if($EscapeFieldSql === FALSE) {
  309.          $Field = '@' . $Field;
  310.       }
  311.       if(is_array($Value)) {
  312.          $FunctionCall = array_keys($Value);
  313.          $FunctionCall = $FunctionCall[0];
  314.          $FunctionArg = $Value[$FunctionCall];
  315.          if($EscapeValueSql)
  316.             $FunctionArg = '[' . $FunctionArg . ']';
  317.          
  318.          if(stripos($FunctionCall, '%s') === FALSE)
  319.             $Value = '=' . $FunctionCall . '(' . $FunctionArg . ')';
  320.          else
  321.             $Value = '=' . sprintf($FunctionCall, $FunctionArg);
  322.          $EscapeValueSql = FALSE;
  323.       } else if(!$EscapeValueSql && !is_null($Value)) {
  324.          $Value = '@' . $Value;
  325.       }
  326.      
  327.       // Check for a straight literal field expression.
  328.       if(!$EscapeFieldSql && !$EscapeValueSql && is_null($Value))
  329.          return substr($Field, 1); // warning: might not be portable across different drivers
  330.      
  331.       $Expr = ''; // final expression which is built up
  332.       $Op = ''; // logical operator
  333.      
  334.       // Try and split an operator out of $Field.
  335.       $FieldOpRegex = "/(?:\s*(=|<>|>|<|>=|<=)\s*$)|\s+(like|not\s+like)\s*$|\s+(?:(is)\s+(null)|(is\s+not)\s+(null))\s*$/i";
  336.       $Split = preg_split($FieldOpRegex, $Field, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
  337.       if(count($Split) > 1) {
  338.          $Field = $Split[0];
  339.          $Op = $Split[1];
  340.          if (count($Split) > 2) {
  341.             $Value = null;
  342.          }
  343.       } else {
  344.          $Op = '=';
  345.       }
  346.      
  347.       if($Op == '=' && is_null($Value)) {
  348.          // This is a special case where the value SQL is checking for an is null operation.
  349.          $Op = 'is';
  350.          $Value = '@null';
  351.          $EscapeValueSql = FALSE;
  352.       }
  353.      
  354.       // Add the left hand side of the expression.
  355.       $Expr .= $this->_ParseExpr($Field, NULL, $EscapeFieldSql);
  356.      
  357.       // Add the expression operator.
  358.       $Expr .= ' '.$Op.' ';
  359.      
  360.       if ($Op == 'is' || $Op == 'is not' && is_null($Value)) {
  361.          $Expr .= 'null';
  362.       } else {
  363.          // Add the right side of the expression.
  364.          $Expr .= $this->_ParseExpr($Value, $Field, $EscapeValueSql);
  365.       }
  366.      
  367.       return $Expr;
  368.    }
  369.    
  370.    /**
  371.     * Set the cache key for this transaction
  372.     *
  373.     * @param string|array $Key The cache key (or array of keys) that this query will save into.
  374.     * @return Gdn_SQLDriver $this
  375.     */
  376.    public function Cache($Key, $Operation = NULL, $Backing = NULL) {
  377.       if (!$Key) {
  378.          $this->_CacheKey = NULL;
  379.          $this->_CacheOperation = NULL;
  380.          $this->_CacheBacking = NULL;
  381.  
  382.          return $this;
  383.       }
  384.  
  385.       $this->_CacheKey = $Key;
  386.      
  387.       if (!is_null($Operation))
  388.          $this->_CacheOperation = $Operation;
  389.      
  390.       if (!is_null($Backing))
  391.          $this->_CacheBacking = $Backing;
  392.  
  393.       return $this;
  394.    }
  395.  
  396.    /**
  397.     * Returns the name of the database currently connected to.
  398.     */
  399.    public function DatabaseName() {
  400.       return $this->Information('DatabaseName');
  401.    }
  402.  
  403.    /**
  404.     * Builds and executes a delete from query.
  405.     *
  406.     * @param mixed $Table The table (or array of table names) to delete from.
  407.     * @param mixed $Where The string on the left side of the where comparison, or an associative
  408.     * array of Field => Value items to compare.
  409.     * @param int $Limit The number of records to limit the query to.
  410.     */
  411.    public function Delete($Table = '', $Where = '', $Limit = FALSE) {
  412.       if ($Table == '') {
  413.          if (!isset($this->_Froms[0]))
  414.             return FALSE;
  415.  
  416.          $Table = $this->_Froms[0];
  417.       } elseif (is_array($Table)) {
  418.          foreach ($Table as $t) {
  419.             $this->Delete($t, $Where, $Limit, FALSE);
  420.          }
  421.  
  422.          return;
  423.       } else {
  424.          $Table = $this->EscapeIdentifier($this->Database->DatabasePrefix.$Table);
  425.       }
  426.  
  427.       if ($Where != '')
  428.          $this->Where($Where);
  429.  
  430.       if ($Limit !== FALSE)
  431.          $this->Limit($Limit);
  432.  
  433.       if (count($this->_Wheres) == 0)
  434.          return FALSE;
  435.  
  436.       $Sql = $this->GetDelete($Table, $this->_Wheres, $this->_Limit);
  437.  
  438.       return $this->Query($Sql, 'delete');
  439.    }
  440.    
  441.    /**
  442.     * Specifies that the query should be run as a distinct so that duplicate
  443.     * columns are grouped together. Returns this object for chaining purposes.
  444.     *
  445.     * @param boolean $Bool A boolean value indicating if the query should be distinct or not.
  446.     */
  447.    public function Distinct($Bool = TRUE) {
  448.       $this->_Distinct = (is_bool($Bool)) ? $Bool : TRUE;
  449.       return $this;
  450.    }
  451.  
  452.    /**
  453.     * Removes all data from a table.
  454.     *
  455.     * @param string $Table The table to empty.
  456.     */
  457.    public function EmptyTable($Table = '') {
  458.       if ($Table == '') {
  459.          if (!isset($this->_Froms[0]))
  460.             return FALSE;
  461.  
  462.          $Table = $this->_Froms[0];
  463.       } else {
  464.          $Table = $this->EscapeIdentifier($this->Database->DatabasePrefix.$Table);
  465.       }
  466.  
  467.  
  468.       $Sql = $this->GetDelete($Table);
  469.      
  470.       return $this->Query($Sql, 'delete');
  471.    }
  472.    
  473.    /**
  474.     * Closes off any open elements in the query before execution.
  475.     * Ideally, the programmer should have everything closed off so this method will do nothing.
  476.     */
  477.    protected function _EndQuery() {
  478.       // Close the where groups.
  479.       while($this->_WhereGroupCount > 0) {
  480.          $this->EndWhereGroup();
  481.       }
  482.    }
  483.    
  484.    /**
  485.     * End a bracketed group in the where clause.
  486.     * <b>Note</b>: If no items where added to the group then no barackets will appear in the final statement.
  487.     *
  488.     * @return Gdn_DatabaseDriver $this.
  489.     */
  490.    public function EndWhereGroup() {
  491.       if($this->_WhereGroupCount > 0) {
  492.          $WhereCount = count($this->_Wheres);
  493.          
  494.          if($this->_OpenWhereGroupCount >= $this->_WhereGroupCount)
  495.             $this->_OpenWhereGroupCount--;
  496.          else if($WhereCount > 0)
  497.             $this->_Wheres[$WhereCount-1] .= ')';
  498.            
  499.          $this->_WhereGroupCount--;
  500.       }  
  501.      
  502.       return $this;
  503.    }
  504.    
  505.    /**
  506.     * Takes a string formatted as an SQL field reference and escapes it for the defined database engine.
  507.     *
  508.     * @param string $RefExpr The reference expression to be escaped.
  509.     *   The reference should be in the form of alias.column.
  510.     */
  511.    protected function EscapeIdentifier($RefExpr) {
  512.       trigger_error(ErrorMessage('The selected database engine does not perform the requested task.', $this->ClassName, 'EscapeSql'), E_USER_ERROR);
  513.    }
  514.    
  515.    /**
  516.     * Takes a string of SQL and escapes it for the defined database engine.
  517.     * ie. adds backticks or any other database-specific formatting.
  518.     *
  519.     * @param mixed $String The string (or array of strings) of SQL to be escaped.
  520.     * @param boolean $FirstWordOnly A boolean value indicating if the first word should be escaped only.
  521.     */
  522.    protected function EscapeSql($String, $FirstWordOnly = FALSE) {
  523.       trigger_error(ErrorMessage('The selected database engine does not perform the requested task.', $this->ClassName, 'EscapeSql'), E_USER_ERROR);
  524.    }
  525.  
  526.    /**
  527.     * Returns a platform-specific query to fetch column data from $Table.
  528.     *
  529.     * @param string $Table The name of the table to fetch column data from.
  530.     */
  531.    public function FetchColumnSql($Table) {
  532.       trigger_error(ErrorMessage('The selected database engine does not perform the requested task.', $this->ClassName, 'FetchColumnSql'), E_USER_ERROR);
  533.    }
  534.  
  535.    /**
  536.     * Returns a platform-specific query to fetch table names.
  537.     * @param mixed $LimitToPrefix Whether or not to limit the search to tables with the database prefix or a specific table name. The following types can be given for this parameter:
  538.      *  - <b>TRUE</b>: The search will be limited to the database prefix.
  539.      *  - <b>FALSE</b>: All tables will be fetched. Default.
  540.      *  - <b>string</b>: The search will be limited to a like clause. The ':_' will be replaced with the database prefix.
  541.     */
  542.    public function FetchTableSql($LimitToPrefix = FALSE) {
  543.       trigger_error(ErrorMessage('The selected database engine does not perform the requested task.', $this->ClassName, 'FetchTableSql'), E_USER_ERROR);
  544.    }
  545.  
  546.    /**
  547.     * Returns an array containing table names in the database.
  548.     * @param mixed $LimitToPrefix Whether or not to limit the search to tables with the database prefix or a specific table name. The following types can be given for this parameter:
  549.      *  - <b>TRUE</b>: The search will be limited to the database prefix.
  550.      *  - <b>FALSE</b>: All tables will be fetched. Default.
  551.      *  - <b>string</b>: The search will be limited to a like clause. The ':_' will be replaced with the database prefix.
  552.     * @return array
  553.     */
  554.    public function FetchTables($LimitToPrefix = FALSE) {
  555.       $Sql = $this->FetchTableSql($LimitToPrefix);
  556.       $Data = $this->Query($Sql);
  557.       $Return = array();
  558.       foreach($Data->ResultArray() as $Row) {
  559.          if (isset($Row['TABLE_NAME']))
  560.             $Return[] = $Row['TABLE_NAME'];
  561.          else
  562.             $Return[] = array_shift($Row);
  563.       }
  564.  
  565.       return $Return;
  566.    }
  567.  
  568.    /**
  569.     * Returns an array of schema data objects for each field in the specified
  570.     * table. The returned array of objects contains the following properties:
  571.     * Name, PrimaryKey, Type, AllowNull, Default, Length, Enum.
  572.     *
  573.     * @param string $Table The name of the table to get schema data for.
  574.     */
  575.    public function FetchTableSchema($Table) {
  576.       trigger_error(ErrorMessage('The selected database engine does not perform the requested task.', $this->ClassName, 'FetchTableSchema'), E_USER_ERROR);
  577.    }
  578.  
  579.    /**
  580.     * Returns a string of SQL that retrieves the database engine version in the
  581.     * fieldname "version".
  582.     */
  583.    public function FetchVersionSql() {
  584.       trigger_error(ErrorMessage('The selected database engine does not perform the requested task.', $this->ClassName, 'FetchVersionSql'), E_USER_ERROR);
  585.    }
  586.  
  587.    /**
  588.     * Returns an array containing column names from $Table.
  589.     *
  590.     * @param string $Table The name of the table to fetch column data from.
  591.     */
  592.    public function FetchColumns($Table) {
  593.       $Sql = $this->FetchColumnSql($Table);
  594.       $Data = $this->Query($Sql);
  595.       $Return = array();
  596.       foreach($Data->ResultArray() as $Row) {
  597.          if (isset($Row['COLUMN_NAME']))
  598.             $Return[] = $Row['COLUMN_NAME'];
  599.          else
  600.             $Return[] = current($Row);
  601.       }
  602.  
  603.       return $Return;
  604.    }
  605.  
  606.    /**
  607.     * Takes a table name and makes sure it is formatted for this database
  608.     * engine.
  609.     *
  610.     * @param string $Table The name of the table name to format.
  611.     */
  612.    public function FormatTableName($Table) {
  613.       trigger_error(ErrorMessage('The selected database engine does not perform the requested task.', $this->ClassName, 'FormatTableName'), E_USER_ERROR);
  614.    }
  615.    
  616.    /**
  617.     * The table(s) from which to select values. Returns this object for
  618.     * chaining purposes.
  619.     *
  620.     * @param mixed $From A string or array of table names/aliases from which to select data.
  621.     * Accepted formats include:
  622.     *    user
  623.     *    user, user u2, role
  624.     *    array("user u", "user u2", "role")
  625.     **/
  626.    public function From($From) {
  627.       if (!is_array($From))
  628.          $From = array($From);
  629.  
  630.       $Count = count($From);
  631.       $i = 0;
  632.       for ($i = 0; $i < $Count; ++$i) {
  633.          $this->_Froms[] = $this->EscapeIdentifier($this->MapAliases($From[$i]));
  634.       }
  635.  
  636.       return $this;
  637.    }
  638.  
  639.    /**
  640.     * Returns a string of comma delimited table names to select from.
  641.     *
  642.     * @param mixed $Tables The name of a table (or an array of table names) to be added in the from
  643.     * clause of a query.
  644.     */
  645.    protected function _FromTables($Tables) {
  646.       return is_array($Tables) ? implode(', ', $Tables) : $Tables;
  647.    }
  648.  
  649.    /**
  650.     * Builds the select statement and runs the query, returning a result object.
  651.     *
  652.     * @param string $Table          The table from which to select data. Adds to the $this->_Froms collection.
  653.     * @param string $OrderFields    A string of fields to be ordered.
  654.     * @param string $OrderDirection The direction of the sort.
  655.     * @param int    $Limit          Adds a limit to the query.
  656.     * @param int    $PageNumber     The page of data to retrieve.
  657.     * @return Gdn_DataSet
  658.     */
  659.    public function Get($Table = '', $OrderFields = '', $OrderDirection = 'asc', $Limit = FALSE, $PageNumber = FALSE) {
  660.       if ($Table != '') {
  661.          //$this->MapAliases($Table);
  662.          $this->From($Table);
  663.       }
  664.  
  665.       if ($OrderFields != '')
  666.          $this->OrderBy($OrderFields, $OrderDirection);
  667.  
  668.       if ($Limit !== FALSE) {
  669.          if ($PageNumber == FALSE || $PageNumber < 1)
  670.             $PageNumber = 1;
  671.  
  672.          $Offset = ($PageNumber - 1) * $Limit;
  673.          $this->Limit($Limit, $Offset);
  674.       }
  675.  
  676.       $Result = $this->Query($this->GetSelect());
  677.       return $Result;
  678.    }
  679.    
  680.    /**
  681.     * A helper function for escaping sql identifiers.
  682.     * @param string The sql containing identifiers to escape in a different language.
  683.     *   All identifiers requiring escaping should be enclosed in back ticks (`).
  684.     * @return array All of the tokens in the sql. The tokens that require escaping will still have back ticks.
  685.     */
  686.    protected function _GetIdentifierTokens($Sql) {
  687.       $Tokens = preg_split('/`/', $Sql, -1, PREG_SPLIT_DELIM_CAPTURE);
  688.       $Result = array();
  689.      
  690.       $InIdent = FALSE;
  691.       $CurrentToken = '';
  692.       for($i = 0; $i < count($Tokens); $i++) {
  693.          $Token = $Tokens[i];
  694.          $Result .= $Token;
  695.          if($Token == '`') {
  696.             if($InIdent && $i < count($Tokens) - 1 && $Tokens[$i + 1] == '`') {
  697.                // This is an escaped back tick.
  698.                $i++; // skip next token
  699.             } else if($InIdent) {
  700.                $Result[] = $CurrentToken;
  701.                $CurrentToken = $CurrentToken;
  702.                $InIdent = false;
  703.             } else {
  704.                $InIdent = true;
  705.             }
  706.          } else if(!$InIdent) {
  707.             $Result[] = $CurrentToken;
  708.             $CurrentToken = '';
  709.          }
  710.       }
  711.      
  712.       return $Result;
  713.    }
  714.  
  715.    /**
  716.     * Returns the total number of records in the specified table.
  717.     *
  718.     * @param string $Table The table from which to count rows of data.
  719.     * @param mixed  $Where Adds to the $this->_Wheres collection using $this->Where();
  720.     */
  721.    public function GetCount($Table = '', $Where = FALSE) {
  722.       if($Table != '') {
  723.          //$this->MapAliases($Table);
  724.          $this->From($Table);
  725.       }
  726.  
  727.       if ($Where !== FALSE)
  728.          $this->Where($Where);
  729.  
  730.       $this->Select('*', 'count', 'RowCount'); // count * slow on innodb
  731.       $Sql = $this->GetSelect();
  732.       $Result = $this->Query($Sql);
  733.  
  734.       $CountData = $Result->FirstRow();
  735.       return $CountData->RowCount;
  736.    }
  737.  
  738.    /**
  739.     * Returns the total number of records in the specified table.
  740.     *
  741.     * @param string $Table The table from which to count rows of data.
  742.     * @param mixed  $Like  Adds to the $this->_Wheres collection using $this->Like();
  743.     */
  744.    public function GetCountLike($Table = '', $Like = FALSE) {
  745.       if ($Table != '') {
  746.          $this->MapAliases($Table);
  747.          $this->From($Table);
  748.       }
  749.  
  750.       if ($Like !== FALSE)
  751.          $this->Like($Like);
  752.  
  753.       $this->Select('*', 'count', 'RowCount');
  754.       $Result = $this->Query($this->GetSelect());
  755.  
  756.       $CountData = $Result->FirstRow();
  757.       return $CountData->RowCount;
  758.    }
  759.  
  760.    /**
  761.     * Returns a delete statement for the specified table and the supplied
  762.     * conditions.
  763.     *
  764.     * @param string $TableName The name of the table to delete from.
  765.     * @param array $Wheres An array of where conditions.
  766.     */
  767.    public function GetDelete($TableName, $Wheres = array()) {
  768.       trigger_error(ErrorMessage('The selected database engine does not perform the requested task.', $this->ClassName, 'GetDelete'), E_USER_ERROR);
  769.    }
  770.  
  771.    /**
  772.     * Returns an insert statement for the specified $Table with the provided $Data.
  773.     *
  774.     * @param string $Table The name of the table to insert data into.
  775.     * @param string $Data An associative array of FieldName => Value pairs that should be inserted
  776.     * $Table.
  777.     */
  778.    public function GetInsert($Table, $Data) {
  779.       trigger_error(ErrorMessage('The selected database engine does not perform the requested task.', $this->ClassName, 'GetInsert'), E_USER_ERROR);
  780.    }
  781.    
  782.    /**
  783.     * Adds a limit clause to the provided query for this database engine.
  784.     *
  785.     * @param string $Query The SQL string to which the limit statement should be appended.
  786.     * @param int $Limit The number of records to limit the query to.
  787.     * @param int $Offset The number of records to offset the query from.
  788.     */
  789.    public function GetLimit($Query, $Limit, $Offset) {
  790.       trigger_error(ErrorMessage('The selected database engine does not perform the requested task.', $this->ClassName, 'GetLimit'), E_USER_ERROR);
  791.    }
  792.  
  793.    /**
  794.     * Builds the select statement based on the various collections in this
  795.     * object. This method should not be called directly; it is called by
  796.     * $this->Get() and $this->GetWhere().
  797.     */
  798.    public function GetSelect() {
  799.       // Close off any open query elements.
  800.       $this->_EndQuery();
  801.      
  802.       $Sql = (!$this->_Distinct) ? 'select ' : 'select distinct ';
  803.  
  804.       // Don't escape the field if it is numeric or an asterisk (all columns)
  805.       $Selects = array();
  806.       foreach($this->_Selects as $Key => $Expr) {
  807.          $Field = $Expr['Field'];
  808.          $Function = $Expr['Function'];
  809.          $Alias = $Expr['Alias'];
  810.          $CaseOptions = ArrayValue('CaseOptions', $Expr);
  811.          if ($Field != '*' && !is_numeric($Field))
  812.             $Field = $this->EscapeIdentifier($Field);
  813.  
  814.          if ($Alias == '' && $Function != '')
  815.             $Alias = $Field;
  816.  
  817.          // if (in_array(strtolower($Function), array('max', 'min', 'avg', 'sum', 'count')))
  818.          if ($Function != '') {
  819.                 if(strpos($Function, '%s') !== FALSE)
  820.                     $Field = sprintf($Function, $Field);
  821.                 else
  822.                     $Field = $Function.'('.$Field.')';
  823.             }
  824.  
  825.          if ($CaseOptions !== FALSE)
  826.             $Field = 'case ' . $Field . $CaseOptions . ' end';
  827.  
  828.          if ($Alias != '')
  829.             $Field .= ' as '.$this->QuoteIdentifier($Alias);
  830.  
  831.          if ($Field != '')
  832.             $Selects[] = $Field;
  833.       }
  834.       $Sql .= (count($Selects) == 0) ? '*' : implode(', ', $Selects);
  835.  
  836.       if (count($this->_Froms) > 0)
  837.          $Sql .= "\nfrom ".$this->_FromTables($this->_Froms);
  838.  
  839.       if (count($this->_Joins) > 0) {
  840.          $Sql .= "\n";
  841.          $Sql .= implode("\n", $this->_Joins);
  842.       }
  843.  
  844.       if (count($this->_Wheres) > 0)
  845.          $Sql .= "\nwhere ";
  846.  
  847.       $Sql .= implode("\n", $this->_Wheres);
  848.  
  849.       // Close any where groups that were left open.
  850.       for ($i = 0; $i < $this->_OpenWhereGroupCount; ++$i) {
  851.          $Sql .= ')';
  852.       }
  853.       $this->_OpenWhereGroupCount = 0;
  854.  
  855.       if (count($this->_GroupBys) > 0) {
  856.          $Sql .= "\ngroup by ";
  857.  
  858.          // special consideration for table aliases
  859.          if (count($this->_AliasMap) > 0 && $this->Database->DatabasePrefix)
  860.             $Sql .= implode(', ', $this->_FilterTableAliases($this->_GroupBys));
  861.          else
  862.             $Sql .= implode(', ', $this->_GroupBys);
  863.       }
  864.  
  865.       if (count($this->_Havings) > 0)
  866.          $Sql .= "\nhaving ".implode("\n", $this->_Havings);
  867.  
  868.       if (count($this->_OrderBys) > 0)
  869.          $Sql .= "\norder by ".implode(', ', $this->_OrderBys);
  870.  
  871.       if (is_numeric($this->_Limit)) {
  872.          $Sql .= "\n";
  873.          $Sql = $this->GetLimit($Sql, $this->_Limit, $this->_Offset);
  874.       }
  875.  
  876.       return $Sql;
  877.    }
  878.  
  879.    /**
  880.     * Returns a truncate statement for this database engine.
  881.     *
  882.     * @param string $Table The name of the table to updated data in.
  883.     */
  884.    public function GetTruncate($Table) {
  885.       trigger_error(ErrorMessage('The selected database engine does not perform the requested task.', $this->ClassName, 'GetTruncate'), E_USER_ERROR);
  886.    }
  887.  
  888.    /**
  889.     * Returns an update statement for the specified table with the provided
  890.     * $Data.
  891.     *
  892.     * @param array $Tables The names of the tables to updated data in.
  893.     * @param array $Data An associative array of FieldName => Value pairs that should be inserted
  894.     * $Table.
  895.     * @param mixed $Where A where clause (or array containing multiple where clauses) to be applied
  896.     * to the where portion of the update statement.
  897.     */
  898.    public function GetUpdate($Tables, $Data, $Where) {
  899.       trigger_error(ErrorMessage('The selected database engine does not perform the requested task.', $this->ClassName, 'GetUpdate'), E_USER_ERROR);
  900.    }
  901.  
  902.    /**
  903.     * Builds the select statement and runs the query, returning a result
  904.     * object. Allows a where clause, limit, and offset to be added directly.
  905.     *
  906.     * @param string $Table The table from which to select data. Adds to the $this->_Froms collection.
  907.     * @param mixed  $Where Adds to the $this->_Wheres collection using $this->Where();
  908.     * @param string $OrderFields A string of fields to be ordered.
  909.     * @param string $OrderDirection The direction of the sort.
  910.     * @param int    $Limit The number of records to limit the query to.
  911.     * @param int    $PageNumber The offset where the query results should begin.
  912.     * @return Gdn_DataSet The data returned by the query.
  913.     */
  914.    public function GetWhere($Table = '', $Where = FALSE, $OrderFields = '', $OrderDirection = 'asc', $Limit = FALSE, $PageNumber = FALSE) {
  915.       if ($Table != '') {
  916.          //$this->MapAliases($Table);
  917.          $this->From($Table);
  918.       }
  919.  
  920.       if ($Where !== FALSE)
  921.          $this->Where($Where);
  922.  
  923.       if ($OrderFields != '')
  924.          $this->OrderBy($OrderFields, $OrderDirection);
  925.  
  926.       if ($Limit !== FALSE) {
  927.          if ($PageNumber == FALSE || $PageNumber < 1)
  928.             $PageNumber = 1;
  929.  
  930.          $Offset = ($PageNumber - 1) * $Limit;
  931.          $this->Limit($Limit, $Offset);
  932.       }
  933.  
  934.       $Result = $this->Query($this->GetSelect());
  935.      
  936.       return $Result;
  937.    }
  938.  
  939.    /**
  940.     * Builds the select statement and runs the query, returning a result
  941.     * object. Allows a like clause, limit, and offset to be added directly.
  942.     *
  943.     * @param string $Table The table from which to select data. Adds to the $this->_Froms collection.
  944.     * @param mixed  $Like Adds to the $this->_Wheres collection using $this->Like();
  945.     * @param string $OrderFields A string of fields to be ordered.
  946.     * @param string $OrderDirection The direction of the sort.
  947.     * @param int    $Limit The number of records to limit the query to.
  948.     * @param int    $PageNumber The offset where the query results should begin.
  949.     */
  950.    public function GetWhereLike($Table = '', $Like = FALSE, $OrderFields = '', $OrderDirection = 'asc', $Limit = FALSE, $PageNumber = FALSE) {
  951.       if ($Table != '') {
  952.          $this->MapAliases($Table);
  953.          $this->From($Table);
  954.       }
  955.  
  956.       if ($Like !== FALSE)
  957.          $this->Like($Like);
  958.  
  959.       if ($OrderFields != '')
  960.          $this->OrderBy($OrderFields, $OrderDirection);
  961.  
  962.       if ($Limit !== FALSE) {
  963.          if ($PageNumber == FALSE || $PageNumber < 1)
  964.             $PageNumber = 1;
  965.  
  966.          $Offset = ($PageNumber - 1) * $Limit;
  967.          $this->Limit($Limit, $Offset);
  968.       }
  969.  
  970.       $Result = $this->Query($this->GetSelect());
  971.      
  972.       return $Result;
  973.    }
  974.  
  975.    /**
  976.     * Adds to the $this->_GroupBys collection.
  977.     *
  978.     * @param mixed $Fields An array of field names (or a comma-delimited list of field names) to be
  979.     * grouped by.
  980.     */
  981.    public function GroupBy($Fields = NULL) {
  982.       if(is_null($Fields)) {
  983.          // Group by every item in the select that isn't a function.
  984.          foreach($this->_Selects as $Alias => $Select) {
  985.             if(ArrayValue('Function', $Select) == '') {
  986.                $this->_GroupBys[] = $Select['Field'];
  987.             }
  988.          }
  989.          return $this;
  990.       }
  991.      
  992.       if (is_string($Fields))
  993.          $Fields = explode(',', $Fields);
  994.  
  995.       foreach ($Fields as $Field) {
  996.          $Field = trim($Field);
  997.  
  998.          if ($Field != '')
  999.             $this->_GroupBys[] = $this->EscapeIdentifier($Field);
  1000.       }
  1001.       return $this;
  1002.    }
  1003.    
  1004.    /**
  1005.     * Adds to the $this->_Havings collection.
  1006.     *
  1007.     * This is the most basic having that adds a freeform string of text.
  1008.     * It should be used only in conjunction with methods that properly escape the sql.
  1009.     *
  1010.     * @param string $Sql The condition to add.
  1011.     * @return Gdn_DatabaseDriver $this
  1012.     */
  1013.    protected function _Having($Sql) {
  1014.       // Figure out the concatenation operator.
  1015.       $Concat = '';
  1016.      
  1017.       if(count($this->_Havings) > 0) {
  1018.          $Concat = ' ' . $this->_WhereConcat . ' ';
  1019.       }
  1020.      
  1021.       // Revert the concat back to 'and'.
  1022.       $this->_WhereConcat = $this->_WhereConcatDefault;
  1023.      
  1024.       $this->_Havings[] = $Concat . $Sql;
  1025.  
  1026.       return $this;
  1027.    }
  1028.    
  1029.    /**
  1030.     * Adds to the $this->_Havings collection. Called by $this->Having() and
  1031.     * $this->OrHaving().
  1032.     *
  1033.     * @param mixed $Field The name of the field (or array of field names) in the having clause.
  1034.     * @param string $Value The string on the right side of the having comparison.
  1035.     * @param boolean $EscapeSql A boolean value indicating if $this->EscapeSql method should be called
  1036.     * on $Field.
  1037.     * @param boolean $EscapeString A boolean value indicating if $this->EscapeString method should be called
  1038.     * on $Value.
  1039.     */
  1040.    function Having($Field, $Value = '', $EscapeField = TRUE, $EscapeValue = TRUE) {
  1041.       if (!is_array($Field))
  1042.          $Field = array($Field => $Value);
  1043.  
  1044.       foreach ($Field as $ChildField => $ChildValue) {
  1045.          $Expr = $this->ConditionExpr($ChildField, $ChildValue, $EscapeField, $EscapeValue);
  1046.          $this->_Having($Expr);
  1047.       }
  1048.  
  1049.       return $this;
  1050.    }
  1051.    
  1052.    public function History($UpdateFields = TRUE, $InsertFields = FALSE) {
  1053.       $UserID = GetValueR('User.UserID', Gdn::Session(), Gdn::Session()->UserID);
  1054.  
  1055.       if($InsertFields)
  1056.          $this->Set('DateInserted', Gdn_Format::ToDateTime())->Set('InsertUserID', $UserID);
  1057.       if($UpdateFields)
  1058.          $this->Set('DateUpdated', Gdn_Format::ToDateTime())->Set('UpdateUserID', $UserID);
  1059.       return $this;
  1060.    }
  1061.  
  1062.    /**
  1063.     * Returns the last identity to be inserted into the database at
  1064.     * $this->_Connection.
  1065.     */
  1066.    public function Identity() {
  1067.       return $this->Connection()->lastInsertId();
  1068.    }
  1069.  
  1070.    /**
  1071.     * Returns information about the database. Values include: Engine, Version,
  1072.     * DatabaseName.
  1073.     *
  1074.     * @param string $Request The piece of information being requested. Accepted values are: Engine,
  1075.     * Version, and DatabaseName.
  1076.     */
  1077.    public function Information($Request) {
  1078.       if (array_key_exists($Request, $this->_DatabaseInfo) === FALSE) {
  1079.          if ($Request == 'Version') {
  1080.             $this->_DatabaseInfo['Version'] = $this->Version();
  1081.          } else {
  1082.             $this->_DatabaseInfo['HostName'] = Gdn::Config('Database.Host', '');
  1083.             $this->_DatabaseInfo['DatabaseName'] = Gdn::Config('Database.Name', '');
  1084.          }
  1085.       }
  1086.       if (array_key_exists($Request, $this->_DatabaseInfo) === TRUE) {
  1087.          return $this->_DatabaseInfo[$Request];
  1088.       } else {
  1089.          return '';
  1090.       }
  1091.    }
  1092.    
  1093.    /**
  1094.     * Builds the insert statement and runs the query, returning a result
  1095.     * object.
  1096.     *
  1097.     * @param string $Table The table to which data should be inserted.
  1098.     * @param mixed  $Set An associative array (or object) of FieldName => Value pairs that should
  1099.     * be inserted, or an array of FieldName values that should have values
  1100.     * inserted from $Select.
  1101.     * @param string $Select A select query that will fill the FieldNames specified in $Set.
  1102.     */
  1103.    public function Insert($Table = '', $Set = NULL, $Select = '') {
  1104.       if (count($Set) == 0 && count($this->_Sets) == 0) {
  1105.          return FALSE;
  1106.       }
  1107.      
  1108.       if (!is_null($Set) && $Select == '' && !array_key_exists(0, $Set)) {
  1109.          $this->Set($Set);
  1110.          $Set = $this->_Sets;
  1111.       }
  1112.  
  1113.       if ($Table == '') {
  1114.          if (!isset($this->_Froms[0]))
  1115.             return FALSE;
  1116.  
  1117.          $Table = $this->_Froms[0];
  1118.       }
  1119.  
  1120.       $Sql = $this->GetInsert($this->EscapeIdentifier($this->Database->DatabasePrefix.$Table), $Set, $Select);
  1121.       $Result = $this->Query($Sql, 'insert');
  1122.      
  1123.       return $Result;
  1124.    }
  1125.    
  1126.    /**
  1127.     * Inserts or updates values in the table depending on whether they are already there.
  1128.     *
  1129.     * @param string $Table The name of the table to insert/update.
  1130.     * @param array $Set The columns to update.
  1131.     * @param array $Where The columns to find the row to update.
  1132.     * If a row is not found then one is inserted and the items in this array are merged with $Set.
  1133.     */
  1134.    public function Replace($Table = '', $Set = NULL, $Where, $CheckExisting = FALSE) {
  1135.       if(count($this->_Sets) > 0) {
  1136.          foreach($this->_Sets as $Key => $Value) {
  1137.             if(array_key_exists($Value, $this->_NamedParameters)) {
  1138.                $Set[$Key] = $this->_NamedParameters[$Value];
  1139.                unset($this->_NamedParameters[$Value]);
  1140.             } else {
  1141.                $Set[$Key] = $Value;
  1142.             }
  1143.          }
  1144.          $this->_Sets = array();
  1145.       }
  1146.      
  1147.       // Check to see if there is a row in the table like this.
  1148.       if ($CheckExisting) {
  1149.          $Row = $this->GetWhere($Table, $Where)->FirstRow(DATASET_TYPE_ARRAY);
  1150.  
  1151.          $Update = FALSE;
  1152.          if ($Row) {
  1153.             $Update = TRUE;
  1154.             foreach ($Set as $Key => $Value) {
  1155.                unset($Set[$Key]);
  1156.                $Key = trim($Key, '`');
  1157.                
  1158.                if (!$this->CaptureModifications && !array_key_exists($Key,$Row))
  1159.                   continue;
  1160.  
  1161.                if (in_array($Key, array('DateInserted', 'InsertUserID', 'DateUpdated', 'UpdateUserID')))
  1162.                   continue;
  1163.  
  1164.  
  1165.                // We are assuming here that if the existing record doesn't contain the column then it's just been added.
  1166.                if (preg_match('/^`(.+)`$/', $Value, $Matches)) {
  1167.                   if (!isset($Row[$Key]) || $Row[$Key] != $Row[$Matches[1]])
  1168.                      $this->Set('`'.$Key.'`', $Value, FALSE);
  1169.                } elseif (!isset($Row[$Key]) || $Row[$Key] != $Value) {
  1170.                   $this->Set('`'.$Key.'`', $Value);
  1171.                }
  1172.                
  1173.             }
  1174.             if (count($this->_Sets) == 0) {
  1175.                $this->Reset();
  1176.                return;
  1177.             }
  1178.          }
  1179.       } else {
  1180.          $Count = $this->GetCount($Table, $Where);
  1181.          $Update = $Count > 0;
  1182.       }
  1183.  
  1184.       if($Update) {
  1185.          // Update the table.
  1186.          $this->Put($Table, $Set, $Where);
  1187.       } else {
  1188.          // Insert the table.
  1189.          $Set = array_merge($Set, $Where);
  1190.          $this->Insert($Table, $Set);
  1191.       }
  1192.    }
  1193.    
  1194.    /**
  1195.     * The table(s) to which this query should join. Returns this object for
  1196.     * chaining purposes.
  1197.     *
  1198.     * @param string $TableName The name of a single table to join to.
  1199.     * @param string $On The conditions on which the join should occur.
  1200.     * ie. "user.role_id = role.id"
  1201.     * @param string $Join The type of join to be made. Accepted values are:
  1202.     * 'inner', 'outer', 'left', 'right', 'left outer', and 'right outer'.
  1203.     * @return Gdn_SQLDriver
  1204.     */
  1205.    public function Join($TableName, $On, $Join = '') {
  1206.       $Join = strtolower(trim($Join));
  1207.       if ($Join != '' && !in_array($Join, array('inner', 'outer', 'left', 'right', 'left outer', 'right outer'), TRUE))
  1208.          $Join = '';
  1209.          
  1210.       // Add the table prefix to any table specifications in the clause
  1211.       // echo '<div>'.$TableName.' ---> '.$this->EscapeSql($this->Database->DatabasePrefix.$TableName, TRUE).'</div>';
  1212.       if($this->Database->DatabasePrefix) {
  1213.          $TableName = $this->MapAliases($TableName);
  1214.  
  1215.          //$Aliases = array_keys($this->_AliasMap);
  1216.          //$Regex = '';
  1217.          //foreach ($Aliases as $Alias) {
  1218.          //   $Regex .= '(?<! '.$Alias.')';
  1219.          //}
  1220.          //$Regex = '/(\w+'.$Regex.'\.)/';
  1221.          //$On = preg_replace($Regex, $this->Database->DatabasePrefix.'$1', ' '.$On);
  1222.       }
  1223.       $JoinClause = ltrim($Join . ' join ') . $this->EscapeIdentifier($TableName, TRUE) . ' on ' . $On;
  1224.       $this->_Joins[]  = $JoinClause;
  1225.  
  1226.       return $this;
  1227.    }
  1228.    
  1229.    /**
  1230.     * A convenience method for Gdn_DatabaseDriver::Join that makes the join type 'left.'
  1231.     * @see Gdn_DatabaseDriver::Join()
  1232.     */
  1233.    public function LeftJoin($TableName, $On) {
  1234.       return $this->Join($TableName, $On, 'left');
  1235.    }
  1236.    
  1237.    /**
  1238.     * Adds to the $this->_Wheres collection. Used to generate the LIKE portion
  1239.     * of a query. Called by $this->Like(), $this->NotLike()
  1240.     *
  1241.     * @param mixed $Field The field name (or array of field name => match values) to search in for
  1242.     * a like $Match.
  1243.     * @param string $Match The value to try to match using a like statement in $Field.
  1244.     * @param string $Concat The concatenation operator for the items being added to the like in
  1245.     * clause.
  1246.     * @param string $Side A string indicating which side of the match to place asterisk operators.
  1247.     * Accepted values are left, right, both, none. Default is both.
  1248.     * @param string $Op Either 'like' or 'not like' clause.
  1249.     * @return Gdn_SQLDriver
  1250.     */
  1251.    public function Like($Field, $Match = '', $Side = 'both', $Op = 'like') {
  1252.       if (!is_array($Field))
  1253.          $Field = array($Field => $Match);
  1254.  
  1255.       foreach ($Field as $SubField => $SubValue) {
  1256.          $SubField .= ' ' . $Op . ' ';
  1257.          switch($Side) {
  1258.             case 'left':
  1259.                $SubValue = '%' . $SubValue;
  1260.                break;
  1261.             case 'right':
  1262.                $SubValue .= '%';
  1263.                break;
  1264.             case 'both':
  1265.                if(strlen($Match) == 0)
  1266.                   $SubValue = '%';
  1267.                else
  1268.                   $SubValue = '%' . $SubValue . '%';
  1269.                break;
  1270.          }
  1271.          $Expr = $this->ConditionExpr($SubField, $SubValue);
  1272.          $this->_Where($Expr);
  1273.       }
  1274.       return $this;
  1275.    }
  1276.  
  1277.    /**
  1278.     * Sets the limit (and offset optionally) for the query.
  1279.     *
  1280.     * @param int $Limit  The number of records to limit the query to.
  1281.     * @param int $Offset The offset where the query results should begin.
  1282.     */
  1283.    public function Limit($Limit, $Offset = FALSE) {
  1284.       $this->_Limit = $Limit;
  1285.  
  1286.       if ($Offset !== FALSE)
  1287.          $this->_Offset = $Offset;
  1288.  
  1289.       return $this;
  1290.    }
  1291.    
  1292.    /**
  1293.     * Takes a provided table specification and parses out any table aliases
  1294.     * provided, placing them in an alias mapping array. Returns the table
  1295.     * specification with any table prefix prepended.
  1296.     *
  1297.     * @param string $TableString The string specification of the table. ie.
  1298.     * "tbl_User as u" or "user u".
  1299.     * @return string
  1300.     */
  1301.    public function MapAliases($TableString) {
  1302.       // Make sure all tables have an alias.
  1303.       if(strpos($TableString, ' ') === FALSE) {
  1304.          $TableString .= ' ' . $TableString;
  1305.       }
  1306.      
  1307.       // Map the alias to the alias mapping array
  1308.       $TableString = trim(preg_replace('/\s+as\s+/i', ' ', $TableString));
  1309.       $Alias = strrchr($TableString, " ");
  1310.       $TableName = substr($TableString, 0, strlen($TableString) - strlen($Alias));
  1311.    
  1312.       // If no alias was specified then it will be set to the tablename.
  1313.       $Alias = trim($Alias);
  1314.       if(strlen($Alias) == 0) {
  1315.          $Alias = $TableName;
  1316.          $TableString .= ' ' . $Alias;
  1317.       }
  1318.      
  1319.       //$this->_AliasMap[$Alias] = $TableName;
  1320.  
  1321.       // Return the string with the database table prefix prepended
  1322.       return $this->Database->DatabasePrefix . $TableString;
  1323.    }
  1324.    
  1325.    /**
  1326.     * A convenience method for Gdn_DatabaseDriver::Like that changes the operator to 'not like.'
  1327.     * @see Gdn_DatabaseDriver::Like()
  1328.     */
  1329.    public function NotLike($Field, $Match = '', $Side = 'both') {
  1330.       return $this->Like($Field, $Match, $Side, 'not like');
  1331.    }
  1332.  
  1333.    /**
  1334.     * Takes a parameter name and makes sure it is cleaned up to be used as a
  1335.     * named parameter in a pdo prepared statement.
  1336.     * @param string $Name The name of the parameter to cleanup
  1337.     * @param boolean $CreateNew Wether or not this is a new or existing parameter.
  1338.     * @return string The cleaned up named parameter name.
  1339.     */
  1340.    public function NamedParameter($Name, $CreateNew = FALSE, $Value = NULL) {
  1341.       // Format the parameter name so it is safe for sql
  1342.       $NiceName = ':'.preg_replace('/([^\w\d_-])/', '', $Name); // Removes everything from the string except letters, numbers, dashes, and underscores
  1343.  
  1344.       if($CreateNew) {
  1345.          // Make sure that the new name doesn't already exist.
  1346.          $NumberedName = $NiceName;
  1347.          $i = 0;
  1348.          while (array_key_exists($NumberedName, $this->_NamedParameters)) {
  1349.             $NumberedName = $NiceName.$i;
  1350.             ++$i;
  1351.          }
  1352.          $NiceName = $NumberedName;
  1353.       }
  1354.      
  1355.       if(!is_null($Value)) {
  1356.          $this->_NamedParameters[$NiceName] = $Value;
  1357.       }
  1358.          
  1359.       return $NiceName;
  1360.    }
  1361.  
  1362.    public function &NamedParameters($NewValue = NULL) {
  1363.       if ($NewValue !== NULL) {
  1364.          $this->_NamedParameters = $NewValue;
  1365.       }
  1366.       $Result =& $this->_NamedParameters;
  1367.       return $Result;
  1368.    }
  1369.    
  1370.    /**
  1371.     * Allows a query to be called without resetting the object.
  1372.     * @param boolean $Reset Whether or not to reset this object when the next query executes.
  1373.     * @param boolean $OneTime Whether or not this will apply for only the next query or for all subsequent queries.
  1374.     */
  1375.    public function NoReset($NoReset = TRUE, $OneTime = TRUE) {
  1376.       $_NoReset = $NoReset ? ($OneTime ? 1 : 2) : 0;
  1377.       return $this;
  1378.    }
  1379.  
  1380.    /**
  1381.     * Sets the offset for the query.
  1382.     *
  1383.     * @param int $Offset The offset where the query results should begin.
  1384.     */
  1385.    public function Offset($Offset) {
  1386.       $this->_Offset = $Offset;
  1387.       return $this;
  1388.    }
  1389.  
  1390.    /**
  1391.     * Gets/sets an option on the object.
  1392.     *
  1393.     * @param string $Key The key of the option.
  1394.     * @param mixed $Value The value of the option or not specified just to get the current value.
  1395.     * @return mixed The value of the option or $this if $Value is specified.
  1396.     */
  1397.    public function Options($Key, $Value = NULL) {
  1398.       if ($Value !== NULL) {
  1399.          $this->_Options[$Key] = $Value;
  1400.          return $this;
  1401.       } elseif (isset($this->_Options[$Key]))
  1402.          return $this->_Options[$Key];
  1403.       else
  1404.          return NULL;
  1405.    }
  1406.  
  1407.    /**
  1408.     * Adds to the $this->_OrderBys collection.
  1409.     *
  1410.     * @param string $Fields A string of fields to be ordered.
  1411.     * @param string $Direction The direction of the sort.
  1412.     */
  1413.    public function OrderBy($Fields, $Direction = 'asc') {
  1414.       if ($Direction && $Direction != 'asc')
  1415.          $Direction = 'desc';
  1416.       else
  1417.          $Direction = 'asc';
  1418.  
  1419.       $this->_OrderBys[] = $this->EscapeIdentifier($Fields, TRUE).' '.$Direction;
  1420.       return $this;
  1421.    }
  1422.    
  1423.    /**
  1424.     * Adds to the $this->_Havings collection. Concatenates multiple calls with OR.
  1425.     *
  1426.     * @param mixed $Field The name of the field (or array of field names) in the having clause.
  1427.     * @param string $Value The string on the right side of the having comparison.
  1428.     * @param boolean $EscapeField A boolean value indicating if $this->EscapeSql method should be called
  1429.     * on $Field.
  1430.     * @param boolean $EscapeValue A boolean value indicating if $this->EscapeString method should be called
  1431.     * on $Value.
  1432.     * @return Gdn_DatabaseDriver $this.
  1433.     * @see Gdn_DatabaseDriver::Having()
  1434.     */
  1435.    function OrHaving($Field, $Value = '', $EscapeField = TRUE, $EscapeValue = TRUE) {
  1436.       return $this->OrOp()->Having($Field, $Value, $EscapeField, $EscapeValue);
  1437.    }
  1438.    
  1439.    /**
  1440.     * A convenience method that calls Gdn_DatabaseDriver::Like with concatenated with an 'or.'
  1441.     * @See Gdn_DatabaseDriver::Like()
  1442.     */
  1443.    public function OrLike($Field, $Match = '', $Side = 'both', $Op = 'like') {
  1444.       if (!is_array($Field))
  1445.          $Field = array($Field => $Match);
  1446.      
  1447.       foreach ($Field as $f => $v) {
  1448.          $this->OrOp()->Like($f, $v, $Side, $Op);
  1449.       }
  1450.       return $this;
  1451.  
  1452. //       return $this->OrOp()->Like($Field, $Match, $Side, $Op);
  1453.    }
  1454.    
  1455.    /** A convenience method for Gdn_DatabaseDriver::Like that changes the operator to 'not like,'
  1456.     *    and is concatenated with an 'or.'
  1457.     * @see Gdn_DatabaseDriver::NotLike()
  1458.     * @see GenricDriver::Like()
  1459.     */
  1460.    public function OrNotLike($Field, $Match = '', $Side = 'both') {
  1461.       return $this->OrLike($Field, $Match, $Side, 'not like');
  1462.    }
  1463.    
  1464.    /**
  1465.     * Concat the next where expression with an 'or' operator.
  1466.     *
  1467.     * @param boolean $SetDefault Whether or not the 'or' is one time, or will revert.
  1468.     * @return Gdn_DatabaseDriver $this
  1469.     * @see Gdn_DatabaseDriver::AndOp()
  1470.     */
  1471.    public function OrOp($SetDefault = FALSE) {
  1472.       $this->_WhereConcat = 'or';
  1473.       if($SetDefault) {
  1474.          $this->_WhereConcatDefault = 'or';
  1475.       }
  1476.      
  1477.       return $this;
  1478.    }
  1479.      
  1480.    /**
  1481.     * @link Gdn_DatabaseDriver::Where()
  1482.     */
  1483.    public function OrWhere($Field, $Value = NULL, $EscapeFieldSql = TRUE, $EscapeValueSql = TRUE) {
  1484.       return $this->OrOp()->Where($Field, $Value, $EscapeFieldSql, $EscapeValueSql);
  1485.    }
  1486.    
  1487.    /**
  1488.     * A convienience method for Gdn_DatabaseDriver::WhereExists() concatenates with an 'or.'
  1489.     * @see Gdn_DatabaseDriver::WhereExists()
  1490.     */
  1491.    public function OrWhereExists($SqlDriver, $Op = 'exists') {
  1492.       return $this->OrOp()->WhereExists($SqlDriver, $Op);
  1493.    }
  1494.    
  1495.    /**
  1496.     * @ling Gdn_DatabaseDriver::WhereIn()
  1497.     */
  1498.    public function OrWhereIn($Field, $Values) {
  1499.       return $this->OrOp()->WhereIn($Field, $Values);
  1500.    }
  1501.    
  1502.    /**
  1503.     * A convienience method for Gdn_DatabaseDriver::WhereExists() that changes the operator to 'not exists,'
  1504.     *   and concatenates with an 'or.'
  1505.     * @see Gdn_DatabaseDriver::WhereExists()
  1506.     * @see Gdn_DatabaseDriver::WhereNotExists()
  1507.     */
  1508.    public function OrWhereNotExists($SqlDriver) {
  1509.       return $this->OrWhereExists($SqlDriver, 'not exists');
  1510.    }
  1511.      
  1512.    /**
  1513.     * A convenience method for Gdn_DatabaseDriver::WhereIn() that changes the operator to 'not in,'
  1514.     *   and concatenates with an 'or.'
  1515.     * @see Gdn_DatabaseDriver::WhereIn()
  1516.     * @see Gdn_DatabaseDriver::WhereNotIn()
  1517.     */
  1518.    public function OrWhereNotIn($Field, $Values) {
  1519.       return $this->OrOp()->WhereNotIn($Field, $Values);
  1520.    }
  1521.    
  1522.    /**
  1523.     * Parses an expression for use in where clauses.
  1524.     *
  1525.     * @param string $Expr The expression to parse.
  1526.     * @param string $Name A name to give the parameter if $Expr becomes a named parameter.
  1527.     * @return string The parsed expression.
  1528.     */
  1529.    protected function _ParseExpr($Expr, $Name = NULL, $EscapeExpr = FALSE) {
  1530.       $Result = '';
  1531.      
  1532.       $C = substr($Expr, 0, 1);
  1533.      
  1534.       if($C === '=' && $EscapeExpr === FALSE) {
  1535.          // This is a function call. Each parameter has to be parsed.
  1536.          $FunctionArray = preg_split('/(\[[^\]]+\])/', substr($Expr, 1), -1, PREG_SPLIT_DELIM_CAPTURE);
  1537.          for($i = 0; $i < count($FunctionArray); $i++) {
  1538.             $Part = $FunctionArray[$i];
  1539.             if(substr($Part, 1) == '[') {
  1540.                // Translate the part of the function call.
  1541.                $Part = $this->_FieldExpr(substr($Part, 1, strlen($Part) - 2), $Name);
  1542.                $FunctionArray[$i] = $Part;
  1543.             }
  1544.          }
  1545.          // Combine the array back to the original function call.
  1546.          $Result = join($FunctionArray);
  1547.       } elseif($C === '@' && $EscapeExpr === FALSE) {
  1548.          // This is a literal. Don't do anything.
  1549.          $Result = substr($Expr, 1);
  1550.       } else {
  1551.          // This is a column reference.
  1552.          if(is_null($Name)) {
  1553.             $Result = $this->EscapeIdentifier($Expr);
  1554.          } else {
  1555.             // This is a named parameter.
  1556.            
  1557.             // Check to see if the named parameter is valid.
  1558.             if(in_array(substr($Expr, 0, 1), array('=', '@'))) {
  1559.                // The parameter has to be a default name.
  1560.                $Result = $this->NamedParameter('Param', TRUE);
  1561.             } else {
  1562.                $Result = $this->NamedParameter($Name, TRUE);
  1563.             }
  1564.             $this->_NamedParameters[$Result] = $Expr;
  1565.          }
  1566.       }
  1567.    
  1568.       return $Result;
  1569.    }
  1570.    
  1571.    /**
  1572.     * Joins the query to a permission junction table and limits the results accordingly.
  1573.     *
  1574.     * @param mixed $Permission The permission name (or array of names) to use when limiting the query.
  1575.     * @param string $ForeignAlias The alias of the table to join to (ie. Category).
  1576.     * @param string $ForeignColumn The primary key column name of $JunctionTable (ie. CategoryID).
  1577.      * @param string $JunctionTable
  1578.      * @param string $JunctionColumn
  1579.     */
  1580.    public function Permission($Permission, $ForeignAlias, $ForeignColumn, $JunctionTable = '', $JunctionColumn = '') {
  1581.       $PermissionModel = Gdn::PermissionModel();
  1582.       $PermissionModel->SQLPermission($this, $Permission, $ForeignAlias, $ForeignColumn, $JunctionTable, $JunctionColumn);
  1583.  
  1584.       return $this;
  1585.    }
  1586.  
  1587.    /**
  1588.     * Prefixes a table with the database prefix if it is not already there.
  1589.     *
  1590.     * @param string $Table The table name to prefix.
  1591.     */
  1592.    public function PrefixTable($Table) {
  1593.       $Prefix = $this->Database->DatabasePrefix;
  1594.      
  1595.       if ($Prefix != '' && substr($Table, 0, strlen($Prefix)) != $Prefix)
  1596.          $Table = $Prefix.$Table;
  1597.  
  1598.       return $Table;
  1599.    }
  1600.  
  1601.    /**
  1602.     * Builds the update statement and runs the query, returning a result object.
  1603.     *
  1604.     * @param string $Table The table to which data should be updated.
  1605.     * @param mixed  $Set An array of $FieldName => $Value pairs, or an object of $DataSet->Field
  1606.     * properties containing one rowset.
  1607.     * @param string $Where Adds to the $this->_Wheres collection using $this->Where();
  1608.     * @param int    $Limit Adds a limit to the query.
  1609.     */
  1610.    public function Put($Table = '', $Set = NULL, $Where = FALSE, $Limit = FALSE) {
  1611.       $this->Update($Table, $Set, $Where, $Limit);
  1612.  
  1613.       if (count($this->_Sets) == 0 || !isset($this->_Froms[0])) {
  1614.          $this->Reset();
  1615.          return FALSE;
  1616.       }
  1617.  
  1618.       $Sql = $this->GetUpdate($this->_Froms, $this->_Sets, $this->_Wheres, $this->_OrderBys, $this->_Limit);
  1619.       $Result = $this->Query($Sql, 'update');
  1620.  
  1621.       return $Result;
  1622.    }
  1623.    
  1624.    public function Query($Sql, $Type = 'select') {
  1625.       switch ($Type) {
  1626.          case 'insert': $ReturnType = 'ID'; break;
  1627.          case 'update': $ReturnType = NULL; break;
  1628.          default: $ReturnType = 'DataSet'; break;
  1629.       }
  1630.  
  1631.       $QueryOptions = array('ReturnType' => $ReturnType);
  1632.       if (!is_null($this->_CacheKey)) {
  1633.          $QueryOptions['Cache'] = $this->_CacheKey;
  1634.       }
  1635.      
  1636.       if (!is_null($this->_CacheKey))
  1637.          $QueryOptions['CacheOperation'] = $this->_CacheOperation;
  1638.      
  1639.       try {
  1640.          if ($this->CaptureModifications && strtolower($Type) != 'select') {
  1641.             if(!property_exists($this->Database, 'CapturedSql'))
  1642.                $this->Database->CapturedSql = array();
  1643.             $Sql2 = $this->ApplyParameters($Sql, $this->_NamedParameters);
  1644.            
  1645.             $this->Database->CapturedSql[] = $Sql2;
  1646.             $this->Reset();
  1647.             return TRUE;
  1648.          }
  1649.  
  1650.          $Result = $this->Database->Query($Sql, $this->_NamedParameters, $QueryOptions);
  1651.       } catch (Exception $Ex) {
  1652.          $this->Reset();
  1653.          throw $Ex;
  1654.       }
  1655.       $this->Reset();
  1656.      
  1657.       return $Result;
  1658.    }
  1659.    
  1660.    public function QuoteIdentifier($String) {
  1661.       return '`'.$String.'`';
  1662.    }
  1663.  
  1664.    /**
  1665.     * Resets properties of this object that relate to building a select
  1666.     * statement back to their default values. Called by $this->Get() and
  1667.     * $this->GetWhere().
  1668.     */
  1669.    public function Reset() {
  1670.       // Check the _NoReset flag.
  1671.       switch($this->_NoReset) {
  1672.          case 1:
  1673.             $this->_NoReset = 0;
  1674.             return;
  1675.          case 2:
  1676.             return;
  1677.       }
  1678.       $this->_Selects         = array();
  1679.       $this->_Froms           = array();
  1680.       $this->_Joins           = array();
  1681.       $this->_Wheres          = array();
  1682.       $this->_WhereConcat     = 'and';
  1683.       $this->_WhereConcatDefault = 'and';
  1684.       $this->_WhereGroupCount = 0;
  1685.       $this->_OpenWhereGroupCount = 0;
  1686.       $this->_GroupBys        = array();
  1687.       $this->_Havings         = array();
  1688.       $this->_OrderBys        = array();
  1689.       $this->_AliasMap        = array();
  1690.      
  1691.       $this->_CacheKey        = NULL;
  1692.       $this->_CacheOperation  = NULL;
  1693.       $this->_Distinct        = FALSE;
  1694.       $this->_Limit           = FALSE;
  1695.       $this->_Offset          = FALSE;
  1696.       $this->_Order           = FALSE;
  1697.      
  1698.       $this->_Sets            = array();
  1699.       $this->_NamedParameters = array();
  1700.       $this->_Options         = array();
  1701.    }
  1702.  
  1703.    /**
  1704.     * Allows the specification of columns to be selected in a database query.
  1705.     * Returns this object for chaining purposes. ie. $db->Select()->From();
  1706.     *
  1707.     * @param mixed $Select NotRequired "*" The field(s) being selected. It
  1708.     * can be a comma delimited string, the name of a single field, or an array
  1709.     * of field names.
  1710.     * @param string $Function NotRequired "" The aggregate function to be used on
  1711.     * the select column. Only valid if a single column name is provided.
  1712.     * Accepted values are MAX, MIN, AVG, SUM.
  1713.     * @param string $Alias NotRequired "" The alias to give a column name.
  1714.     * @return this
  1715.     */
  1716.    public function Select($Select = '*', $Function = '', $Alias = '') {
  1717.       if (is_string($Select)) {
  1718.          if ($Function == '')
  1719.             $Select = explode(',', $Select);
  1720.          else
  1721.             $Select = array($Select);
  1722.       }
  1723.       $Count = count($Select);
  1724.  
  1725.       $i = 0;
  1726.       for ($i = 0; $i < $Count; $i++) {
  1727.          $Field = trim($Select[$i]);
  1728.          
  1729.          // Try and figure out an alias for the field.
  1730.          if($Alias == '' || ($Count > 1 && $i > 0)) {
  1731.             if(preg_match('/^([^\s]+)\s+(?:as\s+)?`?([^`]+)`?$/', $Field, $Matches) > 0) {
  1732.                // This is an explicit alias in the select clause.
  1733.                $Field = $Matches[1];
  1734.                $Alias = $Matches[2];
  1735.             } elseif(preg_match('/^[^\.]+\.`?([^`]+)`?$/', $Field, $Matches) > 0) {
  1736.                // This is an alias from the field name.
  1737.                $Alias = $Matches[1];
  1738.             } else {
  1739.                $Alias = '';
  1740.             }
  1741.             // Make sure we aren't selecting * as an alias.
  1742.             if($Alias == '*')
  1743.                $Alias = '';
  1744.          }
  1745.          
  1746.          $Expr = array('Field' => $Field, 'Function' => $Function, 'Alias' => $Alias);
  1747.          
  1748.          if($Alias == '')
  1749.             $this->_Selects[] = $Expr;
  1750.          else
  1751.             $this->_Selects[$Alias] = $Expr;
  1752.       }
  1753.       return $this;
  1754.    }
  1755.  
  1756.    /**
  1757.     * Allows the specification of a case statement in the select list.
  1758.     *
  1759.     * @param string $Field The field being examined in the case statement.
  1760.     * @param array $Options The options and results in an associative array. A
  1761.     * blank key will be the final "else" option of the case statement. eg.
  1762.     * array('null' => 1, '' => 0) results in "when null then 1 else 0".
  1763.     * @param string $Alias The alias to give a column name.
  1764.     */
  1765.    public function SelectCase($Field, $Options, $Alias) {
  1766.       $CaseOptions = '';
  1767.       foreach ($Options as $Key => $Val) {
  1768.          if ($Key == '')
  1769.             $CaseOptions .= ' else ' . $Val;
  1770.          else
  1771.             $CaseOptions .= ' when ' . $Key . ' then ' . $Val;
  1772.       }
  1773.      
  1774.       $Expr = array('Field' => $Field, 'Function' => '', 'Alias' => $Alias, 'CaseOptions' => $CaseOptions);
  1775.      
  1776.       if($Alias == '')
  1777.          $this->_Selects[] = $Expr;
  1778.       else
  1779.          $this->_Selects[$Alias] = $Expr;
  1780.          
  1781.       return $this;
  1782.    }
  1783.  
  1784.    /**
  1785.     * Adds values to the $this->_Sets collection. Allows for the inserting
  1786.     * and updating of values to the db.
  1787.     *
  1788.     * @param mixed $Field The name of the field to save value as. Alternately this can be an array
  1789.     * of $FieldName => $Value pairs, or even an object of $DataSet->Field
  1790.     * properties containing one rowset.
  1791.     * @param string $Value The value to be set in $Field. Ignored if $Field was an array or object.
  1792.     * @param boolean $EscapeString A boolean value indicating if the $Value(s) should be escaped or not.
  1793.     * @param boolean $CreateNewNamedParameter A boolean value indicating that if (a) a named parameter is being
  1794.     * created, and (b) that name already exists in $this->_NamedParameters
  1795.     * collection, then a new one should be created rather than overwriting the
  1796.     * existing one.
  1797.     */
  1798.    public function Set($Field, $Value = '', $EscapeString = TRUE, $CreateNewNamedParameter = TRUE) {
  1799.       $Field = Gdn_Format::ObjectAsArray($Field);
  1800.  
  1801.       if (!is_array($Field))
  1802.          $Field = array($Field => $Value);
  1803.  
  1804.       foreach ($Field as $f => $v) {
  1805.          if (!is_object($v)) {
  1806.             if (!is_array($v))
  1807.                $v = array($v);
  1808.  
  1809.             foreach($v as $FunctionName => $Val) {
  1810.                if ($EscapeString === FALSE) {
  1811.                   if (is_string($FunctionName) !== FALSE) {
  1812.                      $this->_Sets[$this->EscapeIdentifier($f)] = $FunctionName.'('.$Val.')';
  1813.                   } else {
  1814.                      $this->_Sets[$this->EscapeIdentifier($f)] = $Val;
  1815.                   }
  1816.                } else {
  1817.                   $NamedParameter = $this->NamedParameter($f, $CreateNewNamedParameter);
  1818.                   $this->_NamedParameters[$NamedParameter] = $Val;
  1819.                   $this->_Sets[$this->EscapeIdentifier($f)] = is_string($FunctionName) !== FALSE ? $FunctionName.'('.$NamedParameter.')' : $NamedParameter;
  1820.                }
  1821.             }
  1822.          }
  1823.       }
  1824.  
  1825.       return $this;
  1826.    }
  1827.  
  1828.    /**
  1829.     * Sets the character encoding for this database engine.
  1830.     */
  1831.    public function SetEncoding($Encoding) {
  1832.       trigger_error(ErrorMessage('The selected database engine does not perform the requested task.', $this->ClassName, 'SetEncoding'), E_USER_ERROR);
  1833.    }
  1834.  
  1835.    /**
  1836.     * Similar to $this->Set() in every way except that if a named parameter is
  1837.     * used in place of $Value, it will overwrite any existing value associated
  1838.     * with that name as opposed to adding a new name/value (which is the
  1839.     * default way that $this->Set() works).
  1840.     *
  1841.     * @param mixed $Field The name of the field to save value as. Alternately this can be an array
  1842.     * of $FieldName => $Value pairs, or even an object of $DataSet->Field
  1843.     * properties containing one rowset.
  1844.     * @param string $Value The value to be set in $Field. Ignored if $Field was an array or object.
  1845.     * @param boolean $EscapeString A boolean value indicating if the $Value(s) should be escaped or not.
  1846.     */
  1847.    public function SetOverwrite($Field, $Value = '', $EscapeString = TRUE) {
  1848.       return $this->Set($Field, $Value, $EscapeString, FALSE);
  1849.    }
  1850.  
  1851.    /**
  1852.     * Truncates all data from a table (will delete from the table if database
  1853.     * does not support truncate).
  1854.     *
  1855.     * @param string $Table The table to truncate.
  1856.     */
  1857.    public function Truncate($Table = '') {
  1858.       if ($Table == '') {
  1859.          if (!isset($this->_Froms[0]))
  1860.             return FALSE;
  1861.  
  1862.          $Table = $this->_Froms[0];
  1863.       } else {
  1864.          $Table = $this->EscapeIdentifier($this->Database->DatabasePrefix . $Table);
  1865.       }
  1866.  
  1867.       $Sql = $this->GetTruncate($Table);
  1868.       $Result = $this->Query($Sql, 'truncate');
  1869.       return $Result;
  1870.    }
  1871.  
  1872.    /**
  1873.     * Allows the specification of a table to be updated in a database query.
  1874.     * Returns this object for chaining purposes. ie. $db->Update()->Join()->Set()->Where();
  1875.     *
  1876.     * @param string $Table The table to which data should be updated.
  1877.     * @param mixed  $Set An array of $FieldName => $Value pairs, or an object of $DataSet->Field
  1878.     * properties containing one rowset.
  1879.     * @param string $Where Adds to the $this->_Wheres collection using $this->Where();
  1880.     * @param int    $Limit Adds a limit to the query.
  1881.     */
  1882.    public function Update($Table, $Set = NULL, $Where = FALSE, $Limit = FALSE) {
  1883.         if($Table != '')
  1884.             $this->From($Table);
  1885.  
  1886.       if (!is_null($Set))
  1887.          $this->Set($Set);
  1888.  
  1889.       if ($Where !== FALSE)
  1890.          $this->Where($Where);
  1891.  
  1892.       if ($Limit !== FALSE)
  1893.          $this->Limit($Limit);
  1894.  
  1895.       return $this;
  1896.    }
  1897.  
  1898.    /**
  1899.     * Returns a plain-english string containing the version of the database engine.
  1900.     */
  1901.    public function Version() {
  1902.       $Query = $this->Query($this->FetchVersionSql());
  1903.       return $Query->Value('version');
  1904.    }
  1905.    
  1906.    /**
  1907.     * Adds to the $this->_Wheres collection. This is the most basic where that adds a freeform string of text.
  1908.     *   It should be used only in conjunction with methods that properly escape the sql.
  1909.     *   @param string $Sql The condition to add.
  1910.     *   @return GenricDriver $this
  1911.     */
  1912.    protected function _Where($Sql) {
  1913.       // Figure out the concatenation operator.
  1914.       $Concat = '';
  1915.      
  1916.       if(count($this->_Wheres) > 0) {
  1917.          $Concat = str_repeat(' ', $this->_WhereGroupCount + 1) . $this->_WhereConcat . ' ';
  1918.       }
  1919.      
  1920.        // Open the group(s) if necessary.
  1921.       while($this->_OpenWhereGroupCount > 0) {
  1922.          $Concat .= '(';
  1923.          $this->_OpenWhereGroupCount--;
  1924.       }
  1925.      
  1926.       // Revert the concat back to 'and'.
  1927.       $this->_WhereConcat = $this->_WhereConcatDefault;
  1928.      
  1929.       $this->_Wheres[] = $Concat . $Sql;
  1930.  
  1931.       return $this;
  1932.    }
  1933.  
  1934.    /**
  1935.     * Adds to the $this->_Wheres collection. Called by $this->Where() and $this->OrWhere();
  1936.     *
  1937.     * @param mixed $Field The string on the left side of the comparison, or an associative array of
  1938.     * Field => Value items to compare.
  1939.     * @param mixed $Value The string on the right side of the comparison. You can optionally
  1940.     * provide an array of DatabaseFunction => Value, which will be converted to
  1941.     * DatabaseFunction('Value'). If DatabaseFunction contains a '%s' then sprintf will be used for to place DatabaseFunction into the value.
  1942.     * @param boolean $EscapeFieldSql A boolean value indicating if $this->EscapeSql method should be called
  1943.     * on $Field.
  1944.     * @param boolean $EscapeValueString A boolean value indicating if $this->EscapeString method should be called
  1945.     * on $Value.
  1946.     */
  1947.    public function Where($Field, $Value = NULL, $EscapeFieldSql = TRUE, $EscapeValueSql = TRUE) {
  1948.       if (!is_array($Field))
  1949.          $Field = array($Field => $Value);
  1950.  
  1951.       foreach ($Field as $SubField => $SubValue) {
  1952.          if(is_array($SubValue) && isset($SubValue[0])) {
  1953.             $this->WhereIn($SubField, $SubValue);
  1954.         } else {
  1955.             $WhereExpr = $this->ConditionExpr($SubField, $SubValue, $EscapeFieldSql, $EscapeValueSql);
  1956.             if(strlen($WhereExpr) > 0) {
  1957.               $this->_Where($WhereExpr);
  1958.             }
  1959.         }
  1960.       }
  1961.       return $this;
  1962.    }
  1963.    
  1964.    /**
  1965.     * Adds to the $this->_WhereIns collection. Used to generate a "where field
  1966.     * in (1,2,3)" query. Called by $this->WhereIn(), $this->OrWhereIn(),
  1967.     * $this->WhereNotIn(), and $this->OrWhereNotIn().
  1968.     *
  1969.     * @param string  $Field The field to search in for $Values.
  1970.     * @param array   $Values An array of values to look for in $Field.
  1971.     * @param string $Op Either 'in' or 'not in' for the respective operation.
  1972.     * @param string $Escape Whether or not to escape the items in $Values.
  1973.     * clause.
  1974.     */
  1975.    public function _WhereIn($Field, $Values, $Op = 'in', $Escape = TRUE) {
  1976.       if (is_null($Field) || !is_array($Values))
  1977.          return;
  1978.      
  1979.       $FieldExpr = $this->_ParseExpr($Field);
  1980.          
  1981.       // Build up the in clause.
  1982.       $In = array();
  1983.       foreach ($Values as $Value) {
  1984.          if ($Escape)
  1985.             $ValueExpr = $this->Database->Connection()->quote($Value);
  1986.          else
  1987.             $ValueExpr = (string)$Value;
  1988.  
  1989.          if(strlen($ValueExpr) > 0)
  1990.             $In[] = $ValueExpr;
  1991.       }
  1992.       if(count($In) > 0)
  1993.          $InExpr = '(' . implode(', ', $In) . ')';
  1994.       else
  1995.          $InExpr = '(null)';
  1996.      
  1997.       // Set the final expression.
  1998.       $Expr = $FieldExpr . ' ' . $Op . ' ' . $InExpr;
  1999.       $this->_Where($Expr);
  2000.            
  2001.       return $this;
  2002.    }
  2003.  
  2004.    /**
  2005.     * Adds to the $this->_WhereIns collection. Used to generate a "where field
  2006.     * in (1,2,3)" query. Concatenated with AND.
  2007.     *
  2008.     * @param string $Field  The field to search in for $Values.
  2009.     * @param array  $Values An array of values to look for in $Field.
  2010.     */
  2011.    public function WhereIn($Field, $Values, $Escape = TRUE) {
  2012.       return $this->_WhereIn($Field, $Values, 'in', $Escape);
  2013.    }
  2014.  
  2015.    /**
  2016.     * A convenience method for Gdn_DatabaseDriver::WhereIn() that changes the operator to 'not in.'
  2017.     * @see Gdn_DatabaseDriver::WhereIn()
  2018.     */
  2019.    public function WhereNotIn($Field, $Values, $Escape = TRUE) {
  2020.       return $this->_WhereIn($Field, $Values, 'not in', $Escape);
  2021.    }
  2022.  
  2023.    /**
  2024.     * Adds an Sql exists expression to the $this->_Wheres collection.
  2025.     * @param Gdn_DatabaseDriver $SqlDriver The sql to add.
  2026.     * @param string $Op Either 'exists' or 'not exists'
  2027.     * @return Gdn_DatabaseDriver $this
  2028.     */
  2029.    public function WhereExists($SqlDriver, $Op = 'exists') {
  2030.       $Sql = $Op . " (\r\n" . $SqlDriver->GetSelect() . "\n)";
  2031.      
  2032.       // Add the inner select.
  2033.       $this->_Where($Sql);
  2034.      
  2035.       // Add the named parameters from the inner select to this statement.
  2036.       foreach($SqlDriver->_NamedParameters as $Name => $Value) {
  2037.          $this->_NamedParameters[$Name] = $Value;
  2038.       }
  2039.      
  2040.       return $this;
  2041.    }
  2042.  
  2043.    /**
  2044.     * A convienience method for Gdn_DatabaseDriver::WhereExists() that changes the operator to 'not exists'.
  2045.     * @see Gdn_DatabaseDriver::WhereExists()
  2046.     */
  2047.    public function WhereNotExists($SqlDriver) {
  2048.       return $this->WhereExists(@SqlDriver, 'not exists');
  2049.    }
  2050. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement