1: <?php
2: 3: 4: 5: 6:
7: class QPostgreSqlPdoDatabase extends QPdoDatabase {
8: const Adapter = 'PostgreSQL PDO Database Adapter';
9: const PDO_PGSQL_DSN_IDENTIFIER = 'pgsql';
10: protected $blnOnlyFullGroupBy = true;
11:
12:
13: public function InsertOrUpdate($strTable, $mixColumnsAndValuesArray, $strPKNames = null) {
14: $strEscapedArray = $this->EscapeIdentifiersAndValues($mixColumnsAndValuesArray);
15: $strColumns = array_keys($strEscapedArray);
16: $strUpdateStatement = '';
17: foreach ($strEscapedArray as $strColumn => $strValue) {
18: if ($strUpdateStatement) $strUpdateStatement .= ', ';
19: $strUpdateStatement .= $strColumn . ' = ' . $strValue;
20: }
21: if (is_null($strPKNames)) {
22: $strPKNames = array($strColumns[0]);
23: } else if (is_array($strPKNames)) {
24: $strPKNames = $this->EscapeIdentifiers($strPKNames);
25: } else {
26: $strPKNames = array($this->EscapeIdentifier($strPKNames));
27: }
28: $strMatchCondition = '';
29: foreach ($strPKNames as $strPKName) {
30: if ($strMatchCondition) $strMatchCondition .= ' AND ';
31: $strMatchCondition .= $strPKName.' = '.$strEscapedArray[$strPKName];
32: }
33: $strTable = $this->EscapeIdentifierBegin . $strTable . $this->EscapeIdentifierEnd;
34: $strUpdateSql = sprintf('UPDATE %s SET %s WHERE %s',
35: $strTable, $strUpdateStatement, $strMatchCondition);
36: $strInsertSql = sprintf('INSERT INTO (%s) %s SELECT %s WHERE NOT EXISTS (SELECT 1 FROM %s WHERE %s)',
37: $strTable,
38: implode(', ', $strColumns),
39: implode(', ', array_values($strEscapedArray)),
40: $strTable, $strMatchCondition);
41: $this->TransactionBegin();
42: try {
43: $this->ExecuteNonQuery($strUpdateSql);
44: $this->ExecuteNonQuery($strInsertSql);
45: $this->TransactionCommit();
46: } catch (Exception $ex) {
47: $this->TransactionRollback();
48: throw $ex;
49: }
50: }
51:
52: public function Connect() {
53:
54: $strDsn = sprintf("%s:host=%s;dbname=%s;port=%s",QPostgreSqlPdoDatabase::PDO_PGSQL_DSN_IDENTIFIER, $this->Server, $this->Database, $this->Port);
55:
56:
57: try {
58: $this->objPdo = new PDO($strDsn, $this->Username, $this->Password);
59: } catch (PDOException $expPgSql) {
60: throw new QPostgreSqlDatabaseException(sprintf("Unable to connect to Database: %s",$expPgSql->getMessage()), -1, null);
61: }
62:
63: $this->blnConnectedFlag = true;
64: }
65:
66: public function Close() {
67: parent::Close();
68:
69:
70: $this->blnConnectedFlag = false;
71: }
72:
73: public function SqlVariable($mixData, $blnIncludeEquality = false, $blnReverseEquality = false) {
74:
75: if (is_bool($mixData)) {
76:
77: if ($blnIncludeEquality) {
78:
79:
80: if ($blnReverseEquality) {
81:
82:
83:
84: if (is_null($mixData))
85: return 'IS NOT NULL';
86: else if ($mixData)
87: return "= '0'";
88: else
89: return "!= '0'";
90: } else {
91:
92: if (is_null($mixData))
93: return 'IS NULL';
94: else if ($mixData)
95: return "!= '0'";
96: else
97: return "= '0'";
98: }
99: } else {
100:
101: if (is_null($mixData))
102: return 'NULL';
103: else if ($mixData)
104: return "'1'";
105: else
106: return "'0'";
107: }
108: }
109:
110:
111: if ($blnIncludeEquality) {
112: if ($blnReverseEquality) {
113: if (is_null($mixData))
114: $strToReturn = 'IS NOT ';
115: else
116: $strToReturn = '!= ';
117: } else {
118: if (is_null($mixData))
119: $strToReturn = 'IS ';
120: else
121: $strToReturn = '= ';
122: }
123: } else
124: $strToReturn = '';
125:
126:
127: if (is_null($mixData))
128: return $strToReturn . 'NULL';
129:
130:
131: if (is_integer($mixData) || is_float($mixData))
132: return $strToReturn . sprintf('%s', $mixData);
133:
134:
135: if ($mixData instanceof QDateTime) {
136: if ($mixData->IsTimeNull()) {
137: if ($mixData->IsDateNull()) {
138: return $strToReturn . 'NULL';
139: }
140: return $strToReturn . sprintf("'%s'", $mixData->qFormat('YYYY-MM-DD'));
141: } elseif ($mixData->IsDateNull()) {
142: return $strToReturn . sprintf("'%s'", $mixData->qFormat('hhhh:mm:ss'));
143: } else {
144: return $strToReturn . sprintf("'%s'", $mixData->qFormat(QDateTime::FormatIso));
145: }
146: return $strToReturn . $s;
147:
148: }
149:
150:
151:
152: if (!$this->objPdo) {
153: $this->Connect();
154: }
155: return $strToReturn . sprintf("%s", $this->objPdo->quote($mixData));
156: }
157:
158: public function SqlLimitVariablePrefix($strLimitInfo) {
159:
160:
161: return null;
162: }
163:
164: public function SqlLimitVariableSuffix($strLimitInfo) {
165:
166: if (strlen($strLimitInfo)) {
167: if (strpos($strLimitInfo, ';') !== false)
168: throw new Exception('Invalid Semicolon in LIMIT Info');
169: if (strpos($strLimitInfo, '`') !== false)
170: throw new Exception('Invalid Backtick in LIMIT Info');
171:
172:
173: $strArray = explode(',', $strLimitInfo);
174:
175: if (count($strArray) == 2) {
176:
177: return sprintf('LIMIT %s OFFSET %s', $strArray[1], $strArray[0]);
178: } else if (count($strArray) == 1) {
179: return sprintf('LIMIT %s', $strArray[0]);
180: } else {
181: throw new QPostgreSqlDatabaseException('Invalid Limit Info: ' . $strLimitInfo, 0, null);
182: }
183: }
184:
185: return null;
186: }
187:
188: public function SqlSortByVariable($strSortByInfo) {
189:
190: if (strlen($strSortByInfo)) {
191: if (strpos($strSortByInfo, ';') !== false)
192: throw new Exception('Invalid Semicolon in ORDER BY Info');
193: if (strpos($strSortByInfo, '`') !== false)
194: throw new Exception('Invalid Backtick in ORDER BY Info');
195:
196: return "ORDER BY $strSortByInfo";
197: }
198:
199: return null;
200: }
201:
202: public function GetTables() {
203: $objResult = $this->Query("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = current_schema() ORDER BY TABLE_NAME ASC");
204: $strToReturn = array();
205: while ($strRowArray = $objResult->FetchRow())
206: array_push($strToReturn, $strRowArray[0]);
207: return $strToReturn;
208: }
209:
210: public function GetFieldsForTable($strTableName) {
211: $strTableName = $this->SqlVariable($strTableName);
212: $strQuery = sprintf('
213: SELECT
214: table_name,
215: column_name,
216: ordinal_position,
217: column_default,
218: is_nullable,
219: data_type,
220: character_maximum_length,
221: (pg_get_serial_sequence(table_name,column_name) IS NOT NULL) AS is_serial
222: FROM
223: INFORMATION_SCHEMA.COLUMNS
224: WHERE
225: table_schema = current_schema()
226: AND
227: table_name = %s
228: ORDER BY ordinal_position
229: ', $strTableName);
230:
231: $objResult = $this->Query($strQuery);
232:
233: $objFields = array();
234:
235: while ($objRow = $objResult->GetNextRow()) {
236: array_push($objFields, new QPostgreSqlPdoDatabaseField($objRow, $this));
237: }
238:
239: return $objFields;
240: }
241:
242: public function InsertId($strTableName = null, $strColumnName = null) {
243: $strQuery = sprintf('
244: SELECT currval(pg_get_serial_sequence(%s, %s))
245: ', $this->SqlVariable($strTableName), $this->SqlVariable($strColumnName));
246:
247: $objResult = $this->Query($strQuery);
248: $objRow = $objResult->FetchRow();
249: return $objRow[0];
250: }
251:
252:
253: private function ParseColumnNameArrayFromKeyDefinition($strKeyDefinition) {
254: $strKeyDefinition = trim($strKeyDefinition);
255:
256:
257: $intPosition = strpos($strKeyDefinition, '(');
258: if ($intPosition === false)
259: throw new Exception("Invalid Key Definition: $strKeyDefinition");
260: $strKeyDefinition = trim(substr($strKeyDefinition, $intPosition + 1));
261:
262: $intPosition = strpos($strKeyDefinition, ')');
263: if ($intPosition === false)
264: throw new Exception("Invalid Key Definition: $strKeyDefinition");
265: $strKeyDefinition = trim(substr($strKeyDefinition, 0, $intPosition));
266: $strKeyDefinition = str_replace(" ","",$strKeyDefinition);
267:
268:
269:
270: $strToReturn = explode(',', $strKeyDefinition);
271:
272:
273: for ($intIndex = 0; $intIndex < count($strToReturn); $intIndex++) {
274: $strColumn = $strToReturn[$intIndex];
275:
276: if (substr($strColumn, 0, 1) == '"')
277: $strColumn = substr($strColumn, 1, strpos($strColumn, '"', 1) - 1);
278:
279: $strToReturn[$intIndex] = $strColumn;
280: }
281:
282: return $strToReturn;
283: }
284:
285: public function GetIndexesForTable($strTableName) {
286: $objIndexArray = array();
287:
288: $objResult = $this->Query(sprintf('
289: SELECT
290: c2.relname AS indname,
291: i.indisprimary,
292: i.indisunique,
293: pg_catalog.pg_get_indexdef(i.indexrelid) AS inddef
294: FROM
295: pg_catalog.pg_class c,
296: pg_catalog.pg_class c2,
297: pg_catalog.pg_index i
298: WHERE
299: c.relname = %s
300: AND
301: pg_catalog.pg_table_is_visible(c.oid)
302: AND
303: c.oid = i.indrelid
304: AND
305: i.indexrelid = c2.oid
306: ORDER BY
307: c2.relname
308: ', $this->SqlVariable($strTableName)));
309:
310: while ($objRow = $objResult->GetNextRow()) {
311: $strIndexDefinition = $objRow->GetColumn('inddef');
312: $strKeyName = $objRow->GetColumn('indname');
313: $blnPrimaryKey = $objRow->GetColumn('indisprimary');
314: $blnUnique = $objRow->GetColumn('indisunique');
315: $strColumnNameArray = $this->ParseColumnNameArrayFromKeyDefinition($strIndexDefinition);
316:
317: $objIndex = new QDatabaseIndex($strKeyName, $blnPrimaryKey, $blnUnique, $strColumnNameArray);
318: array_push($objIndexArray, $objIndex);
319: }
320:
321: return $objIndexArray;
322: }
323:
324: public function GetForeignKeysForTable($strTableName) {
325: $objForeignKeyArray = array();
326:
327:
328: $strQuery = sprintf('
329: SELECT
330: pc.conname,
331: pg_catalog.pg_get_constraintdef(pc.oid, true) AS consrc
332: FROM
333: pg_catalog.pg_constraint pc
334: WHERE
335: pc.conrelid =
336: (
337: SELECT
338: oid
339: FROM
340: pg_catalog.pg_class
341: WHERE
342: relname=%s
343: AND
344: relnamespace =
345: (
346: SELECT
347: oid
348: FROM
349: pg_catalog.pg_namespace
350: WHERE
351: nspname=current_schema()
352: )
353: )
354: AND
355: pc.contype = \'f\'
356: ', $this->SqlVariable($strTableName));
357:
358: $objResult = $this->Query($strQuery);
359:
360: while ($objRow = $objResult->GetNextRow()) {
361: $strKeyName = $objRow->GetColumn('conname');
362:
363:
364: if (substr($strKeyName, 0, 1) == '"')
365: $strKeyName = substr($strKeyName, 1, strlen($strKeyName) - 2);
366:
367:
368:
369:
370:
371: $strTokenArray = explode('FOREIGN KEY ', $objRow->GetColumn('consrc'));
372: $strTokenArray[1] = explode(' REFERENCES ', $strTokenArray[1]);
373: $strTokenArray[2] = $strTokenArray[1][1];
374: $strTokenArray[1] = $strTokenArray[1][0];
375: $strTokenArray[2] = explode("(", $strTokenArray[2]);
376: $strTokenArray[3] = "(".$strTokenArray[2][1];
377: $strTokenArray[2] = $strTokenArray[2][0];
378:
379:
380: if (substr($strTokenArray[2], 0, 1) == '"')
381: $strTokenArray[2] = substr($strTokenArray[2], 1, strlen($strTokenArray[2]) - 2);
382:
383: $strColumnNameArray = $this->ParseColumnNameArrayFromKeyDefinition($strTokenArray[1]);
384: $strReferenceTableName = $strTokenArray[2];
385: $strReferenceColumnNameArray = $this->ParseColumnNameArrayFromKeyDefinition($strTokenArray[3]);
386:
387: $objForeignKey = new QDatabaseForeignKey(
388: $strKeyName,
389: $strColumnNameArray,
390: $strReferenceTableName,
391: $strReferenceColumnNameArray);
392: array_push($objForeignKeyArray, $objForeignKey);
393: }
394:
395:
396: return $objForeignKeyArray;
397: }
398:
399: protected function ExecuteQuery($strQuery) {
400:
401: $objResult = $this->objPdo->query($strQuery);
402: if ($objResult === false)
403: throw new QPostgreSqlPdoDatabaseException($this->objPdo->errorInfo(), $this->objPdo->errorCode(), $strQuery);
404:
405:
406: $this->objMostRecentResult = $objResult;
407: $objPdoStatementDatabaseResult = new QPostgreSqlPdoDatabaseResult($objResult, $this);
408: return $objPdoStatementDatabaseResult;
409: }
410:
411:
412: }
413: 414: 415:
416: class QPostgreSqlPdoDatabaseResult extends QPdoDatabaseResult {
417:
418: public function GetNextRow() {
419: $strColumnArray = $this->FetchArray();
420:
421: if ($strColumnArray)
422: return new QPostgreSqlPdoDatabaseRow($strColumnArray);
423: else
424: return null;
425: }
426:
427: public function FetchFields() {
428: $objArrayToReturn = array();
429: while ($objField = $this->FetchColumn()) {
430: array_push($objArrayToReturn, new QPostgreSqlPdoDatabaseField($objField, $this->objDb));
431: }
432: return $objArrayToReturn;
433: }
434:
435: public function FetchField() {
436: if ($objField = $this->FetchColumn())
437: return new QPostgreSqlPdoDatabaseField($objField, $this->objDb);
438: }
439:
440: }
441: 442: 443:
444: class QPostgreSqlPdoDatabaseRow extends QDatabaseRowBase {
445: protected $strColumnArray;
446:
447: public function __construct($strColumnArray) {
448: $this->strColumnArray = $strColumnArray;
449: }
450:
451: 452: 453: 454: 455: 456: 457: 458:
459: public function GetColumn($strColumnName, $strColumnType = null) {
460: if (!isset($this->strColumnArray[$strColumnName])) {
461: return null;
462: }
463:
464: $strColumnValue = $this->strColumnArray[$strColumnName];
465:
466: switch ($strColumnType) {
467: case QDatabaseFieldType::Bit:
468: if (!$strColumnValue) {
469: return false;
470: } else {
471: return ($strColumnValue) ? true : false;
472: }
473:
474: case QDatabaseFieldType::Blob:
475: case QDatabaseFieldType::Char:
476: case QDatabaseFieldType::VarChar:
477: return QType::Cast($strColumnValue, QType::String);
478:
479: case QDatabaseFieldType::Date:
480: case QDatabaseFieldType::DateTime:
481: case QDatabaseFieldType::Time:
482: return new QDateTime($strColumnValue);
483:
484: case QDatabaseFieldType::Float:
485: return QType::Cast($strColumnValue, QType::Float);
486:
487: case QDatabaseFieldType::Integer:
488: return QType::Cast($strColumnValue, QType::Integer);
489:
490: default:
491: return $strColumnValue;
492: }
493: }
494:
495: 496: 497: 498: 499: 500: 501:
502: public function ColumnExists($strColumnName) {
503: return array_key_exists($strColumnName, $this->strColumnArray);
504: }
505:
506: public function GetColumnNameArray() {
507: return $this->strColumnArray;
508: }
509: }
510: 511: 512:
513: class QPostgreSqlPdoDatabaseField extends QDatabaseFieldBase {
514: public function __construct($mixFieldData, $objDb = null) {
515: $this->strName = $mixFieldData->GetColumn('column_name');
516: $this->strOriginalName = $this->strName;
517: $this->strTable = $mixFieldData->GetColumn('table_name');
518: $this->strOriginalTable = $this->strTable;
519: $this->strDefault = $mixFieldData->GetColumn('column_default');
520: $this->intMaxLength = $mixFieldData->GetColumn('character_maximum_length', QDatabaseFieldType::Integer);
521: $this->blnNotNull = ($mixFieldData->GetColumn('is_nullable') == "NO") ? true : false;
522:
523:
524:
525:
526: $this->blnIdentity = false;
527: if ($mixFieldData->GetColumn('is_serial')) {
528: $objIndexes = $objDb->GetIndexesForTable($this->strTable);
529: foreach ($objIndexes as $objIndex) {
530: if ($objIndex->PrimaryKey) {
531: $columns = $objIndex->ColumnNameArray;
532: $this->blnIdentity = (count($columns) == 1 && $columns[0] == $this->strName);
533: break;
534: }
535: }
536: }
537:
538:
539: $objResult = $objDb->Query(sprintf('
540: SELECT
541: kcu.column_name
542: FROM
543: information_schema.table_constraints tc,
544: information_schema.key_column_usage kcu
545: WHERE
546: tc.table_name = %s
547: AND
548: tc.table_schema = current_schema()
549: AND
550: tc.constraint_type = \'PRIMARY KEY\'
551: AND
552: kcu.table_name = tc.table_name
553: AND
554: kcu.table_schema = tc.table_schema
555: AND
556: kcu.constraint_name = tc.constraint_name
557: ', $objDb->SqlVariable($this->strTable)));
558:
559: while ($objRow = $objResult->GetNextRow()) {
560: if ($objRow->GetColumn('column_name') == $this->strName)
561: $this->blnPrimaryKey = true;
562: }
563:
564: if (!$this->blnPrimaryKey)
565: $this->blnPrimaryKey = false;
566:
567:
568: $objResult = $objDb->Query(sprintf('
569: SELECT
570: kcu.column_name, (SELECT COUNT(*) FROM information_schema.key_column_usage kcu2 WHERE kcu2.constraint_name=kcu.constraint_name ) as unique_fields
571: FROM
572: information_schema.table_constraints tc,
573: information_schema.key_column_usage kcu
574: WHERE
575: tc.table_name = %s
576: AND
577: tc.table_schema = current_schema()
578: AND
579: tc.constraint_type = \'UNIQUE\'
580: AND
581: kcu.table_name = tc.table_name
582: AND
583: kcu.table_schema = tc.table_schema
584: AND
585: kcu.constraint_name = tc.constraint_name
586: GROUP BY
587: kcu.constraint_name, kcu.column_name
588: ', $objDb->SqlVariable($this->strTable)));
589: while ($objRow = $objResult->GetNextRow()) {
590: if ($objRow->GetColumn('column_name') == $this->strName && $objRow->GetColumn('unique_fields') == 1)
591: $this->blnUnique = true;
592: }
593: if (!$this->blnUnique)
594: $this->blnUnique = false;
595:
596:
597: $this->strType = $mixFieldData->GetColumn('data_type');
598:
599: switch ($this->strType) {
600: case 'integer':
601: case 'smallint':
602: case 'bigint':
603: $this->strType = QDatabaseFieldType::Integer;
604: break;
605: case 'money':
606:
607: throw new QPostgreSqlDatabaseException('Unsupported Field Type: money. Use numeric or decimal instead.', 0,null);
608: break;
609: case 'decimal':
610: case 'numeric':
611:
612:
613:
614:
615: $this->strType = QDatabaseFieldType::VarChar;
616: break;
617:
618: case 'real':
619: $this->strType = QDatabaseFieldType::Float;
620: break;
621: case 'bit':
622: if ($this->intMaxLength == 1)
623: $this->strType = QDatabaseFieldType::Bit;
624: else
625: throw new QPostgreSqlDatabaseException('Unsupported Field Type: bit with MaxLength > 1', 0, null);
626: break;
627: case 'boolean':
628: $this->strType = QDatabaseFieldType::Bit;
629: break;
630: case 'character':
631: $this->strType = QDatabaseFieldType::Char;
632: break;
633: case 'character varying':
634: case 'double precision':
635:
636:
637:
638:
639: $this->strType = QDatabaseFieldType::VarChar;
640: break;
641: case 'text':
642: $this->strType = QDatabaseFieldType::Blob;
643: break;
644: case 'timestamp':
645: case 'timestamp without time zone':
646: $this->strType = QDatabaseFieldType::DateTime;
647: break;
648: case 'date':
649: $this->strType = QDatabaseFieldType::Date;
650: break;
651: case 'time':
652: case 'time without time zone':
653: $this->strType = QDatabaseFieldType::Time;
654: break;
655: default:
656: throw new QPostgreSqlDatabaseException('Unsupported Field Type: ' . $this->strType, 0, null);
657: }
658: }
659: }
660: 661: 662:
663: class QPostgreSqlPdoDatabaseException extends QPdoDatabaseException {
664:
665: }