1: <?php
2:
3: 4: 5: 6: 7: 8: 9: 10:
11:
12:
13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27:
28: class DibiFirebirdDriver extends DibiObject implements IDibiDriver, IDibiResultDriver, IDibiReflector
29: {
30: const ERROR_EXCEPTION_THROWN = -836;
31:
32:
33: private $connection;
34:
35:
36: private $resultSet;
37:
38:
39: private $transaction;
40:
41:
42: private $inTransaction = FALSE;
43:
44:
45: 46: 47:
48: public function __construct()
49: {
50: if (!extension_loaded('interbase')) {
51: throw new DibiNotSupportedException("PHP extension 'interbase' is not loaded.");
52: }
53: }
54:
55:
56:
57: 58: 59: 60: 61:
62: public function connect(array &$config)
63: {
64: DibiConnection::alias($config, 'database', 'db');
65:
66: if (isset($config['resource'])) {
67: $this->connection = $config['resource'];
68:
69: } else {
70:
71: if (!isset($config['username'])) $config['username'] = ini_get('ibase.default_password');
72: if (!isset($config['password'])) $config['password'] = ini_get('ibase.default_user');
73: if (!isset($config['database'])) $config['database'] = ini_get('ibase.default_db');
74: if (!isset($config['charset'])) $config['charset'] = ini_get('ibase.default_charset');
75: if (!isset($config['buffers'])) $config['buffers'] = 0;
76:
77: DibiDriverException::tryError();
78: if (empty($config['persistent'])) {
79: $this->connection = ibase_connect($config['database'], $config['username'], $config['password'], $config['charset'], $config['buffers']);
80: } else {
81: $this->connection = ibase_pconnect($config['database'], $config['username'], $config['password'], $config['charset'], $config['buffers']);
82: }
83: if (DibiDriverException::catchError($msg)) {
84: throw new DibiDriverException($msg, ibase_errcode());
85: }
86:
87: if (!is_resource($this->connection)) {
88: throw new DibiDriverException(ibase_errmsg(), ibase_errcode());
89: }
90: }
91:
92: }
93:
94:
95:
96: 97: 98: 99:
100: public function disconnect()
101: {
102: ibase_close($this->connection);
103: }
104:
105:
106:
107: 108: 109: 110: 111: 112:
113: public function query($sql)
114: {
115: DibiDriverException::tryError();
116: $resource = $this->inTransaction ? $this->transaction : $this->connection;
117: $res = ibase_query($resource, $sql);
118:
119: if (DibiDriverException::catchError($msg)) {
120: if (ibase_errcode() == self::ERROR_EXCEPTION_THROWN) {
121: preg_match('/exception (\d+) (\w+) (.*)/i', ibase_errmsg(), $match);
122: throw new DibiProcedureException($match[3], $match[1], $match[2], dibi::$sql);
123:
124: } else {
125: throw new DibiDriverException(ibase_errmsg(), ibase_errcode(), dibi::$sql);
126: }
127: }
128:
129: if ($res === FALSE) {
130: throw new DibiDriverException(ibase_errmsg(), ibase_errcode(), $sql);
131:
132: } elseif (is_resource($res)) {
133: return $this->createResultDriver($res);
134: }
135: }
136:
137:
138:
139: 140: 141: 142:
143: public function getAffectedRows()
144: {
145: return ibase_affected_rows($this->connection);
146: }
147:
148:
149:
150: 151: 152: 153: 154:
155: public function getInsertId($sequence)
156: {
157: return ibase_gen_id($sequence, 0, $this->connection);
158: }
159:
160:
161:
162: 163: 164: 165: 166: 167:
168: public function begin($savepoint = NULL)
169: {
170: if ($savepoint !== NULL) {
171: throw new DibiNotSupportedException('Savepoints are not supported in Firebird/Interbase.');
172: }
173: $this->transaction = ibase_trans($this->resource);
174: $this->inTransaction = TRUE;
175: }
176:
177:
178:
179: 180: 181: 182: 183: 184:
185: public function commit($savepoint = NULL)
186: {
187: if ($savepoint !== NULL) {
188: throw new DibiNotSupportedException('Savepoints are not supported in Firebird/Interbase.');
189: }
190:
191: if (!ibase_commit($this->transaction)) {
192: throw new DibiDriverException('Unable to handle operation - failure when commiting transaction.');
193: }
194:
195: $this->inTransaction = FALSE;
196: }
197:
198:
199:
200: 201: 202: 203: 204: 205:
206: public function rollback($savepoint = NULL)
207: {
208: if ($savepoint !== NULL) {
209: throw new DibiNotSupportedException('Savepoints are not supported in Firebird/Interbase.');
210: }
211:
212: if (!ibase_rollback($this->transaction)) {
213: throw new DibiDriverException('Unable to handle operation - failure when rolbacking transaction.');
214: }
215:
216: $this->inTransaction = FALSE;
217: }
218:
219:
220:
221: 222: 223: 224:
225: public function inTransaction()
226: {
227: return $this->inTransaction;
228: }
229:
230:
231:
232: 233: 234: 235:
236: public function getResource()
237: {
238: return $this->connection;
239: }
240:
241:
242:
243: 244: 245: 246:
247: public function getReflector()
248: {
249: return $this;
250: }
251:
252:
253:
254: 255: 256: 257: 258:
259: public function createResultDriver($resource)
260: {
261: $res = clone $this;
262: $res->resultSet = $resource;
263: return $res;
264: }
265:
266:
267:
268:
269:
270:
271:
272: 273: 274: 275: 276: 277: 278:
279: public function escape($value, $type)
280: {
281: switch ($type) {
282: case dibi::TEXT:
283: case dibi::BINARY:
284: return "'" . str_replace("'", "''", $value) . "'";
285:
286: case dibi::IDENTIFIER:
287: return $value;
288:
289: case dibi::BOOL:
290: return $value ? 1 : 0;
291:
292: case dibi::DATE:
293: return $value instanceof DateTime ? $value->format("'Y-m-d'") : date("'Y-m-d'", $value);
294:
295: case dibi::DATETIME:
296: return $value instanceof DateTime ? $value->format("'Y-m-d H:i:s'") : date("'Y-m-d H:i:s'", $value);
297:
298: default:
299: throw new InvalidArgumentException('Unsupported type.');
300: }
301: }
302:
303:
304:
305: 306: 307: 308: 309: 310:
311: public function escapeLike($value, $pos)
312: {
313: throw new DibiNotImplementedException;
314: }
315:
316:
317:
318: 319: 320: 321: 322: 323: 324:
325: public function unescape($value, $type)
326: {
327: if ($type === dibi::BINARY) {
328: return $value;
329: }
330: throw new InvalidArgumentException('Unsupported type.');
331: }
332:
333:
334:
335: 336: 337: 338: 339: 340: 341:
342: public function applyLimit(&$sql, $limit, $offset)
343: {
344: if ($limit < 0 && $offset < 1) return;
345:
346:
347: $sql = 'SELECT FIRST ' . (int) $limit . ($offset > 0 ? ' SKIP ' . (int) $offset : '') . ' * FROM (' . $sql . ')';
348: }
349:
350:
351:
352:
353:
354:
355:
356: 357: 358: 359:
360: public function __destruct()
361: {
362: $this->resultSet && @$this->free();
363: }
364:
365:
366:
367: 368: 369: 370:
371: public function getRowCount()
372: {
373: return ibase_num_fields($this->resultSet);
374: }
375:
376:
377:
378: 379: 380: 381: 382:
383: public function fetch($assoc)
384: {
385: DibiDriverException::tryError();
386: $result = $assoc ? ibase_fetch_assoc($this->resultSet, IBASE_TEXT) : ibase_fetch_row($this->resultSet, IBASE_TEXT);
387:
388: if (DibiDriverException::catchError($msg)) {
389: if (ibase_errcode() == self::ERROR_EXCEPTION_THROWN) {
390: preg_match('/exception (\d+) (\w+) (.*)/i', ibase_errmsg(), $match);
391: throw new DibiProcedureException($match[3], $match[1], $match[2], dibi::$sql);
392:
393: } else {
394: throw new DibiDriverException($msg, ibase_errcode(), dibi::$sql);
395: }
396: }
397:
398: return $result;
399: }
400:
401:
402:
403: 404: 405: 406: 407: 408:
409: public function seek($row)
410: {
411: throw new DibiNotSupportedException("Firebird/Interbase do not support seek in result set.");
412: }
413:
414:
415:
416: 417: 418: 419:
420: public function free()
421: {
422: ibase_free_result($this->resultSet);
423: $this->resultSet = NULL;
424: }
425:
426:
427:
428: 429: 430: 431:
432: public function getResultResource()
433: {
434: return $this->resultSet;
435: }
436:
437:
438:
439: 440: 441: 442:
443: public function getResultColumns()
444: {
445: $count = ibase_num_fields($this->resultSet);
446: $columns = array();
447: for ($i = 0; $i < $count; $i++) {
448: $row = (array) ibase_field_info($this->resultSet, $i);
449: $columns[] = array(
450: 'name' => $row['name'],
451: 'fullname' => $row['name'],
452: 'table' => $row['relation'],
453: 'nativetype' => $row['type'],
454: );
455: }
456: return $columns;
457: }
458:
459:
460:
461:
462:
463:
464:
465: 466: 467: 468:
469: public function getTables()
470: {
471: $res = $this->query("
472: SELECT TRIM(RDB\$RELATION_NAME),
473: CASE RDB\$VIEW_BLR WHEN NULL THEN 'TRUE' ELSE 'FALSE' END
474: FROM RDB\$RELATIONS
475: WHERE RDB\$SYSTEM_FLAG = 0;"
476: );
477: $tables = array();
478: while ($row = $res->fetch(FALSE)) {
479: $tables[] = array(
480: 'name' => $row[0],
481: 'view' => $row[1] === 'TRUE',
482: );
483: }
484: return $tables;
485: }
486:
487:
488:
489: 490: 491: 492: 493:
494: public function getColumns($table)
495: {
496: $table = strtoupper($table);
497: $res = $this->query("
498: SELECT TRIM(r.RDB\$FIELD_NAME) AS FIELD_NAME,
499: CASE f.RDB\$FIELD_TYPE
500: WHEN 261 THEN 'BLOB'
501: WHEN 14 THEN 'CHAR'
502: WHEN 40 THEN 'CSTRING'
503: WHEN 11 THEN 'D_FLOAT'
504: WHEN 27 THEN 'DOUBLE'
505: WHEN 10 THEN 'FLOAT'
506: WHEN 16 THEN 'INT64'
507: WHEN 8 THEN 'INTEGER'
508: WHEN 9 THEN 'QUAD'
509: WHEN 7 THEN 'SMALLINT'
510: WHEN 12 THEN 'DATE'
511: WHEN 13 THEN 'TIME'
512: WHEN 35 THEN 'TIMESTAMP'
513: WHEN 37 THEN 'VARCHAR'
514: ELSE 'UNKNOWN'
515: END AS FIELD_TYPE,
516: f.RDB\$FIELD_LENGTH AS FIELD_LENGTH,
517: r.RDB\$DEFAULT_VALUE AS DEFAULT_VALUE,
518: CASE r.RDB\$NULL_FLAG
519: WHEN 1 THEN 'FALSE' ELSE 'TRUE'
520: END AS NULLABLE
521: FROM RDB\$RELATION_FIELDS r
522: LEFT JOIN RDB\$FIELDS f ON r.RDB\$FIELD_SOURCE = f.RDB\$FIELD_NAME
523: WHERE r.RDB\$RELATION_NAME = '$table'
524: ORDER BY r.RDB\$FIELD_POSITION;"
525:
526: );
527: $columns = array();
528: while ($row = $res->fetch(TRUE)) {
529: $key = $row['FIELD_NAME'];
530: $columns[$key] = array(
531: 'name' => $key,
532: 'table' => $table,
533: 'nativetype' => trim($row['FIELD_TYPE']),
534: 'size' => $row['FIELD_LENGTH'],
535: 'nullable' => $row['NULLABLE'] === 'TRUE',
536: 'default' => $row['DEFAULT_VALUE'],
537: 'autoincrement' => FALSE,
538: );
539: }
540: return $columns;
541: }
542:
543:
544:
545: 546: 547: 548: 549:
550: public function getIndexes($table)
551: {
552: $table = strtoupper($table);
553: $res = $this->query("
554: SELECT TRIM(s.RDB\$INDEX_NAME) AS INDEX_NAME,
555: TRIM(s.RDB\$FIELD_NAME) AS FIELD_NAME,
556: i.RDB\$UNIQUE_FLAG AS UNIQUE_FLAG,
557: i.RDB\$FOREIGN_KEY AS FOREIGN_KEY,
558: TRIM(r.RDB\$CONSTRAINT_TYPE) AS CONSTRAINT_TYPE,
559: s.RDB\$FIELD_POSITION AS FIELD_POSITION
560: FROM RDB\$INDEX_SEGMENTS s
561: LEFT JOIN RDB\$INDICES i ON i.RDB\$INDEX_NAME = s.RDB\$INDEX_NAME
562: LEFT JOIN RDB\$RELATION_CONSTRAINTS r ON r.RDB\$INDEX_NAME = s.RDB\$INDEX_NAME
563: WHERE UPPER(i.RDB\$RELATION_NAME) = '$table'
564: ORDER BY s.RDB\$FIELD_POSITION"
565: );
566: $indexes = array();
567: while ($row = $res->fetch(TRUE)) {
568: $key = $row['INDEX_NAME'];
569: $indexes[$key]['name'] = $key;
570: $indexes[$key]['unique'] = $row['UNIQUE_FLAG'] === 1;
571: $indexes[$key]['primary'] = $row['CONSTRAINT_TYPE'] === 'PRIMARY KEY';
572: $indexes[$key]['table'] = $table;
573: $indexes[$key]['columns'][$row['FIELD_POSITION']] = $row['FIELD_NAME'];
574: }
575: return $indexes;
576: }
577:
578:
579:
580: 581: 582: 583: 584:
585: public function getForeignKeys($table)
586: {
587: $table = strtoupper($table);
588: $res = $this->query("
589: SELECT TRIM(s.RDB\$INDEX_NAME) AS INDEX_NAME,
590: TRIM(s.RDB\$FIELD_NAME) AS FIELD_NAME,
591: FROM RDB\$INDEX_SEGMENTS s
592: LEFT JOIN RDB\$RELATION_CONSTRAINTS r ON r.RDB\$INDEX_NAME = s.RDB\$INDEX_NAME
593: WHERE UPPER(i.RDB\$RELATION_NAME) = '$table'
594: AND r.RDB\$CONSTRAINT_TYPE = 'FOREIGN KEY'
595: ORDER BY s.RDB\$FIELD_POSITION"
596: );
597: $keys = array();
598: while ($row = $res->fetch(TRUE)) {
599: $key = $row['INDEX_NAME'];
600: $keys[$key] = array(
601: 'name' => $key,
602: 'column' => $row['FIELD_NAME'],
603: 'table' => $table,
604: );
605: }
606: return $keys;
607: }
608:
609:
610:
611: 612: 613: 614: 615:
616: public function getIndices($table)
617: {
618: $res = $this->query("
619: SELECT TRIM(RDB\$INDEX_NAME)
620: FROM RDB\$INDICES
621: WHERE RDB\$RELATION_NAME = UPPER('$table')
622: AND RDB\$UNIQUE_FLAG IS NULL
623: AND RDB\$FOREIGN_KEY IS NULL;"
624: );
625: $indices = array();
626: while ($row = $res->fetch(FALSE)) {
627: $indices[] = $row[0];
628: }
629: return $indices;
630: }
631:
632:
633:
634: 635: 636: 637: 638:
639: public function getConstraints($table)
640: {
641: $res = $this->query("
642: SELECT TRIM(RDB\$INDEX_NAME)
643: FROM RDB\$INDICES
644: WHERE RDB\$RELATION_NAME = UPPER('$table')
645: AND (
646: RDB\$UNIQUE_FLAG IS NOT NULL
647: OR RDB\$FOREIGN_KEY IS NOT NULL
648: );"
649: );
650: $constraints = array();
651: while ($row = $res->fetch(FALSE)) {
652: $constraints[] = $row[0];
653: }
654: return $constraints;
655: }
656:
657:
658:
659: 660: 661: 662: 663: 664: 665:
666: public function getTriggersMeta($table = NULL)
667: {
668: $res = $this->query("
669: SELECT TRIM(RDB\$TRIGGER_NAME) AS TRIGGER_NAME,
670: TRIM(RDB\$RELATION_NAME) AS TABLE_NAME,
671: CASE RDB\$TRIGGER_TYPE
672: WHEN 1 THEN 'BEFORE'
673: WHEN 2 THEN 'AFTER'
674: WHEN 3 THEN 'BEFORE'
675: WHEN 4 THEN 'AFTER'
676: WHEN 5 THEN 'BEFORE'
677: WHEN 6 THEN 'AFTER'
678: END AS TRIGGER_TYPE,
679: CASE RDB\$TRIGGER_TYPE
680: WHEN 1 THEN 'INSERT'
681: WHEN 2 THEN 'INSERT'
682: WHEN 3 THEN 'UPDATE'
683: WHEN 4 THEN 'UPDATE'
684: WHEN 5 THEN 'DELETE'
685: WHEN 6 THEN 'DELETE'
686: END AS TRIGGER_EVENT,
687: CASE RDB\$TRIGGER_INACTIVE
688: WHEN 1 THEN 'FALSE' ELSE 'TRUE'
689: END AS TRIGGER_ENABLED
690: FROM RDB\$TRIGGERS
691: WHERE RDB\$SYSTEM_FLAG = 0"
692: . ($table === NULL ? ";" : " AND RDB\$RELATION_NAME = UPPER('$table');")
693: );
694: $triggers = array();
695: while ($row = $res->fetch(TRUE)) {
696: $triggers[$row['TRIGGER_NAME']] = array(
697: 'name' => $row['TRIGGER_NAME'],
698: 'table' => $row['TABLE_NAME'],
699: 'type' => trim($row['TRIGGER_TYPE']),
700: 'event' => trim($row['TRIGGER_EVENT']),
701: 'enabled' => trim($row['TRIGGER_ENABLED']) === 'TRUE',
702: );
703: }
704: return $triggers;
705: }
706:
707:
708:
709: 710: 711: 712: 713: 714:
715: public function getTriggers($table = NULL)
716: {
717: $q = "SELECT TRIM(RDB\$TRIGGER_NAME)
718: FROM RDB\$TRIGGERS
719: WHERE RDB\$SYSTEM_FLAG = 0";
720: $q .= $table === NULL ? ";" : " AND RDB\$RELATION_NAME = UPPER('$table')";
721:
722: $res = $this->query($q);
723: $triggers = array();
724: while ($row = $res->fetch(FALSE)) {
725: $triggers[] = $row[0];
726: }
727: return $triggers;
728: }
729:
730:
731:
732: 733: 734: 735: 736:
737: public function getProceduresMeta()
738: {
739: $res = $this->query("
740: SELECT
741: TRIM(p.RDB\$PARAMETER_NAME) AS PARAMETER_NAME,
742: TRIM(p.RDB\$PROCEDURE_NAME) AS PROCEDURE_NAME,
743: CASE p.RDB\$PARAMETER_TYPE
744: WHEN 0 THEN 'INPUT'
745: WHEN 1 THEN 'OUTPUT'
746: ELSE 'UNKNOWN'
747: END AS PARAMETER_TYPE,
748: CASE f.RDB\$FIELD_TYPE
749: WHEN 261 THEN 'BLOB'
750: WHEN 14 THEN 'CHAR'
751: WHEN 40 THEN 'CSTRING'
752: WHEN 11 THEN 'D_FLOAT'
753: WHEN 27 THEN 'DOUBLE'
754: WHEN 10 THEN 'FLOAT'
755: WHEN 16 THEN 'INT64'
756: WHEN 8 THEN 'INTEGER'
757: WHEN 9 THEN 'QUAD'
758: WHEN 7 THEN 'SMALLINT'
759: WHEN 12 THEN 'DATE'
760: WHEN 13 THEN 'TIME'
761: WHEN 35 THEN 'TIMESTAMP'
762: WHEN 37 THEN 'VARCHAR'
763: ELSE 'UNKNOWN'
764: END AS FIELD_TYPE,
765: f.RDB\$FIELD_LENGTH AS FIELD_LENGTH,
766: p.RDB\$PARAMETER_NUMBER AS PARAMETER_NUMBER
767: FROM RDB\$PROCEDURE_PARAMETERS p
768: LEFT JOIN RDB\$FIELDS f ON f.RDB\$FIELD_NAME = p.RDB\$FIELD_SOURCE
769: ORDER BY p.RDB\$PARAMETER_TYPE, p.RDB\$PARAMETER_NUMBER;"
770: );
771: $procedures = array();
772: while ($row = $res->fetch(TRUE)) {
773: $key = $row['PROCEDURE_NAME'];
774: $io = trim($row['PARAMETER_TYPE']);
775: $num = $row['PARAMETER_NUMBER'];
776: $procedures[$key]['name'] = $row['PROCEDURE_NAME'];
777: $procedures[$key]['params'][$io][$num]['name'] = $row['PARAMETER_NAME'];
778: $procedures[$key]['params'][$io][$num]['type'] = trim($row['FIELD_TYPE']);
779: $procedures[$key]['params'][$io][$num]['size'] = $row['FIELD_LENGTH'];
780: }
781: return $procedures;
782: }
783:
784:
785:
786: 787: 788: 789:
790: public function getProcedures()
791: {
792: $res = $this->query("
793: SELECT TRIM(RDB\$PROCEDURE_NAME)
794: FROM RDB\$PROCEDURES;"
795: );
796: $procedures = array();
797: while ($row = $res->fetch(FALSE)) {
798: $procedures[] = $row[0];
799: }
800: return $procedures;
801: }
802:
803:
804:
805: 806: 807: 808:
809: public function getGenerators()
810: {
811: $res = $this->query("
812: SELECT TRIM(RDB\$GENERATOR_NAME)
813: FROM RDB\$GENERATORS
814: WHERE RDB\$SYSTEM_FLAG = 0;"
815: );
816: $generators = array();
817: while ($row = $res->fetch(FALSE)) {
818: $generators[] = $row[0];
819: }
820: return $generators;
821: }
822:
823:
824:
825: 826: 827: 828:
829: public function getFunctions()
830: {
831: $res = $this->query("
832: SELECT TRIM(RDB\$FUNCTION_NAME)
833: FROM RDB\$FUNCTIONS
834: WHERE RDB\$SYSTEM_FLAG = 0;"
835: );
836: $functions = array();
837: while ($row = $res->fetch(FALSE)) {
838: $functions[] = $row[0];
839: }
840: return $functions;
841: }
842:
843: }
844:
845:
846:
847:
848: 849: 850: 851: 852: 853: 854:
855: class DibiProcedureException extends DibiException
856: {
857:
858: protected $severity;
859:
860:
861: 862: 863: 864: 865: 866:
867: public function __construct($message = NULL, $code = 0, $severity = NULL, $sql = NULL)
868: {
869: parent::__construct($message, (int) $code, $sql);
870: $this->severity = $severity;
871: }
872:
873:
874:
875: 876: 877: 878:
879: public function getSeverity()
880: {
881: $this->severity;
882: }
883:
884: }
885: