PHP userland backwards compatibility layer that emulates PHP 5.5+ core functions.

⌈⌋ ⎇ branch:  upgrade.php


Check-in [1bd07841f1]

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:(no comment)
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: 1bd07841f1a52350d20601234847d3f3bb5f49ae
User & Date: mario 2014-04-24 02:22:36
Context
2014-04-24
02:23
Add ellipse … paramized call support and ->is handler. check-in: ab91327edb user: mario tags: trunk
02:22
(no comment) check-in: 1bd07841f1 user: mario tags: trunk
02:21
Fixes for json_encode bugs ce9ff32a53, 7412bf040f check-in: 86352370f9 user: mario tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to ext/contrib/pdo_mysql.php.

1
2
3
4
5
6
7
8
9
10
11
12
13

14
15
16
17
18
19
20
<?php
/**
 * api: php
 * title: Procedural pdo_* interface mimicking mysql_* functions
 * version: 0.9.2
 * priority: EXPERIMENTAL
 * license: Public Domain
 * type: functions
 * suggests: upgradephp
 * config:  <const name="PDO_HELPFUL" type="boolean" value="1" help="Provide additional notices for common pitfalls"/>   <const name="PDO_SEEKABLE" type="int" value="0" help="Adds a faux cursor wrapper atop PDOStatement to allow seeking across result rows. Set to 1 to enable, or any larger integer for caching rows."/>
 * category: library
 * url: http://fossil.include-once.org/upgradephp/finfo?name=ext/contrib/pdo_mysql.php
 * requires: php (>= 5.1.3), php:pdo

 *
 *
 * The dated mysql_* functions are chaperoned by deprecation nagging and cursorily
 * mentions of PDO and MSQLI. Forced-switching APIs is kinda pointless however when
 * bound parameters go unmentioned and code rewriting remains prohibitive.
 *
 * This collection of functions provide pdo_ lookalikes for most mysql_* functions.




|




|



>







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
/**
 * api: php
 * title: Procedural pdo_* interface mimicking mysql_* functions
 * version: 0.9.3
 * priority: EXPERIMENTAL
 * license: Public Domain
 * type: functions
 * suggests: upgradephp
 * config:  <const name="PDO_HELPFUL" type="select" value="0=None|1=Notice|2=Syntax" help="Provide additional notices for common pitfalls"/>   <const name="PDO_SEEKABLE" type="int" value="0" help="Adds a faux cursor wrapper atop PDOStatement to allow seeking across result rows. Set to 1 to enable, or any larger integer for caching rows."/>
 * category: library
 * url: http://fossil.include-once.org/upgradephp/finfo?name=ext/contrib/pdo_mysql.php
 * requires: php (>= 5.1.3), php:pdo
 * doc: http://stackoverflow.com/a/20767765
 *
 *
 * The dated mysql_* functions are chaperoned by deprecation nagging and cursorily
 * mentions of PDO and MSQLI. Forced-switching APIs is kinda pointless however when
 * bound parameters go unmentioned and code rewriting remains prohibitive.
 *
 * This collection of functions provide pdo_ lookalikes for most mysql_* functions.
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
 *     If you need arbitrary  pdo_result(), _fetch_lengths(), _data_seek()
 *     access, please enable PDO_SEEKABLE. It does refetch if need be for
 *     out-of-order retrieval. (Mysqld itself provides enough buffering.)
 *
 *   BOUND PARAMETERS    In SQL only values can be bound parameters with ?
 *     placeholders. Identifiers like table names, column names can't. You
 *     still have to use interpolation (with withelisting) for those.
 *     Likewise can't LIMIT ?,? clauses be used in PDO. Use typecasted
 *     interpolated numbers there.
 *
 *   NAMED PARAMETERS    Besides ? placeholders there are also :named_keys.
 *     pdo_query() supports those too if passed as a single array with
 *     :key => value pairs.  You can only use either or.
 *
 *   SOMETHING DOESN'T WORK    Before asking for personal support anywhere
 *     on the Internet, please have the courtesy to `print pdo_error();`







|
|







66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
 *     If you need arbitrary  pdo_result(), _fetch_lengths(), _data_seek()
 *     access, please enable PDO_SEEKABLE. It does refetch if need be for
 *     out-of-order retrieval. (Mysqld itself provides enough buffering.)
 *
 *   BOUND PARAMETERS    In SQL only values can be bound parameters with ?
 *     placeholders. Identifiers like table names, column names can't. You
 *     still have to use interpolation (with withelisting) for those.
 *     Likewise can't LIMIT ?,? clauses be used in pdo_query(). Use
 *     typecasted interpolated numbers there.
 *
 *   NAMED PARAMETERS    Besides ? placeholders there are also :named_keys.
 *     pdo_query() supports those too if passed as a single array with
 *     :key => value pairs.  You can only use either or.
 *
 *   SOMETHING DOESN'T WORK    Before asking for personal support anywhere
 *     on the Internet, please have the courtesy to `print pdo_error();`
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
 *
 *
 */




// provide development tips (deprecated and redundant functions)
defined("PDO_HELPFUL") or
define("PDO_HELPFUL", 1);


// apply PDO wrapper for random-access row seeking / faux cursor
defined("PDO_SEEKABLE") or
define("PDO_SEEKABLE", 1);


// upgradephp
defined("E_USER_DEPRECATED") or
define("E_USER_DEPRECATED", E_USER_NOTICE);









|






|







122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
 *
 *
 */




// provide development tips (deprecated and redundant functions), set to 2 for SQL syntax warnings about unbound strings
defined("PDO_HELPFUL") or
define("PDO_HELPFUL", 1);


// apply PDO wrapper for random-access row seeking / faux cursor
defined("PDO_SEEKABLE") or
define("PDO_SEEKABLE", 1);  // set this to a higher integer (e.g. 500) to enable a cache for random access


// upgradephp
defined("E_USER_DEPRECATED") or
define("E_USER_DEPRECATED", E_USER_NOTICE);


177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202

203
204
205
206
207
208
209
  function pdo_close($link=NULL) {
     PDO_HELPFUL and pdo_trigger_error("pdo_close() Does not need to be called usually. Just unset() your database handle.", E_USER_DEPRECATED);

     if ($link) {
        unset($link);
     }
     else {
        unset($GLOBALS["pdo"]);
     }
  }




  /**
   * Connect to database.
   *
   * Also stores the newest PDO handle in the global `$pdo` variable.
   *
   */  
  function pdo_connect($server=NULL, $user=NULL, $pass=NULL, $new_link=FALSE, $client_flags=0x0000, $pconnect=0x0000) {
     
     // get params
     $server or $server = ini_get("mysql.default_host");
     $user or $user = ini_get("mysql.default_user");
     $pass or $pass = ini_get("mysql.default_password");


     // prepare Data Source Name
     $dsn = "mysql:";
     
     // servername contains a socket path
     if (strpos($server, "/")) {
        strpos($server, ":") and $server = substr($server, strpos($server, ":") + 1);







|


















>







178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
  function pdo_close($link=NULL) {
     PDO_HELPFUL and pdo_trigger_error("pdo_close() Does not need to be called usually. Just unset() your database handle.", E_USER_DEPRECATED);

     if ($link) {
        unset($link);
     }
     else {
        $GLOBALS["pdo"] = /*unset with*/ new pdo_dummy("No database connection. You had just disconnected using pdo_close()!");
     }
  }




  /**
   * Connect to database.
   *
   * Also stores the newest PDO handle in the global `$pdo` variable.
   *
   */  
  function pdo_connect($server=NULL, $user=NULL, $pass=NULL, $new_link=FALSE, $client_flags=0x0000, $pconnect=0x0000) {
     
     // get params
     $server or $server = ini_get("mysql.default_host");
     $user or $user = ini_get("mysql.default_user");
     $pass or $pass = ini_get("mysql.default_password");


     // prepare Data Source Name
     $dsn = "mysql:";
     
     // servername contains a socket path
     if (strpos($server, "/")) {
        strpos($server, ":") and $server = substr($server, strpos($server, ":") + 1);
217
218
219
220
221
222
223

224
225
226
227
228
229
230
231
232
233
234
        }
        else {
           $dsn .= "host=$server";
        }
     }
     //("we don't have a dbname= at this point");
     

     // driver flags
     $flags = array(
        PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => true,
        #PDO::MYSQL_ATTR_INIT_COMMAND => '',   // direct queries, no prepared statements
        #PDO::MYSQL_ATTR_DIRECT_QUERY => true, // direct queries, no prepared statements
        #PDO::MYSQL_ATTR_FOUND_ROWS => true,   // rowCount() for SELECTs, not sure if this is an init parameter, or Statement setting
     );
     if ($client_flags & MYSQL_CLIENT_COMPRESS) {
        $flags[PDO::MYSQL_ATTR_COMPRESS] = 1;
     }
     if ($client_flags & MYSQL_CLIENT_SSL) {







>



|







219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
        }
        else {
           $dsn .= "host=$server";
        }
     }
     //("we don't have a dbname= at this point");
     

     // driver flags
     $flags = array(
        PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => true,
        #PDO::MYSQL_ATTR_INIT_COMMAND => '',   // first SQL command after connect
        #PDO::MYSQL_ATTR_DIRECT_QUERY => true, // direct queries, no prepared statements
        #PDO::MYSQL_ATTR_FOUND_ROWS => true,   // rowCount() for SELECTs, not sure if this is an init parameter, or Statement setting
     );
     if ($client_flags & MYSQL_CLIENT_COMPRESS) {
        $flags[PDO::MYSQL_ATTR_COMPRESS] = 1;
     }
     if ($client_flags & MYSQL_CLIENT_SSL) {
242
243
244
245
246
247
248

249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264

265
266
267
268
269
270
271
     if ($client_flags & 128) {
        $flags[PDO::MYSQL_ATTR_LOCAL_INFILE] = 1;
     }
     if ($pconnect) {
        $flags[PDO::ATTR_PERSISTENT] = 1;
     }
     

     // instantiate connection
     try {
        $pdo = new PDO($dsn, $user, $pass, $flags);
     }
     catch (RuntimeException $pdo) {
        PDO_HELPFUL and pdo_trigger_error("pdo_connect() Failed. {$pdo->getMessage()}", E_USER_WARNING);
        return new pdo_dummy("Database connection had failed [ErrCode{$pdo->getCode()}].");
     }
     
     // set PDO flags
     $pdo->setAttribute(PDO::ATTR_ERRMODE, (PDO_HELPFUL ? PDO::ERRMODE_WARNING : PDO::ERRMODE_SILENT)); //or PDO::ERRMODE_EXCEPTION
     $pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
     $pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
     $pdo->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, false);
     $pdo->setAttribute(PDO::ATTR_CASE, PDO::CASE_NATURAL);
     $pdo->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, true);


     // done     
     return pdo_handle($pdo, "SET_HANDLE_AS_DEFAULT");
  }










