Subversion Repositories Applications.papyrus

Rev

Rev 1688 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
320 jpm 1
<?php
2
 
3
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
4
 
5
/**
6
 * The PEAR DB driver for PHP's interbase extension
7
 * for interacting with Interbase and Firebird databases
8
 *
9
 * While this class works with PHP 4, PHP's InterBase extension is
10
 * unstable in PHP 4.  Use PHP 5.
11
 *
12
 * PHP versions 4 and 5
13
 *
14
 * LICENSE: This source file is subject to version 3.0 of the PHP license
15
 * that is available through the world-wide-web at the following URI:
16
 * http://www.php.net/license/3_0.txt.  If you did not receive a copy of
17
 * the PHP License and are unable to obtain it through the web, please
18
 * send a note to license@php.net so we can mail you a copy immediately.
19
 *
20
 * @category   Database
21
 * @package    DB
22
 * @author     Sterling Hughes <sterling@php.net>
23
 * @author     Daniel Convissor <danielc@php.net>
24
 * @copyright  1997-2005 The PHP Group
25
 * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
1173 jp_milcent 26
 * @version    CVS: $Id: ibase.php,v 1.3 2006-12-14 15:04:28 jp_milcent Exp $
320 jpm 27
 * @link       http://pear.php.net/package/DB
28
 */
29
 
30
/**
31
 * Obtain the DB_common class so it can be extended from
32
 */
33
require_once 'DB/common.php';
34
 
35
/**
36
 * The methods PEAR DB uses to interact with PHP's interbase extension
37
 * for interacting with Interbase and Firebird databases
38
 *
39
 * These methods overload the ones declared in DB_common.
40
 *
41
 * While this class works with PHP 4, PHP's InterBase extension is
42
 * unstable in PHP 4.  Use PHP 5.
43
 *
44
 * NOTICE:  limitQuery() only works for Firebird.
45
 *
46
 * @category   Database
47
 * @package    DB
48
 * @author     Sterling Hughes <sterling@php.net>
49
 * @author     Daniel Convissor <danielc@php.net>
50
 * @copyright  1997-2005 The PHP Group
51
 * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
1173 jp_milcent 52
 * @version    Release: 1.7.6
320 jpm 53
 * @link       http://pear.php.net/package/DB
54
 * @since      Class became stable in Release 1.7.0
55
 */
