root/fStatement.php

Revision 895, 23.5 kB (checked in by wbond, 2 weeks ago)

Changed fStatement to be more aggressive about saving memory with the mysqli extension, fixed fUnbufferedResult to work with mysqli prepared statements

LineHide Line Numbers
1 <?php
2 /**
3  * Representation of a prepared statement for use with the fDatabase class
4  *
5  * @copyright  Copyright (c) 2010 Will Bond
6  * @author     Will Bond [wb] <will@flourishlib.com>
7  * @license    http://flourishlib.com/license
8  *
9  * @package    Flourish
10  * @link       http://flourishlib.com/fStatement
11  *
12  * @version    1.0.0b5
13  * @changes    1.0.0b5  Fixed an edge case where the mysqli extension would leak memory when fetching a `TEXT` or `BLOB` column [wb, 2010-08-28]
14  * @changes    1.0.0b4  Updated class to use fCore::startErrorCapture() instead of `error_reporting()` [wb, 2010-08-09]
15  * @changes    1.0.0b3  Backwards Compatibility Break - removed ODBC support. Fixed UTF-8 support for the `pdo_dblib` extension. [wb, 2010-07-31]
16  * @changes    1.0.0b2  Added IBM DB2 support [wb, 2010-04-13]
17  * @changes    1.0.0b   The initial implementation [wb, 2010-03-02]
18  */
19 class fStatement
20 {    
21     /**
22     * This holds references for params in sqlsrv prepared statements
23     *
24     * @var string
25     */
26     private $bound_params = array();
27    
28     /**
29     * The database the statement was created from
30     *
31     * @var fDatabase
32     */
33     private $database = NULL;
34    
35     /**
36     * The identifier for this statement - only used by the pgsql extension
37     *
38     * @var string
39     */
40     private $identifier = NULL;
41    
42     /**
43     * The data type placeholders
44     *
45     * @var array
46     */
47     private $placeholders = array();
48    
49     /**
50     * The statement object
51     *
52     * @var mixed
53     */
54     private $statement = NULL;
55    
56     /**
57     * The SQL the statement was created from
58     *
59     * @var string
60     */
61     private $sql = NULL;
62    
63     /**
64     * The original SQL, if it was translated
65     *
66     * @var string
67     */
68     private $untranslated_sql = NULL;
69    
70     /**
71     * If the statement has been used yet
72     *
73     * @var boolean
74     */
75     private $used = FALSE;
76    
77    
78     /**
79     * Sets up a prepared statement
80     *
81     * @internal
82      *
83     * @param  fDatabase $database            The database object this result set was created from
84     * @param  string    $query               MSSQL only: the character set to transcode from since MSSQL doesn't do UTF-8
85     * @param  array     $placeholders        The data type placeholders
86     * @param  string    $untranslated_query  If the SQL was translated - only relevant for Oracle
87     * @return fResult
88     */
89     public function __construct($database, $query, $placeholders, $untranslated_sql)
90     {
91         if (!$database instanceof fDatabase) {
92             throw new fProgrammerException(
93                 'The database object provided does not appear to be a descendant of fDatabase'
94             );
95         }
96        
97         $this->database         = $database;
98         $this->placeholders     = $placeholders;
99         $this->sql              = vsprintf($query, $placeholders);
100         $this->untranslated_sql = $untranslated_sql;
101        
102         $extension = $this->database->getExtension();
103         if ($extension == 'pdo' && $this->database->getType() == 'mssql') {
104             $extension = 'pdo_dblib';
105         }
106        
107         switch ($extension) {
108             // These database extensions don't have prepared statements
109             case 'mssql':
110             case 'mysql':
111             case 'pdo_dblib':
112             case 'sqlite':
113                 break;
114                
115             case 'oci8':   
116                 $named_placeholders = array();
117                 for ($i = 1; $i <= sizeof($placeholders); $i++) {
118                     $named_placeholders[] = ':p' . $i;   
119                 }
120                 $query = vsprintf($query, $named_placeholders);
121                 break;
122                
123             case 'ibm_db2':
124             case 'mysqli':
125             case 'pdo':
126             case 'sqlsrv':
127                 $question_marks = array();
128                 if (sizeof($placeholders)) {
129                     $question_marks = array_fill(0, sizeof($placeholders), '?');   
130                 }
131                 $query = vsprintf($query, $question_marks);
132                 break;
133                
134             case 'pgsql':
135                 $dollar_placeholders = array();
136                 for ($i = 1; $i <= sizeof($placeholders); $i++) {
137                     $dollar_placeholders[] = '$' . $i;   
138                 }
139                 $query = vsprintf($query, $dollar_placeholders);
140                 break;
141         }
142        
143         $connection = $this->database->getConnection();
144        
145         fCore::startErrorCapture(E_WARNING);
146        
147         switch ($extension) {
148             // These database extensions don't have prepared statements
149             case 'mssql':
150             case 'mysql':
151             case 'pdo_dblib':
152             case 'sqlite':
153                 $statement = $query;
154                 break;
155                
156             case 'ibm_db2':
157                 $statement = db2_prepare($connection, $query, array('cursor' => DB2_FORWARD_ONLY));
158                 break;
159            
160             case 'mysqli':
161                 $statement = mysqli_prepare($connection, $query);
162                 break;
163                
164             case 'oci8':   
165                 $statement = oci_parse($connection, $query);
166                 break;
167                
168             case 'pdo':
169                 $statement = $connection->prepare($query);
170                 break;
171                
172             case 'pgsql':
173                 static $statement_number = 0;
174                 $statement_number++;
175                 $this->identifier = 'fstmt' . $statement_number;
176                 $statement = pg_prepare($connection, $this->identifier, $query);
177                 break;
178                
179             case 'sqlsrv':
180                 $params = array();
181                 for ($i = 0; $i < sizeof($placeholders); $i++) {
182                     if ($placeholders[$i] == '%s') {
183                         $this->bound_params[$i] = array(
184                             NULL,
185                             SQLSRV_PARAM_IN,
186                             SQLSRV_PHPTYPE_STRING('UTF-8')
187                         );   
188                     } else {
189                         $this->bound_params[$i] = array(NULL);
190                     }
191                     $params[$i] =& $this->bound_params[$i];
192                 }
193                 $statement = sqlsrv_prepare($connection, $query, $params);
194                 break;
195         }
196        
197         fCore::stopErrorCapture();
198        
199         if (!$statement) {
200             switch ($extension) {
201                 case 'ibm_db2':
202                     $message = db2_stmt_errormsg($statement);
203                     break;
204                
205                 case 'mysqli':
206                     $message = mysqli_error($connection);
207                     break;
208                    
209                 case 'oci8':
210                     $error_info = oci_error($statement);
211                     $message = $error_info['message'];
212                     break;
213                    
214                 case 'pgsql':
215                     $message = pg_last_error($connection);
216                     break;
217                
218                 case 'sqlsrv':
219                     $error_info = sqlsrv_errors(SQLSRV_ERR_ALL);
220                     $message = $error_info[0]['message'];
221                     break;
222                    
223                 case 'pdo':
224                     $error_info = $connection->errorInfo();
225                     $message = $error_info[2];
226                     break;
227             }
228            
229             $db_type_map = array(
230                 'db2'        => 'DB2',
231                 'mssql'      => 'MSSQL',
232                 'mysql'      => 'MySQL',
233                 'oracle'     => 'Oracle',
234                 'postgresql' => 'PostgreSQL',
235                 'sqlite'     => 'SQLite'
236             );
237            
238             throw new fSQLException(
239                 '%1$s error (%2$s) in %3$s',
240                 $db_type_map[$this->database->getType()],
241                 $message,
242                 $this->sql
243             );   
244         }
245        
246         $this->statement = $statement;
247     }
248    
249    
250     /**
251     * Frees up the result object to save memory
252     *
253     * @internal
254      *
255     * @return void
256     */
257     public function __destruct()
258     {
259         if (!$this->statement) {
260             return;   
261         }
262        
263         $extension = $this->database->getExtension();
264         if ($extension == 'pdo' && $this->database->getType() == 'mssql') {
265             $extension = 'pdo_dblib';
266         }
267        
268         switch ($extension) {
269             case 'ibm_db2':
270                 db2_free_stmt($this->statement);
271                 break;
272                
273             case 'pdo':
274                 $this->statement->closeCursor();
275                 break;
276                
277             case 'oci8':
278                 oci_free_statement($this->statement);
279                 break;
280                
281             case 'sqlsrv':
282                 sqlsrv_free_stmt($this->statement);
283                 break;
284         }
285        
286         unset($this->statement);
287     }
288    
289    
290     /**
291     * All requests that hit this method should be requests for callbacks
292     *
293     * @internal
294      *
295     * @param  string $method  The method to create a callback for
296     * @return callback  The callback for the method requested
297     */
298     public function __get($method)
299     {
300         return array($this, $method);       
301     }
302    
303    
304     /**
305     * Executes the statement without returning a result
306     *
307     * @internal
308      *
309     * @param  array   $params     The parameters for the statement
310     * @param  mixed   &$extra     A variable to place extra information needed by some database extensions
311     * @param  boolean $different  If this statement is different than the last statement run on the fDatabase instance
312     * @return mixed  The (usually boolean) result of the extension function/method call
313     */
314     public function execute($params, &$extra, $different)
315     {
316         if ($different && $this->used) {
317             $this->regenerateStatement();
318         }
319         $this->used = TRUE;
320        
321         $extension = $this->database->getExtension();
322         if ($extension == 'pdo' && $this->database->getType() == 'mssql') {
323             $extension = 'pdo_dblib';
324         }
325        
326         $connection = $this->database->getConnection();
327         $statement  = $this->statement;
328        
329         $params = $this->prepareParams($params);
330                
331         switch ($extension) {
332             case 'ibm_db2':
333                 $extra  = $statement;
334                 $result = db2_execute($statement, $params);
335                 break;
336                
337             case 'mssql':
338                 $result = mssql_query($this->database->escape($statement, $params), $connection);
339                 break;
340                
341             case 'mysql':
342                 $result = mysql_unbuffered_query($this->database->escape($statement, $params), $connection);
343                 break;
344                
345             case 'mysqli':
346                 $result = mysqli_stmt_execute($statement);
347                 break;
348                
349             case 'oci8':
350                 $result = oci_execute($statement, $this->database->isInsideTransaction() ? OCI_DEFAULT : OCI_COMMIT_ON_SUCCESS);
351                 $extra  = $this->statement;
352                 break;
353                
354             case 'pgsql':
355                 $result = pg_execute($connection, $this->identifier, $params);
356                 break;
357                
358             case 'sqlite':
359                 $result = sqlite_exec($connection, $this->database->escape($statement, $params), $extra);
360                 break;
361                
362             case 'sqlsrv':
363                 $result = sqlsrv_execute($this->statement);
364                 break;
365                
366             case 'pdo':
367                 $extra  = $statement;
368                 $result = $statement->execute();
369                 break;
370            
371             case 'pdo_dblib':
372                 $sql = $this->database->escape($statement, $params);
373                 if (!fCore::checkOS('windows')) {
374                     $result = $connection->query($sql);
375                     if ($result instanceof PDOStatement) {
376                         $extra = $result;
377                         $result->closeCursor();
378                         $result = TRUE;
379                     } else {
380                         $result = FALSE;
381                     }
382                 } else {
383                     $result = $connection->exec($sql);
384                 }
385                 break;
386         }
387        
388         return $result;
389     }
390    
391    
392     /**
393     * Executes the statement in buffered mode
394     *
395     * @internal
396      *
397     * @param  fResult $result     The object to place the result into
398     * @param  array   $params     The parameters for the statement
399     * @param  mixed   &$extra     A variable to place extra information needed by some database extensions
400     * @param  boolean $different  If this statement is different than the last statement run on the fDatabase instance
401     * @return void
402     */
403     public function executeQuery($result, $params, &$extra, $different)
404     {
405         if ($different && $this->used) {
406             $this->regenerateStatement();
407         }
408         $this->used = TRUE;
409        
410         $extension = $this->database->getExtension();
411         if ($extension == 'pdo' && $this->database->getType() == 'mssql') {
412             $extension = 'pdo_dblib';
413         }
414        
415         $connection = $this->database->getConnection();
416         $statement  = $this->statement;
417        
418         $params = $this->prepareParams($params);
419                
420         switch ($extension) {
421             case 'ibm_db2':
422                 $extra = $statement;
423                 if (db2_execute($statement, $params)) {
424                     $rows = array();
425                     while ($row = db2_fetch_assoc($statement)) {
426                         $rows[] = $row;
427                     }
428                     $result->setResult($rows);
429                     unset($rows);
430                 } else {
431                     $result->setResult(FALSE);
432                 }
433                 break;
434                
435             case 'mssql':
436                 $result->setResult(mssql_query($this->database->escape($statement, $params), $connection));
437                 break;
438            
439             case 'mysql':
440                 $result->setResult(mysql_query($this->database->escape($statement, $params), $connection));
441                 break;
442            
443             case 'mysqli':
444                 $extra = $this->statement;
445                 if ($statement->execute()) {
446                     $statement->store_result();
447                     $rows = array();
448                     $meta = $statement->result_metadata();
449                     if ($meta) {
450                         $row_references = array();
451                         while ($field = $meta->fetch_field()) {
452                             $row_references[] = &$row[$field->name];
453                         }
454  
455                         call_user_func_array(array($statement, 'bind_result'), $row_references);
456                        
457                         while ($statement->fetch()) {
458                             $copied_row = array();
459                             foreach($row as $key => $val)
460                             {
461                                 $copied_row[$key] = $val;
462                             }
463                             $rows[] = $copied_row;
464                         }
465                         unset($row_references);
466                         $meta->free_result();
467                     }
468                     $result->setResult($rows);
469                     $statement->free_result();
470                 } else {
471                     $result->setResult(FALSE);   
472                 }
473                 break;
474            
475             case 'oci8':
476                 $extra = $this->statement;
477                 if (oci_execute($extra, $this->database->isInsideTransaction() ? OCI_DEFAULT : OCI_COMMIT_ON_SUCCESS)) {
478                     oci_fetch_all($extra, $rows, 0, -1, OCI_FETCHSTATEMENT_BY_ROW + OCI_ASSOC);
479                     $result->setResult($rows);
480                     unset($rows);   
481                 } else {
482                     $result->setResult(FALSE);
483                 }
484                 break;
485            
486             case 'pgsql':
487                 $result->setResult(pg_execute($connection, $this->identifier, $params));
488                 break;
489            
490             case 'sqlite':
491                 $result->setResult(sqlite_query($connection, $this->database->escape($statement, $params), SQLITE_ASSOC, $extra));
492                 break;
493            
494             case 'sqlsrv':
495                 $extra = $statement;
496                 if (sqlsrv_execute($statement)) {
497                     $rows = array();
498                     while ($row = sqlsrv_fetch_array($statement, SQLSRV_FETCH_ASSOC)) {
499                         $rows[] = $row;
500                     }
501                     $result->setResult($rows);
502                     unset($rows);
503                 } else {
504                     $result->setResult(FALSE);
505                 }
506                 break;
507            
508             case 'pdo':
509                 $extra = $this->statement;
510                 if (preg_match('#^\s*CREATE(\s+OR\s+REPLACE)?\s+TRIGGER#i', $result->getSQL())) {
511                     $extra->execute();
512                     $returned_rows = array();
513                 } else {
514                     if (!$extra->execute()) {
515                         $returned_rows = FALSE;
516                     } else {
517                         // This fixes a segfault issue with blobs and fetchAll() for pdo_ibm
518                         if ($this->database->getType() == 'db2') {
519                             $returned_rows = array();
520                             $scanned_for_blobs = FALSE;
521                             $blob_columns = array();
522                             while (($row = $extra->fetch(PDO::FETCH_ASSOC)) !== FALSE) {
523                                 if (!$scanned_for_blobs) {
524                                     foreach ($row as $key => $value) {
525                                         if (is_resource($value)) {
526                                             $blob_columns[] = $key;
527                                         }
528                                     }
529                                 }
530                                 foreach ($blob_columns as $blob_column) {
531                                     $row[$blob_column] = stream_get_contents($row[$blob_column]);
532                                 }
533                                 $returned_rows[] = $row;
534                             }
535                         } else {
536                             $returned_rows = $extra->fetchAll(PDO::FETCH_ASSOC);
537                         }
538                        
539                         // The pdo_pgsql driver likes to return empty rows equal to the number of affected rows for insert and deletes
540                         if ($this->database->getType() == 'postgresql' && $returned_rows && $returned_rows[0] == array()) {
541                             $returned_rows = array();         
542                         }   
543                     }
544                 }
545                
546                 $result->setResult($returned_rows);
547                 break;
548            
549             case 'pdo_dblib':
550                 $extra = $connection->query($this->database->escape($statement, $params));
551                 $returned_rows = (is_object($extra)) ? $extra->fetchAll(PDO::FETCH_ASSOC) : $extra;
552                 $result->setResult($returned_rows);
553                 break;
554         }
555        
556         return $result;       
557     }
558    
559    
560     /**
561     * Executes the statement in unbuffered mode (if possible)
562     *
563     * @internal
564      *
565     * @param  fUnbufferedResult $result     The object to place the result into
566     * @param  array             $params     The parameters for the statement
567     * @param  mixed             &$extra     A variable to place extra information needed by some database extensions
568     * @param  boolean           $different  If this statement is different than the last statement run on the fDatabase instance
569     * @return void
570     */
571     public function executeUnbufferedQuery($result, $params, &$extra, $different)
572     {
573         if ($different && $this->used) {
574             $this->regenerateStatement();
575         }
576         $this->used = TRUE;
577        
578         $extension = $this->database->getExtension();
579         if ($extension == 'pdo' && $this->database->getType() == 'mssql') {
580             $extension = 'pdo_dblib';
581         }
582        
583         $connection = $this->database->getConnection();
584         $statement  = $this->statement;
585        
586         $params = $this->prepareParams($params);
587        
588         // For the extensions that require the statement be passed to the result
589         // object, we store it in a stdClass object so the result object knows
590         // not to free it when done
591         $statement_holder = new stdClass;
592         $statement_holder->statement = NULL;
593                
594         switch ($extension) {
595             case 'ibm_db2':
596                 $extra = $statement;
597                 if (db2_execute($statement, $params)) {
598                     $statement_holder->statement = $statement;
599                 } else {
600                     $result->setResult(FALSE);
601                 }
602                 break;
603            
604             case 'mssql':
605                 $result->setResult(mssql_query($result->getSQL(), $this->connection, 20));
606                 break;
607            
608             case 'mysql':
609                 $result->setResult(mysql_unbuffered_query($result->getSQL(), $this->connection));
610                 break;
611            
612             case 'mysqli':
613                 $extra = $this->statement;
614                 if ($statement->execute()) {
615                     $statement_holder->statement = $statement;
616                 } else {
617                     $result->setResult(FALSE);   
618                 }
619                 break;
620            
621             case 'oci8':
622                 $result->setResult(oci_execute($statement, $this->database->isInsideTransaction() ? OCI_DEFAULT : OCI_COMMIT_ON_SUCCESS));
623                 break;
624            
625             case 'pgsql':
626                 $result->setResult(pg_execute($connection, $this->identifier, $params));
627                 break;
628            
629             case 'sqlite':
630                 $result->setResult(sqlite_unbuffered_query($connection, $this->database->escape($statement, $params), SQLITE_ASSOC, $extra));
631                 break;
632            
633             case 'sqlsrv':
634                 $extra = sqlsrv_execute($statement);
635                 if ($extra) {
636                     $statement_holder->statement = $statement;
637                 } else {
638                     $result->setResult($extra);
639                 }
640                 break;
641            
642             case 'pdo':
643                 $extra = $statement->execute();
644                 if ($extra) {
645                     $result->setResult($statement);
646                 } else {
647                     $result->setResult($extra);   
648                 }
649                 break;
650            
651             case 'pdo_dblib':
652                 $sql = $this->database->escape($statement, $params);
653                 $result->setResult($res = $connection->query($sql));
654                 break;
655         }
656        
657         if ($statement_holder->statement) {
658             $result->setResult($statement_holder);   
659         }
660        
661         return $result;       
662     }
663    
664    
665     /**
666     * Returns the SQL for the prepared statement
667     *
668     * @return string  The SQL statement
669     */
670     public function getSQL()
671     {
672         return $this->sql;   
673     }
674    
675    
676     /**
677     * Takes an array of parameters and prepares them for use in a prepared statement
678     *
679     * @param  array $params  The parameters
680     * @return array  The prepared parameters
681     */
682     private function prepareParams($params)
683     {
684         $type      = $this->database->getType();
685         $extension = $this->database->getExtension();
686         if ($extension == 'pdo' && $this->database->getType() == 'mssql') {
687             $extension = 'pdo_dblib';
688         }
689        
690         $statement  = $this->statement;
691         $new_params = array();
692        
693         // The mysqli extension requires all params be set in one call
694         $params_reference = array();
695         $params_type      = '';
696        
697         foreach ($params as $i => $param) {
698             // Prepared statements don't support multi-value params
699             if (is_array($param)) {
700                 throw new fProgrammerException(
701                     'The value passed for placeholder #%s is an array, however multiple values are not supported with prepared statements',
702                     $i+1
703                 );   
704             }
705            
706             // A few of the database extensions don't have prepared statement support
707             // so instead we don't bother preparing the params, we just do a normal escape
708             if (in_array($extension, array('mssql', 'mysql', 'pdo_dblib', 'sqlite'))) {
709                 $new_params[] = $params[$i];
710                 continue;       
711             }
712            
713             $placeholder = $this->placeholders[$i];
714            
715             // We want to normally escape all values except strings
716             // since that actually changed the string
717             if (!in_array($placeholder, array('%l', '%s'))) {
718                
719                 $params[$i] = $this->database->escape($placeholder, $params[$i]);
720                
721                 // Dates, times, timestamps and some booleans need to be unquoted
722                 if (in_array($placeholder, array('%d', '%t', '%p')) || (($type == 'mssql' || $type == 'sqlite' || $type == 'db2') && $placeholder == '%b')) {
723                     $params[$i] = substr($params[$i], 1, -1);   
724                
725                 // Some booleans need to be converted to integers
726                 } elseif ($placeholder == '%b' && ($type == 'postgresql' || $type == 'mysql')) {
727                     $params[$i] = $params[$i] == 'TRUE' ? 1 : 0;
728                 }
729                    
730             // For strings and blobs we need to manually cast objects
731             // This is done in fDatabase::escape() for all other types
732             } else {
733                 if (is_object($params[$i]) && is_callable(array($params[$i], '__toString'))) {
734                     $params[$i] = $params[$i]->__toString();
735                 } elseif (is_object($params[$i])) {
736                     $params[$i] = (string) $params[$i];   
737                 }
738             }
739            
740             // For the database extensions that require is, here we bind params
741             // to the actual statements using the appropriate data types
742             switch ($extension) {
743                
744                 case 'mysqli':
745                     switch ($placeholder) {
746                         case '%l':
747                             // Blobs that are larger than the packet size have to have NULL
748                             // bound and then the data sent via the long data function
749                             $n = NULL;
750                             $params_type         .= 'b';
751                             $params_reference[$i] = &$params[$i];
752                             $statement->send_long_data($i, $params[$i]);
753                             break;
754                         case '%b':
755                             $params_type         .= 'i';
756                             $params_reference[$i] = &$params[$i];
757                             break;
758                         case '%f':
759                             $params_type         .= 'd';
760                             $params_reference[$i] = &$params[$i];
761                             break;
762                         case '%d':
763                         case '%i': // Ints are sent as strings to get past 32 bit int limit, allowing unsigned ints
764                         case '%s':
765                         case '%t':
766                         case '%p':
767                             $params_type         .= 's';
768                             $params_reference[$i] = &$params[$i];
769                             break;
770                     }
771                     break;
772            
773                 case 'oci8':
774                     switch ($placeholder) {
775                         case '%l':
776                             oci_bind_by_name($statement, ':p' . ($i+1), $params[$i], -1, SQLT_BLOB);
777                             break;
778                         case '%b':
779                         case '%i':
780                             oci_bind_by_name($statement, ':p' . ($i+1), $params[$i], -1, SQLT_INT);
781                             break;
782                         case '%d':
783                         case '%f': // There is no constant for floats, so we treat them as strings
784                         case '%s':
785                         case '%t':
786                         case '%p':
787                             oci_bind_by_name($statement, ':p' . ($i+1), $params[$i], -1, SQLT_CHR);
788                             break;
789                     }
790                     break;
791                
792                 case 'pdo':
793                     switch ($placeholder) {
794                         case '%l':
795                             $statement->bindParam($i+1, $params[$i], $params[$i] === NULL ? PDO::PARAM_NULL : PDO::PARAM_LOB);
796                             break;
797                         case '%b':
798                             $statement->bindParam($i+1, $params[$i], $params[$i] === NULL ? PDO::PARAM_NULL : PDO::PARAM_BOOL);
799                             break;
800                         case '%i':
801                             $statement->bindParam($i+1, $params[$i], $params[$i] === NULL ? PDO::PARAM_NULL : PDO::PARAM_INT);
802                             break;
803                         case '%d':
804                         case '%f': // For some reason float are supposed to be bound as strings
805                         case '%s':
806                         case '%t':
807                         case '%p':
808                             $statement->bindParam($i+1, $params[$i], $params[$i] === NULL ? PDO::PARAM_NULL : PDO::PARAM_STR);
809                             break;
810                     }
811                     break;
812                
813                 case 'sqlsrv':
814                     $this->bound_params[$i][0] = $params[$i];
815                     break;
816             }
817            
818             $new_params[] = $params[$i];
819         }
820        
821         if ($extension == 'mysqli') {
822             array_unshift($params_reference, $params_type);
823             call_user_func_array(array($statement, 'bind_param'), $params_reference);   
824         }
825        
826         return $new_params;
827     }
828    
829    
830     /**
831     * The MySQL PDO driver has issues if you try to reuse a prepared statement
832     * without any placeholders.
833     *
834     * @return void
835     */
836     private function regenerateStatement()
837     {
838         $is_pdo   = $this->database->getExtension() == 'pdo';
839         $is_mysql = $this->database->getType() == 'mysql';
840         if ($this->placeholders || !$is_pdo || !$is_mysql) {
841             return;
842         }
843        
844         $this->statement = $this->database->getConnection()->prepare($this->sql);
845     }
846 }
847  
848  
849  
850 /**
851  * Copyright (c) 2010 Will Bond <will@flourishlib.com>
852  *
853  * Permission is hereby granted, free of charge, to any person obtaining a copy
854  * of this software and associated documentation files (the "Software"), to deal
855  * in the Software without restriction, including without limitation the rights
856  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
857  * copies of the Software, and to permit persons to whom the Software is
858  * furnished to do so, subject to the following conditions:
859  *
860  * The above copyright notice and this permission notice shall be included in
861  * all copies or substantial portions of the Software.
862  *
863  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
864  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
865  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
866  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
867  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
868  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
869  * THE SOFTWARE.
870  */