>
















>







245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
     if ($client_flags & 128) {
        $flags[PDO::MYSQL_ATTR_LOCAL_INFILE] = 1;
     }
     if ($pconnect) {
        $flags[PDO::ATTR_PERSISTENT] = 1;
     }
     

     // instantiate connection
     try {
        $pdo = new PDO($dsn, $user, $pass, $flags);
     }
     catch (RuntimeException $pdo) {
        PDO_HELPFUL and pdo_trigger_error("pdo_connect() Failed. {$pdo->getMessage()}", E_USER_WARNING);
        return new pdo_dummy("Database connection had failed [ErrCode{$pdo->getCode()}].");
     }
     
     // set PDO flags
     $pdo->setAttribute(PDO::ATTR_ERRMODE, (PDO_HELPFUL ? PDO::ERRMODE_WARNING : PDO::ERRMODE_SILENT)); //or PDO::ERRMODE_EXCEPTION
     $pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
     $pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
     $pdo->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, false);
     $pdo->setAttribute(PDO::ATTR_CASE, PDO::CASE_NATURAL);
     $pdo->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, true);


     // done     
     return pdo_handle($pdo, "SET_HANDLE_AS_DEFAULT");
  }



542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
   * Unallocating a query result.
   *
   * This call has little practical use.
   * Rather just unset() your pdo_query $result object variables.
   *
   */
  function pdo_free_result($stmt) {
     PDO_HELPFUL and trigger_error("pdo_free_result() usage is mostly redundant. PHP cleans up resource handles/objects if you just unset() them, or move out of scope.", E_USER_DEPRECATED);

     // free up some resources
     try {
        pdo_stmt($stmt)->fetchAll();
        pdo_stmt($stmt)->closeCursor();
     }
     catch (PDOException $e) {







|







547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
   * Unallocating a query result.
   *
   * This call has little practical use.
   * Rather just unset() your pdo_query $result object variables.
   *
   */
  function pdo_free_result($stmt) {
     ###PDO_HELPFUL and trigger_error("pdo_free_result() usage is mostly redundant. PHP cleans up resource handles/objects if you just unset() them, or move out of scope.", E_USER_DEPRECATED);

     // free up some resources
     try {
        pdo_stmt($stmt)->fetchAll();
        pdo_stmt($stmt)->closeCursor();
     }
     catch (PDOException $e) {
792
793
794
795
796
797
798

799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817

818
819
820
821







822
823
824
825
826
827
828
829
830
831
832
833
834

835
836
837
838
839
840
841
        // or the last
        elseif (is_object(end($params))) {
           $link = array_pop($params);
        }
     }
     // or we use the default $pdo
     $link = pdo_handle($link);

     
     // is $params a list to pdo_query(), or just one array with :key=>value pairs?
     if (count($params)==1 && is_array($params[0])) {
        $params = array_shift($params);
     }


     // add PDO_MySQL driver flag / workaround for specific query types
     switch (strtoupper(substr($sql, 0, strspn(strtoupper($sql), "SELECT,USE,CREATE")))) {

        // ought to make ->rowCount() work
        case "SELECT":
           $flags[PDO::MYSQL_ATTR_FOUND_ROWS] = true;
           break;

        // temporarily disable prepared statement mode for unbindable directives
        case "USE":
           $direct = true;
           $link->setAttribute(PDO::ATTR_EMULATE_PREPARES, true);

           break;

        default:
     }









     // unparameterized query()
     if ($direct) {
        $stmt = $link->query($sql);
        $link->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
     }
     // or prepare() and execute()
     else {
        if ($stmt = $link->prepare($sql, $flags)) {   // no try-catch in _WARNING mode
           $stmt->execute($params);
        }
     }

     
     // result
     if (!$stmt and PDO_HELPFUL) {
        pdo_trigger_error("pdo_query() SQL query failed, see pdo_error()", E_USER_WARNING);
     }
     elseif (PDO_SEEKABLE & !$direct) {
        return new PDOStatement_Seekable($stmt, $params);







>



















>




>
>
>
>
>
>
>













>







797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
        // or the last
        elseif (is_object(end($params))) {
           $link = array_pop($params);
        }
     }
     // or we use the default $pdo
     $link = pdo_handle($link);

     
     // is $params a list to pdo_query(), or just one array with :key=>value pairs?
     if (count($params)==1 && is_array($params[0])) {
        $params = array_shift($params);
     }


     // add PDO_MySQL driver flag / workaround for specific query types
     switch (strtoupper(substr($sql, 0, strspn(strtoupper($sql), "SELECT,USE,CREATE")))) {

        // ought to make ->rowCount() work
        case "SELECT":
           $flags[PDO::MYSQL_ATTR_FOUND_ROWS] = true;
           break;

        // temporarily disable prepared statement mode for unbindable directives
        case "USE":
           $direct = true;
           $link->setAttribute(PDO::ATTR_EMULATE_PREPARES, true);
           #$flags[PDO::MYSQL_ATTR_DIRECT_QUERY] = true; // has no effect as stmt flag, also overwritten by setAttr EMULATE_PREPARES
           break;

        default:
     }
     
     
     // add syntax notices?
     if (PDO_HELPFUL>=2) {
        strpos($sql, "'") and pdo_trigger_error("pdo_query() It looks like you're still using single ' quotes in your SQL. This indicates plain or interpolated strings or variables in SQL context. Investigate if parameter binding `pdo_query(\"SELECT ?, ?\", \$var1, \$var2)` would fit.");
        strpos($sql, '"') and pdo_trigger_error("pdo_query() You are using double \" quotes in your SQL. Beware that this is ambiguous. In MySQLs default mode these indicate interpolated string variables (don't do that, use bound parameters), while in MySQL ANSI mode they quote identifiers (table or column name).");
     }


     // unparameterized query()
     if ($direct) {
        $stmt = $link->query($sql);
        $link->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
     }
     // or prepare() and execute()
     else {
        if ($stmt = $link->prepare($sql, $flags)) {   // no try-catch in _WARNING mode
           $stmt->execute($params);
        }
     }

     
     // result
     if (!$stmt and PDO_HELPFUL) {
        pdo_trigger_error("pdo_query() SQL query failed, see pdo_error()", E_USER_WARNING);
     }
     elseif (PDO_SEEKABLE & !$direct) {
        return new PDOStatement_Seekable($stmt, $params);
881
882
883
884
885
886
887

888
889
890
891
892
893
894
895
896
897
898
899
900
901
     // workaround: fetch everything, works just once
     else {
        pdo_trigger_error("pdo_result() Can currently only be used once, for fetching a value from the first \$row. Scrolling only works with PDO_SEEKABLE enabled.", E_USER_WARNING);

        $rows = pdo_stmt($stmt)->fetchAll(PDO::FETCH_BOTH);
        #static $rows[hash(stmt)]; //would otherwise bind the result set here
     }

     
     // check if found
     if (isset($rows[$row][$field])) {
        return $rows[$row][$field];
     }
     else {
        pdo_trigger_error("pdo_result() Couldn't find row [$row] and column `$field`.", E_USER_NOTICE);
     }
     
  }











>






|







896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
     // workaround: fetch everything, works just once
     else {
        pdo_trigger_error("pdo_result() Can currently only be used once, for fetching a value from the first \$row. Scrolling only works with PDO_SEEKABLE enabled.", E_USER_WARNING);

        $rows = pdo_stmt($stmt)->fetchAll(PDO::FETCH_BOTH);
        #static $rows[hash(stmt)]; //would otherwise bind the result set here
     }

     
     // check if found
     if (isset($rows[$row][$field])) {
        return $rows[$row][$field];
     }
     else {
        pdo_trigger_error("pdo_result() Couldn't find row [$row] and column `$field`.");
     }
     
  }




922
923
924
925
926
927
928

929
930
931
932
933
934
935




  /**
   * Return server status and some settings.
   *

   */
  function pdo_stat() {
     return array_column(pdo_query("SHOW STATUS")->fetchAll(), "Value", "Variable_name");
  }










>







938
939
940
941
942
943
944
945
946
947
948
949
950
951
952




  /**
   * Return server status and some settings.
   *
   * @requires: php (>= 5.5) | upgradephp
   */
  function pdo_stat() {
     return array_column(pdo_query("SHOW STATUS")->fetchAll(), "Value", "Variable_name");
  }



1080
1081
1082
1083
1084
1085
1086
1087
1088

1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109





1110

1111
1112
1113
1114
1115
1116
1117

1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130

1131
1132
1133
1134
1135
1136
1137

1138
1139
1140
1141
1142

1143
1144
1145
1146
1147
1148
1149
1150
1151
1152

1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
  class PDOStatement_Seekable implements Iterator {

     // the actual PDOStatement
     private $stmt = NULL;
     // bound parameters for reexecution
     private $params = array();

     // row counter
     private $i = -1;

     private $max;
     // last fetched rows
     private $cache = array();


     // keep proxied PDOStatement and data necessary to rerun it
     function __construct($stmt, $params) {
        $this->stmt = $stmt;
        $this->params = $params;
        $this->max = $stmt->rowCount() - 1;
     }


     // invoke wrapped PDOStatement
     function __call($func, $args) {
        return call_user_func_array(array($this->stmt, $func), $args);
     }


     // repopulate PDOStatement
     function reExecute() {





         $this->stmt->execute($this->params);

         $this->i = -1;   // last fetched row
         $this->prior_row = false;   // corresponds to [$i]
     }


     // fetch a single row
     function fetch($type=PDO::FETCH_ASSOC, $ori=PDO::FETCH_ORI_NEXT, $offset=0) {


        // calculate offset
        $last = & $this->i;
        $target = array(                                    
            PDO::FETCH_ORI_NEXT => $last + 1,
            PDO::FETCH_ORI_PRIOR => $last,
            PDO::FETCH_ORI_REL => $last + $offset,
            PDO::FETCH_ORI_FIRST => -1,
            PDO::FETCH_ORI_LAST => $this->rowCount() - 1,
            PDO::FETCH_ORI_ABS => $offset,
        );
        $target = $target[$ori];
#print "seek($last->$target) ";

        
        // last row? got that covered!
        if (isset($this->cache[$target])) {
#print "seek(==) ";
            return $this->rowType($type, $this->cache[$target]);
        }
        

        // moving farther backwards
        if ($target < $last) {
#print "seek(<<) ";
            $this->reExecute();
        }

        
        // jump forwards
        while ($target > $last + 1) {
#print "seek(>>) ";
            $row = $this->stmt->fetch(PDO::FETCH_ASSOC);
            $last++;
            if (!$row) {
               return pdo_trigger_error("PDOStatement_Seekable: scrolling past last row", E_USER_WARNING) and $row;
            }
        }


        // actually fetch next row
        if ($row = $this->stmt->fetch(PDO::FETCH_ASSOC)) {
#print "seek(ft) ";
           assert($target == ++$last);
           // keep last row(s)
           if (count($this->cache) > PDO_SEEKABLE) {
              $this->cache = array_slice($this->cache, $last, -PDO_SEEKABLE, true);
           }
           $this->cache[$last] = $row;
        }
        return $this->rowType($type, $row);
     }









|

>









|









|
|
>
>
>
>
>
|
>
|
|





>








|




>







>



|

>







|


>







|







1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
  class PDOStatement_Seekable implements Iterator {

     // the actual PDOStatement
     private $stmt = NULL;
     // bound parameters for reexecution
     private $params = array();

     // row cursor
     private $i = -1;
     // available max key / rowcount
     private $max;
     // last fetched rows
     private $cache = array();


     // keep proxied PDOStatement and data necessary to rerun it
     function __construct($stmt, $params) {
        $this->stmt = $stmt;
        $this->params = $params;
        $this->max = $stmt->rowCount(/*PDO::MYSQL_ATTR_FOUND_ROWS*/) - 1;
     }


     // invoke wrapped PDOStatement
     function __call($func, $args) {
        return call_user_func_array(array($this->stmt, $func), $args);
     }


     // repopulate PDOStatement, reset cache and cursor
     function execute($params = NULL) {
        # Superposes the actual stmt::execute(), in case it's really a new query.
        # Note that rewinding also has to reset the cache, in case the table
        # contents changed meanwhile. So may result in a different set of result
        # rows alltogether. Which is really not wanted since this is all just
        # meant for seeking. (No way around that in a userland faux cursor.)
        $this->stmt->execute($params ? $params : $this->params);
        $this->max = $stmt->rowCount(/*PDO::MYSQL_ATTR_FOUND_ROWS*/) - 1;
        $this->i = -1;   // last fetched row
        $this->cache = array();
     }


     // fetch a single row
     function fetch($type=PDO::FETCH_ASSOC, $ori=PDO::FETCH_ORI_NEXT, $offset=0) {


        // calculate offset
        $last = & $this->i;
        $target = array(                                    
            PDO::FETCH_ORI_NEXT => $last + 1,
            PDO::FETCH_ORI_PRIOR => $last,
            PDO::FETCH_ORI_REL => $last + $offset,
            PDO::FETCH_ORI_FIRST => -1,
            PDO::FETCH_ORI_LAST => $this->max,
            PDO::FETCH_ORI_ABS => $offset,
        );
        $target = $target[$ori];
#print "seek($last->$target) ";

        
        // last row? got that covered!
        if (isset($this->cache[$target])) {
#print "seek(==) ";
            return $this->rowType($type, $this->cache[$target]);
        }
        

        // moving farther backwards
        if ($target < $last) {
#print "seek(<<) ";
            $this->execute();
        }

        
        // jump forwards
        while ($target > $last + 1) {
#print "seek(>>) ";
            $row = $this->stmt->fetch(PDO::FETCH_ASSOC);
            $last++;
            if (!$row) {
               return pdo_trigger_error("PDOStatement_Seekable::fetch() Scrolling past last row.", E_USER_WARNING) and $row;
            }
        }


        // actually fetch next row
        if ($row = $this->stmt->fetch(PDO::FETCH_ASSOC)) {
#print "seek(ft) ";
           assert($target == ++$last);
           // keep last row(s)
           if (count($this->cache) > PDO_SEEKABLE) {
              $this->cache = array_slice($this->cache, $last, - PDO_SEEKABLE, true);
           }
           $this->cache[$last] = $row;
        }
        return $this->rowType($type, $row);
     }


1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193

     // Iterator handling
     function current() {
        return $this->fetch();
     }
     function rewind() {
        if ($this->i >= 0) {
           $this->reExecute();
        }
     }
     // PDOStatement itself implements just Traversable internally, not Iterator, so we have to handle the keys ourselves
     function key() {
        return $this->i >= 0 ? $this->i : NULL;
     }
     function next() {







|







1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222

     // Iterator handling
     function current() {
        return $this->fetch();
     }
     function rewind() {
        if ($this->i >= 0) {
           $this->execute();
        }
     }
     // PDOStatement itself implements just Traversable internally, not Iterator, so we have to handle the keys ourselves
     function key() {
        return $this->i >= 0 ? $this->i : NULL;
     }
     function next() {