56
class DB_ibase extends DB_common
57
{
58
    // {{{ properties
59
 
60
    /**
61
     * The DB driver type (mysql, oci8, odbc, etc.)
62
     * @var string
63
     */
64
    var $phptype = 'ibase';
65
 
66
    /**
67
     * The database syntax variant to be used (db2, access, etc.), if any
68
     * @var string
69
     */
70
    var $dbsyntax = 'ibase';
71
 
72
    /**
73
     * The capabilities of this DB implementation
74
     *
75
     * The 'new_link' element contains the PHP version that first provided
76
     * new_link support for this DBMS.  Contains false if it's unsupported.
77
     *
78
     * Meaning of the 'limit' element:
79
     *   + 'emulate' = emulate with fetch row by number
80
     *   + 'alter'   = alter the query
81
     *   + false     = skip rows
82
     *
83
     * NOTE: only firebird supports limit.
84
     *
85
     * @var array
86
     */
87
    var $features = array(
88
        'limit'         => false,
89
        'new_link'      => false,
90
        'numrows'       => 'emulate',
91
        'pconnect'      => true,
92
        'prepare'       => true,
93
        'ssl'           => false,
94
        'transactions'  => true,
95
    );
96
 
97
    /**
98
     * A mapping of native error codes to DB error codes
99
     * @var array
100
     */
101
    var $errorcode_map = array(
102
        -104 => DB_ERROR_SYNTAX,
103
        -150 => DB_ERROR_ACCESS_VIOLATION,
104
        -151 => DB_ERROR_ACCESS_VIOLATION,
105
        -155 => DB_ERROR_NOSUCHTABLE,
106
        -157 => DB_ERROR_NOSUCHFIELD,
107
        -158 => DB_ERROR_VALUE_COUNT_ON_ROW,
108
        -170 => DB_ERROR_MISMATCH,
109
        -171 => DB_ERROR_MISMATCH,
110
        -172 => DB_ERROR_INVALID,
111
        // -204 =>  // Covers too many errors, need to use regex on msg
112
        -205 => DB_ERROR_NOSUCHFIELD,
113
        -206 => DB_ERROR_NOSUCHFIELD,
114
        -208 => DB_ERROR_INVALID,
115
        -219 => DB_ERROR_NOSUCHTABLE,
116
        -297 => DB_ERROR_CONSTRAINT,
117
        -303 => DB_ERROR_INVALID,
118
        -413 => DB_ERROR_INVALID_NUMBER,
119
        -530 => DB_ERROR_CONSTRAINT,
120
        -551 => DB_ERROR_ACCESS_VIOLATION,
121
        -552 => DB_ERROR_ACCESS_VIOLATION,
122
        // -607 =>  // Covers too many errors, need to use regex on msg
123
        -625 => DB_ERROR_CONSTRAINT_NOT_NULL,
124
        -803 => DB_ERROR_CONSTRAINT,
125
        -804 => DB_ERROR_VALUE_COUNT_ON_ROW,
126
        -904 => DB_ERROR_CONNECT_FAILED,
127
        -922 => DB_ERROR_NOSUCHDB,
128
        -923 => DB_ERROR_CONNECT_FAILED,
129
        -924 => DB_ERROR_CONNECT_FAILED
130
    );
131
 
132
    /**
133
     * The raw database connection created by PHP
134
     * @var resource
135
     */
136
    var $connection;
137
 
138
    /**
139
     * The DSN information for connecting to a database
140
     * @var array
141
     */
142
    var $dsn = array();
143
 
144
 
145
    /**
146
     * The number of rows affected by a data manipulation query
147
     * @var integer
148
     * @access private
149
     */
150
    var $affected = 0;
151
 
152
    /**
153
     * Should data manipulation queries be committed automatically?
154
     * @var bool
155
     * @access private
156
     */
157
    var $autocommit = true;
158
 
159
    /**
160
     * The prepared statement handle from the most recently executed statement
161
     *
162
     * {@internal  Mainly here because the InterBase/Firebird API is only
163
     * able to retrieve data from result sets if the statemnt handle is
164
     * still in scope.}}
165
     *
166
     * @var resource
167
     */
168
    var $last_stmt;
169
 
170
    /**
171
     * Is the given prepared statement a data manipulation query?
172
     * @var array
173
     * @access private
174
     */
175
    var $manip_query = array();
176
 
177
 
178
    // }}}
179
    // {{{ constructor
180
 
181
    /**
182
     * This constructor calls <kbd>$this->DB_common()</kbd>
183
     *
184
     * @return void
185
     */
186
    function DB_ibase()
187
    {
188
        $this->DB_common();
189
    }
190
 
191
    // }}}
192
    // {{{ connect()
193
 
194
    /**
195
     * Connect to the database server, log in and open the database
196
     *
197
     * Don't call this method directly.  Use DB::connect() instead.
198
     *
199
     * PEAR DB's ibase driver supports the following extra DSN options:
200
     *   + buffers    The number of database buffers to allocate for the
201
     *                 server-side cache.
202
     *   + charset    The default character set for a database.
203
     *   + dialect    The default SQL dialect for any statement
204
     *                 executed within a connection.  Defaults to the
205
     *                 highest one supported by client libraries.
206
     *                 Functional only with InterBase 6 and up.
207
     *   + role       Functional only with InterBase 5 and up.
208
     *
209
     * @param array $dsn         the data source name
210
     * @param bool  $persistent  should the connection be persistent?
211
     *
212
     * @return int  DB_OK on success. A DB_Error object on failure.
213
     */
214
    function connect($dsn, $persistent = false)
215
    {
216
        if (!PEAR::loadExtension('interbase')) {
217
            return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND);
218
        }
219
 
220
        $this->dsn = $dsn;
221
        if ($dsn['dbsyntax']) {
222
            $this->dbsyntax = $dsn['dbsyntax'];
223
        }
224
        if ($this->dbsyntax == 'firebird') {
225
            $this->features['limit'] = 'alter';
226
        }
227
 
228
        $params = array(
229
            $dsn['hostspec']
230
                    ? ($dsn['hostspec'] . ':' . $dsn['database'])
231
                    : $dsn['database'],
232
            $dsn['username'] ? $dsn['username'] : null,
233
            $dsn['password'] ? $dsn['password'] : null,
234
            isset($dsn['charset']) ? $dsn['charset'] : null,
235
            isset($dsn['buffers']) ? $dsn['buffers'] : null,
236
            isset($dsn['dialect']) ? $dsn['dialect'] : null,
237
            isset($dsn['role'])    ? $dsn['role'] : null,
238
        );
239
 
240
        $connect_function = $persistent ? 'ibase_pconnect' : 'ibase_connect';
241
 
242
        $this->connection = @call_user_func_array($connect_function, $params);
243
        if (!$this->connection) {
244
            return $this->ibaseRaiseError(DB_ERROR_CONNECT_FAILED);
245
        }
246
        return DB_OK;
247
    }
248
 
249
    // }}}
250
    // {{{ disconnect()
251
 
252
    /**
253
     * Disconnects from the database server
254
     *
255
     * @return bool  TRUE on success, FALSE on failure
256
     */
257
    function disconnect()
258
    {
259
        $ret = @ibase_close($this->connection);
260
        $this->connection = null;
261
        return $ret;
262
    }
263
 
264
    // }}}
265
    // {{{ simpleQuery()
266
 
267
    /**
268
     * Sends a query to the database server
269
     *
270
     * @param string  the SQL query string
271
     *
272
     * @return mixed  + a PHP result resrouce for successful SELECT queries
273
     *                + the DB_OK constant for other successful queries
274
     *                + a DB_Error object on failure
275
     */
276
    function simpleQuery($query)
277
    {
278
        $ismanip = DB::isManip($query);
279
        $this->last_query = $query;
280
        $query = $this->modifyQuery($query);
281
        $result = @ibase_query($this->connection, $query);
282
 
283
        if (!$result) {
284
            return $this->ibaseRaiseError();
285
        }
286
        if ($this->autocommit && $ismanip) {
287
            @ibase_commit($this->connection);
288
        }
289
        if ($ismanip) {
290
            $this->affected = $result;
291
            return DB_OK;
292
        } else {
293
            $this->affected = 0;
294
            return $result;
295
        }
296
    }
297
 
298
    // }}}
299
    // {{{ modifyLimitQuery()
300
 
301
    /**
302
     * Adds LIMIT clauses to a query string according to current DBMS standards
303
     *
304
     * Only works with Firebird.
305
     *
306
     * @param string $query   the query to modify
307
     * @param int    $from    the row to start to fetching (0 = the first row)
308
     * @param int    $count   the numbers of rows to fetch
309
     * @param mixed  $params  array, string or numeric data to be used in
310
     *                         execution of the statement.  Quantity of items
311
     *                         passed must match quantity of placeholders in
312
     *                         query:  meaning 1 placeholder for non-array
313
     *                         parameters or 1 placeholder per array element.
314
     *
315
     * @return string  the query string with LIMIT clauses added
316
     *
317
     * @access protected
318
     */
319
    function modifyLimitQuery($query, $from, $count, $params = array())
320
    {
321
        if ($this->dsn['dbsyntax'] == 'firebird') {
322
            $query = preg_replace('/^([\s(])*SELECT/i',
323
                                  "SELECT FIRST $count SKIP $from", $query);
324
        }
325
        return $query;
326
    }
327
 
328
    // }}}
329
    // {{{ nextResult()
330
 
331
    /**
332
     * Move the internal ibase result pointer to the next available result
333
     *
334
     * @param a valid fbsql result resource
335
     *
336
     * @access public
337
     *
338
     * @return true if a result is available otherwise return false
339
     */
340
    function nextResult($result)
341
    {
342
        return false;
343
    }
344
 
345
    // }}}
346
    // {{{ fetchInto()
347
 
348
    /**
349
     * Places a row from the result set into the given array
350
     *
351
     * Formating of the array and the data therein are configurable.
352
     * See DB_result::fetchInto() for more information.
353
     *
354
     * This method is not meant to be called directly.  Use
355
     * DB_result::fetchInto() instead.  It can't be declared "protected"
356
     * because DB_result is a separate object.
357
     *
358
     * @param resource $result    the query result resource
359
     * @param array    $arr       the referenced array to put the data in
360
     * @param int      $fetchmode how the resulting array should be indexed
361
     * @param int      $rownum    the row number to fetch (0 = first row)
362
     *
363
     * @return mixed  DB_OK on success, NULL when the end of a result set is
364
     *                 reached or on failure
365
     *
366
     * @see DB_result::fetchInto()
367
     */
368
    function fetchInto($result, &$arr, $fetchmode, $rownum = null)
369
    {
370
        if ($rownum !== null) {
371
            return $this->ibaseRaiseError(DB_ERROR_NOT_CAPABLE);
372
        }
373
        if ($fetchmode & DB_FETCHMODE_ASSOC) {
374
            if (function_exists('ibase_fetch_assoc')) {
375
                $arr = @ibase_fetch_assoc($result);
376
            } else {
377
                $arr = get_object_vars(ibase_fetch_object($result));
378
            }
379
            if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE && $arr) {
380
                $arr = array_change_key_case($arr, CASE_LOWER);
381
            }
382
        } else {
383
            $arr = @ibase_fetch_row($result);
384
        }
385
        if (!$arr) {
386
            return null;
387
        }
388
        if ($this->options['portability'] & DB_PORTABILITY_RTRIM) {
389
            $this->_rtrimArrayValues($arr);
390
        }
391
        if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) {
392
            $this->_convertNullArrayValuesToEmpty($arr);
393
        }
394
        return DB_OK;
395
    }
396
 
397
    // }}}
398
    // {{{ freeResult()
399
 
400
    /**
401
     * Deletes the result set and frees the memory occupied by the result set
402
     *
403
     * This method is not meant to be called directly.  Use
404
     * DB_result::free() instead.  It can't be declared "protected"
405
     * because DB_result is a separate object.
406
     *
407
     * @param resource $result  PHP's query result resource
408
     *
409
     * @return bool  TRUE on success, FALSE if $result is invalid
410
     *
411
     * @see DB_result::free()
412
     */
413
    function freeResult($result)
414
    {
415
        return @ibase_free_result($result);
416
    }
417
 
418
    // }}}
419
    // {{{ freeQuery()
420
 
421
    function freeQuery($query)
422
    {
423
        @ibase_free_query($query);
424
        return true;
425
    }
426
 
427
    // }}}
428
    // {{{ affectedRows()
429
 
430
    /**
431
     * Determines the number of rows affected by a data maniuplation query
432
     *
433
     * 0 is returned for queries that don't manipulate data.
434
     *
435
     * @return int  the number of rows.  A DB_Error object on failure.
436
     */
437
    function affectedRows()
438
    {
439
        if (is_integer($this->affected)) {
440
            return $this->affected;
441
        }
442
        return $this->ibaseRaiseError(DB_ERROR_NOT_CAPABLE);
443
    }
444
 
445
    // }}}
446
    // {{{ numCols()
447
 
448
    /**
449
     * Gets the number of columns in a result set
450
     *
451
     * This method is not meant to be called directly.  Use
452
     * DB_result::numCols() instead.  It can't be declared "protected"
453
     * because DB_result is a separate object.
454
     *
455
     * @param resource $result  PHP's query result resource
456
     *
457
     * @return int  the number of columns.  A DB_Error object on failure.
458
     *
459
     * @see DB_result::numCols()
460
     */
461
    function numCols($result)
462
    {
463
        $cols = @ibase_num_fields($result);
464
        if (!$cols) {
465
            return $this->ibaseRaiseError();
466
        }
467
        return $cols;
468
    }
469
 
470
    // }}}
471
    // {{{ prepare()
472
 
473
    /**
474
     * Prepares a query for multiple execution with execute().
475
     *
476
     * prepare() requires a generic query as string like <code>
477
     *    INSERT INTO numbers VALUES (?, ?, ?)
478
     * </code>.  The <kbd>?</kbd> characters are placeholders.
479
     *
480
     * Three types of placeholders can be used:
481
     *   + <kbd>?</kbd>  a quoted scalar value, i.e. strings, integers
482
     *   + <kbd>!</kbd>  value is inserted 'as is'
483
     *   + <kbd>&</kbd>  requires a file name.  The file's contents get
484
     *                     inserted into the query (i.e. saving binary
485
     *                     data in a db)
486
     *
487
     * Use backslashes to escape placeholder characters if you don't want
488
     * them to be interpreted as placeholders.  Example: <code>
489
     *    "UPDATE foo SET col=? WHERE col='over \& under'"
490
     * </code>
491
     *
492
     * @param string $query query to be prepared
493
     * @return mixed DB statement resource on success. DB_Error on failure.
494
     */
495
    function prepare($query)
496
    {
497
        $tokens   = preg_split('/((?<!\\\)[&?!])/', $query, -1,
498
                               PREG_SPLIT_DELIM_CAPTURE);
499
        $token    = 0;
500
        $types    = array();
501
        $newquery = '';
502
 
503
        foreach ($tokens as $key => $val) {
504
            switch ($val) {
505
                case '?':
506
                    $types[$token++] = DB_PARAM_SCALAR;
507
                    break;
508
                case '&':
509
                    $types[$token++] = DB_PARAM_OPAQUE;
510
                    break;
511
                case '!':
512
                    $types[$token++] = DB_PARAM_MISC;
513
                    break;
514
                default:
515
                    $tokens[$key] = preg_replace('/\\\([&?!])/', "\\1", $val);
516
                    $newquery .= $tokens[$key] . '?';
517
            }
518
        }
519
 
520
        $newquery = substr($newquery, 0, -1);
521
        $this->last_query = $query;
522
        $newquery = $this->modifyQuery($newquery);
523
        $stmt = @ibase_prepare($this->connection, $newquery);
524
        $this->prepare_types[(int)$stmt] = $types;
525
        $this->manip_query[(int)$stmt]   = DB::isManip($query);
526
        return $stmt;
527
    }
528
 
529
    // }}}
530
    // {{{ execute()
531
 
532
    /**
533
     * Executes a DB statement prepared with prepare().
534
     *
535
     * @param resource  $stmt  a DB statement resource returned from prepare()
536
     * @param mixed  $data  array, string or numeric data to be used in
537
     *                      execution of the statement.  Quantity of items
538
     *                      passed must match quantity of placeholders in
539
     *                      query:  meaning 1 for non-array items or the
540
     *                      quantity of elements in the array.
541
     * @return object  a new DB_Result or a DB_Error when fail
542
     * @see DB_ibase::prepare()
543
     * @access public
544
     */
545
    function &execute($stmt, $data = array())
546
    {
547
        $data = (array)$data;
548
        $this->last_parameters = $data;
549
 
550
        $types =& $this->prepare_types[(int)$stmt];
551
        if (count($types) != count($data)) {
552
            $tmp =& $this->raiseError(DB_ERROR_MISMATCH);
553
            return $tmp;
554
        }
555
 
556
        $i = 0;
557
        foreach ($data as $key => $value) {
558
            if ($types[$i] == DB_PARAM_MISC) {
559
                /*
560
                 * ibase doesn't seem to have the ability to pass a
561
                 * parameter along unchanged, so strip off quotes from start
562
                 * and end, plus turn two single quotes to one single quote,
563
                 * in order to avoid the quotes getting escaped by
564
                 * ibase and ending up in the database.
565
                 */
566
                $data[$key] = preg_replace("/^'(.*)'$/", "\\1", $data[$key]);
567
                $data[$key] = str_replace("''", "'", $data[$key]);
568
            } elseif ($types[$i] == DB_PARAM_OPAQUE) {
569
                $fp = @fopen($data[$key], 'rb');
570
                if (!$fp) {
571
                    $tmp =& $this->raiseError(DB_ERROR_ACCESS_VIOLATION);
572
                    return $tmp;
573
                }
574
                $data[$key] = fread($fp, filesize($data[$key]));
575
                fclose($fp);
576
            }
577
            $i++;
578
        }
579
 
580
        array_unshift($data, $stmt);
581
 
582
        $res = call_user_func_array('ibase_execute', $data);
583
        if (!$res) {
584
            $tmp =& $this->ibaseRaiseError();
585
            return $tmp;
586
        }
587
        /* XXX need this?
588
        if ($this->autocommit && $this->manip_query[(int)$stmt]) {
589
            @ibase_commit($this->connection);
590
        }*/
591
        $this->last_stmt = $stmt;
592
        if ($this->manip_query[(int)$stmt]) {
593
            $tmp = DB_OK;
594
        } else {
595
            $tmp =& new DB_result($this, $res);
596
        }
597
        return $tmp;
598
    }
599
 
600
    /**
601
     * Frees the internal resources associated with a prepared query
602
     *
603
     * @param resource $stmt           the prepared statement's PHP resource
604
     * @param bool     $free_resource  should the PHP resource be freed too?
605
     *                                  Use false if you need to get data
606
     *                                  from the result set later.
607
     *
608
     * @return bool  TRUE on success, FALSE if $result is invalid
609
     *
610
     * @see DB_ibase::prepare()
611
     */
612
    function freePrepared($stmt, $free_resource = true)
613
    {
614
        if (!is_resource($stmt)) {
615
            return false;
616
        }
617
        if ($free_resource) {
618
            @ibase_free_query($stmt);
619
        }
620
        unset($this->prepare_tokens[(int)$stmt]);
621
        unset($this->prepare_types[(int)$stmt]);
622
        unset($this->manip_query[(int)$stmt]);
623
        return true;
624
    }
625
 
626
    // }}}
627
    // {{{ autoCommit()
628
 
629
    /**
630
     * Enables or disables automatic commits
631
     *
632
     * @param bool $onoff  true turns it on, false turns it off
633
     *
634
     * @return int  DB_OK on success.  A DB_Error object if the driver
635
     *               doesn't support auto-committing transactions.
636
     */
637
    function autoCommit($onoff = false)
638
    {
639
        $this->autocommit = $onoff ? 1 : 0;
640
        return DB_OK;
641
    }
642
 
643
    // }}}
644
    // {{{ commit()
645
 
646
    /**
647
     * Commits the current transaction
648
     *
649
     * @return int  DB_OK on success.  A DB_Error object on failure.
650
     */
651
    function commit()
652
    {
653
        return @ibase_commit($this->connection);
654
    }
655
 
656
    // }}}
657
    // {{{ rollback()
658
 
659
    /**
660
     * Reverts the current transaction
661
     *
662
     * @return int  DB_OK on success.  A DB_Error object on failure.
663
     */
664
    function rollback()
665
    {
666
        return @ibase_rollback($this->connection);
667
    }
668
 
669
    // }}}
670
    // {{{ transactionInit()
671
 
672
    function transactionInit($trans_args = 0)
673
    {
674
        return $trans_args
675
                ? @ibase_trans($trans_args, $this->connection)
676
                : @ibase_trans();
677
    }
678
 
679
    // }}}
680
    // {{{ nextId()
681
 
