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