682
    /**
683
     * Returns the next free id in a sequence
684
     *
685
     * @param string  $seq_name  name of the sequence
686
     * @param boolean $ondemand  when true, the seqence is automatically
687
     *                            created if it does not exist
688
     *
689
     * @return int  the next id number in the sequence.
690
     *               A DB_Error object on failure.
691
     *
692
     * @see DB_common::nextID(), DB_common::getSequenceName(),
693
     *      DB_ibase::createSequence(), DB_ibase::dropSequence()
694
     */
695
    function nextId($seq_name, $ondemand = true)
696
    {
697
        $sqn = strtoupper($this->getSequenceName($seq_name));
698
        $repeat = 0;
699
        do {
700
            $this->pushErrorHandling(PEAR_ERROR_RETURN);
701
            $result =& $this->query("SELECT GEN_ID(${sqn}, 1) "
702
                                   . 'FROM RDB$GENERATORS '
703
                                   . "WHERE RDB\$GENERATOR_NAME='${sqn}'");
704
            $this->popErrorHandling();
705
            if ($ondemand && DB::isError($result)) {
706
                $repeat = 1;
707
                $result = $this->createSequence($seq_name);
708
                if (DB::isError($result)) {
709
                    return $result;
710
                }
711
            } else {
712
                $repeat = 0;
713
            }
714
        } while ($repeat);
715
        if (DB::isError($result)) {
716
            return $this->raiseError($result);
717
        }
718
        $arr = $result->fetchRow(DB_FETCHMODE_ORDERED);
719
        $result->free();
720
        return $arr[0];
721
    }
722
 
723
    // }}}
724
    // {{{ createSequence()
725
 
726
    /**
727
     * Creates a new sequence
728
     *
729
     * @param string $seq_name  name of the new sequence
730
     *
731
     * @return int  DB_OK on success.  A DB_Error object on failure.
732
     *
733
     * @see DB_common::createSequence(), DB_common::getSequenceName(),
734
     *      DB_ibase::nextID(), DB_ibase::dropSequence()
735
     */
736
    function createSequence($seq_name)
737
    {
738
        $sqn = strtoupper($this->getSequenceName($seq_name));
739
        $this->pushErrorHandling(PEAR_ERROR_RETURN);
740
        $result = $this->query("CREATE GENERATOR ${sqn}");
741
        $this->popErrorHandling();
742
 
743
        return $result;
744
    }
745
 
746
    // }}}
747
    // {{{ dropSequence()
748
 
749
    /**
750
     * Deletes a sequence
751
     *
752
     * @param string $seq_name  name of the sequence to be deleted
753
     *
754
     * @return int  DB_OK on success.  A DB_Error object on failure.
755
     *
756
     * @see DB_common::dropSequence(), DB_common::getSequenceName(),
757
     *      DB_ibase::nextID(), DB_ibase::createSequence()
758
     */
759
    function dropSequence($seq_name)
760
    {
761
        return $this->query('DELETE FROM RDB$GENERATORS '
762
                            . "WHERE RDB\$GENERATOR_NAME='"
763
                            . strtoupper($this->getSequenceName($seq_name))
764
                            . "'");
765
    }
766
 
767
    // }}}
768
    // {{{ _ibaseFieldFlags()
769
 
770
    /**
771
     * Get the column's flags
772
     *
773
     * Supports "primary_key", "unique_key", "not_null", "default",
774
     * "computed" and "blob".
775
     *
776
     * @param string $field_name  the name of the field
777
     * @param string $table_name  the name of the table
778
     *
779
     * @return string  the flags
780
     *
781
     * @access private
782
     */
783
    function _ibaseFieldFlags($field_name, $table_name)
784
    {
785
        $sql = 'SELECT R.RDB$CONSTRAINT_TYPE CTYPE'
786
               .' FROM RDB$INDEX_SEGMENTS I'
787
               .'  JOIN RDB$RELATION_CONSTRAINTS R ON I.RDB$INDEX_NAME=R.RDB$INDEX_NAME'
788
               .' WHERE I.RDB$FIELD_NAME=\'' . $field_name . '\''
789
               .'  AND UPPER(R.RDB$RELATION_NAME)=\'' . strtoupper($table_name) . '\'';
790
 
791
        $result = @ibase_query($this->connection, $sql);
792
        if (!$result) {
793
            return $this->ibaseRaiseError();
794
        }
795
 
796
        $flags = '';
797
        if ($obj = @ibase_fetch_object($result)) {
798
            @ibase_free_result($result);
799
            if (isset($obj->CTYPE)  && trim($obj->CTYPE) == 'PRIMARY KEY') {
800
                $flags .= 'primary_key ';
801
            }
802
            if (isset($obj->CTYPE)  && trim($obj->CTYPE) == 'UNIQUE') {
803
                $flags .= 'unique_key ';
804
            }
805
        }
806
 
807
        $sql = 'SELECT R.RDB$NULL_FLAG AS NFLAG,'
808
               .'  R.RDB$DEFAULT_SOURCE AS DSOURCE,'
809
               .'  F.RDB$FIELD_TYPE AS FTYPE,'
810
               .'  F.RDB$COMPUTED_SOURCE AS CSOURCE'
811
               .' FROM RDB$RELATION_FIELDS R '
812
               .'  JOIN RDB$FIELDS F ON R.RDB$FIELD_SOURCE=F.RDB$FIELD_NAME'
813
               .' WHERE UPPER(R.RDB$RELATION_NAME)=\'' . strtoupper($table_name) . '\''
814
               .'  AND R.RDB$FIELD_NAME=\'' . $field_name . '\'';
815
 
816
        $result = @ibase_query($this->connection, $sql);
817
        if (!$result) {
818
            return $this->ibaseRaiseError();
819
        }
820
        if ($obj = @ibase_fetch_object($result)) {
821
            @ibase_free_result($result);
822
            if (isset($obj->NFLAG)) {
823
                $flags .= 'not_null ';
824
            }
825
            if (isset($obj->DSOURCE)) {
826
                $flags .= 'default ';
827
            }
828
            if (isset($obj->CSOURCE)) {
829
                $flags .= 'computed ';
830
            }
831
            if (isset($obj->FTYPE)  && $obj->FTYPE == 261) {
832
                $flags .= 'blob ';
833
            }
834
        }
835
 
836
        return trim($flags);
837
    }
838
 
839
    // }}}
840
    // {{{ ibaseRaiseError()
841
 
842
    /**
843
     * Produces a DB_Error object regarding the current problem
844
     *
845
     * @param int $errno  if the error is being manually raised pass a
846
     *                     DB_ERROR* constant here.  If this isn't passed
847
     *                     the error information gathered from the DBMS.
848
     *
849
     * @return object  the DB_Error object
850
     *
851
     * @see DB_common::raiseError(),
852
     *      DB_ibase::errorNative(), DB_ibase::errorCode()
853
     */
854
    function &ibaseRaiseError($errno = null)
855
    {
856
        if ($errno === null) {
857
            $errno = $this->errorCode($this->errorNative());
858
        }
859
        $tmp =& $this->raiseError($errno, null, null, null, @ibase_errmsg());
860
        return $tmp;
861
    }
862
 
863
    // }}}
864
    // {{{ errorNative()
865
 
866
    /**
867
     * Gets the DBMS' native error code produced by the last query
868
     *
869
     * @return int  the DBMS' error code.  NULL if there is no error code.
870
     *
871
     * @since Method available since Release 1.7.0
872
     */
873
    function errorNative()
874
    {
875
        if (function_exists('ibase_errcode')) {
876
            return @ibase_errcode();
877
        }
878
        if (preg_match('/^Dynamic SQL Error SQL error code = ([0-9-]+)/i',
879
                       @ibase_errmsg(), $m)) {
880
            return (int)$m[1];
881
        }
882
        return null;
883
    }
884
 
885
    // }}}
886
    // {{{ errorCode()
887
 
888
    /**
889
     * Maps native error codes to DB's portable ones
890
     *
891
     * @param int $nativecode  the error code returned by the DBMS
892
     *
893
     * @return int  the portable DB error code.  Return DB_ERROR if the
894
     *               current driver doesn't have a mapping for the
895
     *               $nativecode submitted.
896
     *
897
     * @since Method available since Release 1.7.0
898
     */
899
    function errorCode($nativecode = null)
900
    {
901
        if (isset($this->errorcode_map[$nativecode])) {
902
            return $this->errorcode_map[$nativecode];
903
        }
904
 
905
        static $error_regexps;
906
        if (!isset($error_regexps)) {
907
            $error_regexps = array(
908
                '/generator .* is not defined/'
909
                    => DB_ERROR_SYNTAX,  // for compat. w ibase_errcode()
910
                '/table.*(not exist|not found|unknown)/i'
911
                    => DB_ERROR_NOSUCHTABLE,
912
                '/table .* already exists/i'
913
                    => DB_ERROR_ALREADY_EXISTS,
914
                '/unsuccessful metadata update .* failed attempt to store duplicate value/i'
915
                    => DB_ERROR_ALREADY_EXISTS,
916
                '/unsuccessful metadata update .* not found/i'
917
                    => DB_ERROR_NOT_FOUND,
918
                '/validation error for column .* value "\*\*\* null/i'
919
                    => DB_ERROR_CONSTRAINT_NOT_NULL,
920
                '/violation of [\w ]+ constraint/i'
921
                    => DB_ERROR_CONSTRAINT,
922
                '/conversion error from string/i'
923
                    => DB_ERROR_INVALID_NUMBER,
924
                '/no permission for/i'
925
                    => DB_ERROR_ACCESS_VIOLATION,
926
                '/arithmetic exception, numeric overflow, or string truncation/i'
927
                    => DB_ERROR_INVALID,
928
            );
929
        }
930
 
931
        $errormsg = @ibase_errmsg();
932
        foreach ($error_regexps as $regexp => $code) {
933
            if (preg_match($regexp, $errormsg)) {
934
                return $code;
935
            }
936
        }
937
        return DB_ERROR;
938
    }
939
 
940
    // }}}
941
    // {{{ tableInfo()
942
 
943
    /**
944
     * Returns information about a table or a result set
945
     *
946
     * NOTE: only supports 'table' and 'flags' if <var>$result</var>
947
     * is a table name.
948
     *
949
     * @param object|string  $result  DB_result object from a query or a
950
     *                                 string containing the name of a table.
951
     *                                 While this also accepts a query result
952
     *                                 resource identifier, this behavior is
953
     *                                 deprecated.
954
     * @param int            $mode    a valid tableInfo mode
955
     *
956
     * @return array  an associative array with the information requested.
957
     *                 A DB_Error object on failure.
958
     *
959
     * @see DB_common::tableInfo()
960
     */
961
    function tableInfo($result, $mode = null)
962
    {
963
        if (is_string($result)) {
964
            /*
965
             * Probably received a table name.
966
             * Create a result resource identifier.
967
             */
968
            $id = @ibase_query($this->connection,
969
                               "SELECT * FROM $result WHERE 1=0");
970
            $got_string = true;
971
        } elseif (isset($result->result)) {
972
            /*
973
             * Probably received a result object.
974
             * Extract the result resource identifier.
975
             */
976
            $id = $result->result;
977
            $got_string = false;
978
        } else {
979
            /*
980
             * Probably received a result resource identifier.
981
             * Copy it.
982
             * Deprecated.  Here for compatibility only.
983
             */
984
            $id = $result;
985
            $got_string = false;
986
        }
987
 
988
        if (!is_resource($id)) {
989
            return $this->ibaseRaiseError(DB_ERROR_NEED_MORE_DATA);
990
        }
991
 
992
        if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) {
993
            $case_func = 'strtolower';
994
        } else {
995
            $case_func = 'strval';
996
        }
997
 
998
        $count = @ibase_num_fields($id);
999
        $res   = array();
1000
 
1001
        if ($mode) {
1002
            $res['num_fields'] = $count;
1003
        }
1004
 
1005
        for ($i = 0; $i < $count; $i++) {
1006
            $info = @ibase_field_info($id, $i);
1007
            $res[$i] = array(
1008
                'table' => $got_string ? $case_func($result) : '',
1009
                'name'  => $case_func($info['name']),
1010
                'type'  => $info['type'],
1011
                'len'   => $info['length'],
1012
                'flags' => ($got_string)
1013
                            ? $this->_ibaseFieldFlags($info['name'], $result)
1014
                            : '',
1015
            );
1016
            if ($mode & DB_TABLEINFO_ORDER) {
1017
                $res['order'][$res[$i]['name']] = $i;
1018
            }
1019
            if ($mode & DB_TABLEINFO_ORDERTABLE) {
1020
                $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i;
1021
            }
1022
        }
1023
 
1024
        // free the result only if we were called on a table
1025
        if ($got_string) {
1026
            @ibase_free_result($id);
1027
        }
1028
        return $res;
1029
    }
1030
 
1031
    // }}}
1032
    // {{{ getSpecialQuery()
1033
 
1034
    /**
1035
     * Obtains the query string needed for listing a given type of objects
1036
     *
1037
     * @param string $type  the kind of objects you want to retrieve
1038
     *
1039
     * @return string  the SQL query string or null if the driver doesn't
1040
     *                  support the object type requested
1041
     *
1042
     * @access protected
1043
     * @see DB_common::getListOf()
1044
     */
1045
    function getSpecialQuery($type)
1046
    {
1047
        switch ($type) {
1048
            case 'tables':
1049
                return 'SELECT DISTINCT R.RDB$RELATION_NAME FROM '
1050
                       . 'RDB$RELATION_FIELDS R WHERE R.RDB$SYSTEM_FLAG=0';
1051
            case 'views':
1052
                return 'SELECT DISTINCT RDB$VIEW_NAME from RDB$VIEW_RELATIONS';
1053
            case 'users':
1054
                return 'SELECT DISTINCT RDB$USER FROM RDB$USER_PRIVILEGES';
1055
            default:
1056
                return null;
1057
        }
1058
    }
1059
 
1060
    // }}}
1061
 
1062
}
1063
 
1064
/*
1065
 * Local variables:
1066
 * tab-width: 4
1067
 * c-basic-offset: 4
1068
 * End:
1069
 */
1070
 
1071
?>