MediaWiki REL1_37
DatabasePostgres.php
Go to the documentation of this file.
1<?php
23namespace Wikimedia\Rdbms;
24
25use RuntimeException;
26use Wikimedia\Timestamp\ConvertibleTimestamp;
27use Wikimedia\WaitConditionLoop;
28
34 private $port;
36 private $coreSchema;
38 private $tempSchema;
43
46
54 public function __construct( array $params ) {
55 $this->port = intval( $params['port'] ?? null );
56
57 if ( isset( $params['keywordTableMap'] ) ) {
58 wfDeprecatedMsg( 'Passing keywordTableMap parameter to ' .
59 'DatabasePostgres::__construct() is deprecated', '1.37'
60 );
61
62 $this->keywordTableMap = $params['keywordTableMap'];
63 }
64
65 parent::__construct( $params );
66 }
67
68 public function getType() {
69 return 'postgres';
70 }
71
72 public function implicitOrderby() {
73 return false;
74 }
75
76 protected function open( $server, $user, $password, $db, $schema, $tablePrefix ) {
77 if ( !function_exists( 'pg_connect' ) ) {
79 "Postgres functions missing, have you compiled PHP with the --with-pgsql\n" .
80 "option? (Note: if you recently installed PHP, you may need to restart your\n" .
81 "webserver and database)"
82 );
83 }
84
85 $this->close( __METHOD__ );
86
87 $connectVars = [
88 // A database must be specified in order to connect to Postgres. If $dbName is not
89 // specified, then use the standard "postgres" database that should exist by default.
90 'dbname' => strlen( $db ) ? $db : 'postgres',
91 'user' => $user,
92 'password' => $password
93 ];
94 if ( strlen( $server ) ) {
95 $connectVars['host'] = $server;
96 }
97 if ( $this->port > 0 ) {
98 $connectVars['port'] = $this->port;
99 }
100 if ( $this->getFlag( self::DBO_SSL ) ) {
101 $connectVars['sslmode'] = 'require';
102 }
103 $connectString = $this->makeConnectionString( $connectVars );
104
105 $this->installErrorHandler();
106 try {
107 $this->conn = pg_connect( $connectString, PGSQL_CONNECT_FORCE_NEW ) ?: null;
108 } catch ( RuntimeException $e ) {
109 $this->restoreErrorHandler();
110 throw $this->newExceptionAfterConnectError( $e->getMessage() );
111 }
112 $error = $this->restoreErrorHandler();
113
114 if ( !$this->conn ) {
115 throw $this->newExceptionAfterConnectError( $error ?: $this->lastError() );
116 }
117
118 try {
119 // Since no transaction is active at this point, any SET commands should apply
120 // for the entire session (e.g. will not be reverted on transaction rollback).
121 // See https://www.postgresql.org/docs/8.3/sql-set.html
122 $variables = [
123 'client_encoding' => 'UTF8',
124 'datestyle' => 'ISO, YMD',
125 'timezone' => 'GMT',
126 'standard_conforming_strings' => 'on',
127 'bytea_output' => 'escape',
128 'client_min_messages' => 'ERROR'
129 ];
130 foreach ( $variables as $var => $val ) {
131 $this->query(
132 'SET ' . $this->addIdentifierQuotes( $var ) . ' = ' . $this->addQuotes( $val ),
133 __METHOD__,
134 self::QUERY_IGNORE_DBO_TRX | self::QUERY_NO_RETRY | self::QUERY_CHANGE_TRX
135 );
136 }
137 $this->determineCoreSchema( $schema );
138 $this->currentDomain = new DatabaseDomain( $db, $schema, $tablePrefix );
139 } catch ( RuntimeException $e ) {
140 throw $this->newExceptionAfterConnectError( $e->getMessage() );
141 }
142 }
143
144 protected function relationSchemaQualifier() {
145 if ( $this->coreSchema === $this->currentDomain->getSchema() ) {
146 // The schema to be used is now in the search path; no need for explicit qualification
147 return '';
148 }
149
150 return parent::relationSchemaQualifier();
151 }
152
153 public function databasesAreIndependent() {
154 return true;
155 }
156
157 public function doSelectDomain( DatabaseDomain $domain ) {
158 if ( $this->getDBname() !== $domain->getDatabase() ) {
159 // Postgres doesn't support selectDB in the same way MySQL does.
160 // So if the DB name doesn't match the open connection, open a new one
161 $this->open(
162 $this->connectionParams[self::CONN_HOST],
163 $this->connectionParams[self::CONN_USER],
164 $this->connectionParams[self::CONN_PASSWORD],
165 $domain->getDatabase(),
166 $domain->getSchema(),
167 $domain->getTablePrefix()
168 );
169 } else {
170 $this->currentDomain = $domain;
171 }
172
173 return true;
174 }
175
180 private function makeConnectionString( $vars ) {
181 $s = '';
182 foreach ( $vars as $name => $value ) {
183 $s .= "$name='" . str_replace( "'", "\\'", $value ) . "' ";
184 }
185
186 return $s;
187 }
188
189 protected function closeConnection() {
190 return $this->conn ? pg_close( $this->conn ) : true;
191 }
192
193 protected function isTransactableQuery( $sql ) {
194 return parent::isTransactableQuery( $sql ) &&
195 !preg_match( '/^SELECT\s+pg_(try_|)advisory_\w+\‍(/', $sql );
196 }
197
202 public function doQuery( $sql ) {
203 $conn = $this->getBindingHandle();
204
205 $sql = mb_convert_encoding( $sql, 'UTF-8' );
206 // Clear previously left over PQresult
207 while ( $res = pg_get_result( $conn ) ) {
208 pg_free_result( $res );
209 }
210 if ( pg_send_query( $conn, $sql ) === false ) {
211 throw new DBUnexpectedError( $this, "Unable to post new query to PostgreSQL\n" );
212 }
213 $this->lastResultHandle = pg_get_result( $conn );
214 if ( pg_result_error( $this->lastResultHandle ) ) {
215 return false;
216 }
217
218 return $this->lastResultHandle ?
219 new PostgresResultWrapper( $this, $this->getBindingHandle(), $this->lastResultHandle ) : false;
220 }
221
222 protected function dumpError() {
223 $diags = [
224 PGSQL_DIAG_SEVERITY,
225 PGSQL_DIAG_SQLSTATE,
226 PGSQL_DIAG_MESSAGE_PRIMARY,
227 PGSQL_DIAG_MESSAGE_DETAIL,
228 PGSQL_DIAG_MESSAGE_HINT,
229 PGSQL_DIAG_STATEMENT_POSITION,
230 PGSQL_DIAG_INTERNAL_POSITION,
231 PGSQL_DIAG_INTERNAL_QUERY,
232 PGSQL_DIAG_CONTEXT,
233 PGSQL_DIAG_SOURCE_FILE,
234 PGSQL_DIAG_SOURCE_LINE,
235 PGSQL_DIAG_SOURCE_FUNCTION
236 ];
237 foreach ( $diags as $d ) {
238 $this->queryLogger->debug( sprintf( "PgSQL ERROR(%d): %s",
239 $d, pg_result_error_field( $this->lastResultHandle, $d ) ) );
240 }
241 }
242
243 public function insertId() {
244 $res = $this->query(
245 "SELECT lastval()",
246 __METHOD__,
247 self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE
248 );
249 $row = $this->fetchRow( $res );
250
251 return $row[0] === null ? null : (int)$row[0];
252 }
253
254 public function lastError() {
255 if ( $this->conn ) {
256 if ( $this->lastResultHandle ) {
257 return pg_result_error( $this->lastResultHandle );
258 } else {
259 return pg_last_error();
260 }
261 }
262
263 return $this->getLastPHPError() ?: 'No database connection';
264 }
265
266 public function lastErrno() {
267 if ( $this->lastResultHandle ) {
268 return pg_result_error_field( $this->lastResultHandle, PGSQL_DIAG_SQLSTATE );
269 } else {
270 return false;
271 }
272 }
273
274 protected function fetchAffectedRowCount() {
275 if ( !$this->lastResultHandle ) {
276 return 0;
277 }
278
279 return pg_affected_rows( $this->lastResultHandle );
280 }
281
297 public function estimateRowCount( $table, $var = '*', $conds = '',
298 $fname = __METHOD__, $options = [], $join_conds = []
299 ) {
300 $conds = $this->normalizeConditions( $conds, $fname );
301 $column = $this->extractSingleFieldFromList( $var );
302 if ( is_string( $column ) && !in_array( $column, [ '*', '1' ] ) ) {
303 $conds[] = "$column IS NOT NULL";
304 }
305
306 $options['EXPLAIN'] = true;
307 $res = $this->select( $table, $var, $conds, $fname, $options, $join_conds );
308 $rows = -1;
309 if ( $res ) {
310 $row = $this->fetchRow( $res );
311 $count = [];
312 if ( preg_match( '/rows=(\d+)/', $row[0], $count ) ) {
313 $rows = (int)$count[1];
314 }
315 }
316
317 return $rows;
318 }
319
320 public function indexInfo( $table, $index, $fname = __METHOD__ ) {
321 $res = $this->query(
322 "SELECT indexname FROM pg_indexes WHERE tablename='$table'",
323 $fname,
324 self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE
325 );
326 if ( !$res ) {
327 return null;
328 }
329 foreach ( $res as $row ) {
330 if ( $row->indexname == $this->indexName( $index ) ) {
331 return $row;
332 }
333 }
334
335 return false;
336 }
337
338 public function indexAttributes( $index, $schema = false ) {
339 if ( $schema === false ) {
340 $schemas = $this->getCoreSchemas();
341 } else {
342 $schemas = [ $schema ];
343 }
344
345 $eindex = $this->addQuotes( $index );
346
347 $flags = self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE;
348 foreach ( $schemas as $schema ) {
349 $eschema = $this->addQuotes( $schema );
350 /*
351 * A subquery would be not needed if we didn't care about the order
352 * of attributes, but we do
353 */
354 $sql = <<<__INDEXATTR__
355
356 SELECT opcname,
357 attname,
358 i.indoption[s.g] as option,
359 pg_am.amname
360 FROM
361 (SELECT generate_series(array_lower(isub.indkey,1), array_upper(isub.indkey,1)) AS g
362 FROM
363 pg_index isub
364 JOIN pg_class cis
365 ON cis.oid=isub.indexrelid
366 JOIN pg_namespace ns
367 ON cis.relnamespace = ns.oid
368 WHERE cis.relname=$eindex AND ns.nspname=$eschema) AS s,
369 pg_attribute,
370 pg_opclass opcls,
371 pg_am,
372 pg_class ci
373 JOIN pg_index i
374 ON ci.oid=i.indexrelid
375 JOIN pg_class ct
376 ON ct.oid = i.indrelid
377 JOIN pg_namespace n
378 ON ci.relnamespace = n.oid
379 WHERE
380 ci.relname=$eindex AND n.nspname=$eschema
381 AND attrelid = ct.oid
382 AND i.indkey[s.g] = attnum
383 AND i.indclass[s.g] = opcls.oid
384 AND pg_am.oid = opcls.opcmethod
385__INDEXATTR__;
386 $res = $this->query( $sql, __METHOD__, $flags );
387 $a = [];
388 if ( $res ) {
389 foreach ( $res as $row ) {
390 $a[] = [
391 $row->attname,
392 $row->opcname,
393 $row->amname,
394 $row->option ];
395 }
396 return $a;
397 }
398 }
399 return null;
400 }
401
402 public function indexUnique( $table, $index, $fname = __METHOD__ ) {
403 $flags = self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE;
404 $sql = "SELECT indexname FROM pg_indexes WHERE tablename='{$table}'" .
405 " AND indexdef LIKE 'CREATE UNIQUE%(" .
406 $this->strencode( $this->indexName( $index ) ) .
407 ")'";
408 $res = $this->query( $sql, $fname, $flags );
409 if ( !$res ) {
410 return null;
411 }
412
413 return $res->numRows() > 0;
414 }
415
416 public function selectSQLText(
417 $table, $vars, $conds = '', $fname = __METHOD__, $options = [], $join_conds = []
418 ) {
419 if ( is_string( $options ) ) {
420 $options = [ $options ];
421 }
422
423 // Change the FOR UPDATE option as necessary based on the join conditions. Then pass
424 // to the parent function to get the actual SQL text.
425 // In Postgres when using FOR UPDATE, only the main table and tables that are inner joined
426 // can be locked. That means tables in an outer join cannot be FOR UPDATE locked. Trying to
427 // do so causes a DB error. This wrapper checks which tables can be locked and adjusts it
428 // accordingly.
429 // MySQL uses "ORDER BY NULL" as an optimization hint, but that is illegal in PostgreSQL.
430 if ( is_array( $options ) ) {
431 $forUpdateKey = array_search( 'FOR UPDATE', $options, true );
432 if ( $forUpdateKey !== false && $join_conds ) {
433 unset( $options[$forUpdateKey] );
434 $options['FOR UPDATE'] = [];
435
436 $toCheck = $table;
437 reset( $toCheck );
438 while ( $toCheck ) {
439 $alias = key( $toCheck );
440 $name = $toCheck[$alias];
441 unset( $toCheck[$alias] );
442
443 $hasAlias = !is_numeric( $alias );
444 if ( !$hasAlias && is_string( $name ) ) {
445 $alias = $name;
446 }
447
448 if ( !isset( $join_conds[$alias] ) ||
449 !preg_match( '/^(?:LEFT|RIGHT|FULL)(?: OUTER)? JOIN$/i', $join_conds[$alias][0] )
450 ) {
451 if ( is_array( $name ) ) {
452 // It's a parenthesized group, process all the tables inside the group.
453 $toCheck = array_merge( $toCheck, $name );
454 } else {
455 // Quote alias names so $this->tableName() won't mangle them
456 $options['FOR UPDATE'][] = $hasAlias ? $this->addIdentifierQuotes( $alias ) : $alias;
457 }
458 }
459 }
460 }
461
462 if ( isset( $options['ORDER BY'] ) && $options['ORDER BY'] == 'NULL' ) {
463 unset( $options['ORDER BY'] );
464 }
465 }
466
467 return parent::selectSQLText( $table, $vars, $conds, $fname, $options, $join_conds );
468 }
469
471 return [ 'INSERT INTO', 'ON CONFLICT DO NOTHING' ];
472 }
473
474 public function doInsertNonConflicting( $table, array $rows, $fname ) {
475 // Postgres 9.5 supports "ON CONFLICT"
476 if ( $this->getServerVersion() >= 9.5 ) {
477 parent::doInsertNonConflicting( $table, $rows, $fname );
478
479 return;
480 }
481
483 // Emulate INSERT IGNORE via savepoints/rollback
484 $tok = $this->startAtomic( "$fname (outer)", self::ATOMIC_CANCELABLE );
485 try {
486 $encTable = $this->tableName( $table );
487 foreach ( $rows as $row ) {
488 list( $sqlColumns, $sqlTuples ) = $this->makeInsertLists( [ $row ] );
489 $tempsql = "INSERT INTO $encTable ($sqlColumns) VALUES $sqlTuples";
490
491 $this->startAtomic( "$fname (inner)", self::ATOMIC_CANCELABLE );
492 try {
493 $this->query( $tempsql, $fname, self::QUERY_CHANGE_ROWS );
494 $this->endAtomic( "$fname (inner)" );
496 } catch ( DBQueryError $e ) {
497 $this->cancelAtomic( "$fname (inner)" );
498 // Our IGNORE is supposed to ignore duplicate key errors, but not others.
499 // (even though MySQL's version apparently ignores all errors)
500 if ( $e->errno !== '23505' ) {
501 throw $e;
502 }
503 }
504 }
505 } catch ( RuntimeException $e ) {
506 $this->cancelAtomic( "$fname (outer)", $tok );
507 throw $e;
508 }
509 $this->endAtomic( "$fname (outer)" );
510 // Set the affected row count for the whole operation
511 $this->affectedRowCount = $affectedRowCount;
512 }
513
514 protected function makeUpdateOptionsArray( $options ) {
515 $options = $this->normalizeOptions( $options );
516 // PostgreSQL doesn't support anything like "ignore" for UPDATE.
517 $options = array_diff( $options, [ 'IGNORE' ] );
518
519 return parent::makeUpdateOptionsArray( $options );
520 }
521
540 protected function doInsertSelectNative(
541 $destTable,
542 $srcTable,
543 array $varMap,
544 $conds,
545 $fname,
546 array $insertOptions,
547 array $selectOptions,
548 $selectJoinConds
549 ) {
550 if ( in_array( 'IGNORE', $insertOptions ) ) {
551 if ( $this->getServerVersion() >= 9.5 ) {
552 // Use "ON CONFLICT DO" if we have it for IGNORE
553 $destTable = $this->tableName( $destTable );
554
555 $selectSql = $this->selectSQLText(
556 $srcTable,
557 array_values( $varMap ),
558 $conds,
559 $fname,
560 $selectOptions,
561 $selectJoinConds
562 );
563
564 $sql = "INSERT INTO $destTable (" . implode( ',', array_keys( $varMap ) ) . ') ' .
565 $selectSql . ' ON CONFLICT DO NOTHING';
566
567 $this->query( $sql, $fname, self::QUERY_CHANGE_ROWS );
568 } else {
569 // IGNORE and we don't have ON CONFLICT DO NOTHING, so just use the non-native version
571 $destTable, $srcTable, $varMap, $conds, $fname,
572 $insertOptions, $selectOptions, $selectJoinConds
573 );
574 }
575 } else {
576 parent::doInsertSelectNative( $destTable, $srcTable, $varMap, $conds, $fname,
577 $insertOptions, $selectOptions, $selectJoinConds );
578 }
579 }
580
581 public function tableName( $name, $format = 'quoted' ) {
582 return parent::tableName( $name, $format );
583 }
584
590 public function remappedTableName( $name ) {
591 wfDeprecated( __METHOD__, '1.37' );
592
593 return $this->keywordTableMap[$name] ?? $name;
594 }
595
601 public function realTableName( $name, $format = 'quoted' ) {
602 return parent::tableName( $name, $format );
603 }
604
605 public function nextSequenceValue( $seqName ) {
606 return new NextSequenceValue;
607 }
608
615 public function currentSequenceValue( $seqName ) {
616 $res = $this->query(
617 "SELECT currval('" . str_replace( "'", "''", $seqName ) . "')",
618 __METHOD__,
619 self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE
620 );
621 $row = $this->fetchRow( $res );
622 $currval = $row[0];
623
624 return $currval;
625 }
626
627 public function textFieldSize( $table, $field ) {
628 $flags = self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE;
629 $encTable = $this->tableName( $table );
630 $sql = "SELECT t.typname as ftype,a.atttypmod as size
631 FROM pg_class c, pg_attribute a, pg_type t
632 WHERE relname='$encTable' AND a.attrelid=c.oid AND
633 a.atttypid=t.oid and a.attname='$field'";
634 $res = $this->query( $sql, __METHOD__, $flags );
635 $row = $this->fetchObject( $res );
636 if ( $row->ftype == 'varchar' ) {
637 $size = $row->size - 4;
638 } else {
639 $size = $row->size;
640 }
641
642 return $size;
643 }
644
645 public function limitResult( $sql, $limit, $offset = false ) {
646 return "$sql LIMIT $limit " . ( is_numeric( $offset ) ? " OFFSET {$offset} " : '' );
647 }
648
649 public function wasDeadlock() {
650 // https://www.postgresql.org/docs/9.2/static/errcodes-appendix.html
651 return $this->lastErrno() === '40P01';
652 }
653
654 public function wasLockTimeout() {
655 // https://www.postgresql.org/docs/9.2/static/errcodes-appendix.html
656 return $this->lastErrno() === '55P03';
657 }
658
659 public function wasConnectionError( $errno ) {
660 // https://www.postgresql.org/docs/9.2/static/errcodes-appendix.html
661 static $codes = [ '08000', '08003', '08006', '08001', '08004', '57P01', '57P03', '53300' ];
662
663 return in_array( $errno, $codes, true );
664 }
665
666 protected function wasKnownStatementRollbackError() {
667 return false; // transaction has to be rolled-back from error state
668 }
669
670 public function duplicateTableStructure(
671 $oldName, $newName, $temporary = false, $fname = __METHOD__
672 ) {
673 $newNameE = $this->addIdentifierQuotes( $newName );
674 $oldNameE = $this->addIdentifierQuotes( $oldName );
675
676 $temporary = $temporary ? 'TEMPORARY' : '';
677
678 $ret = $this->query(
679 "CREATE $temporary TABLE $newNameE " .
680 "(LIKE $oldNameE INCLUDING DEFAULTS INCLUDING INDEXES)",
681 $fname,
682 self::QUERY_PSEUDO_PERMANENT | self::QUERY_CHANGE_SCHEMA
683 );
684 if ( !$ret ) {
685 return $ret;
686 }
687
688 $res = $this->query(
689 'SELECT attname FROM pg_class c'
690 . ' JOIN pg_namespace n ON (n.oid = c.relnamespace)'
691 . ' JOIN pg_attribute a ON (a.attrelid = c.oid)'
692 . ' JOIN pg_attrdef d ON (c.oid=d.adrelid and a.attnum=d.adnum)'
693 . ' WHERE relkind = \'r\''
694 . ' AND nspname = ' . $this->addQuotes( $this->getCoreSchema() )
695 . ' AND relname = ' . $this->addQuotes( $oldName )
696 . ' AND pg_get_expr(adbin, adrelid) LIKE \'nextval(%\'',
697 $fname,
698 self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE
699 );
700 $row = $this->fetchObject( $res );
701 if ( $row ) {
702 $field = $row->attname;
703 $newSeq = "{$newName}_{$field}_seq";
704 $fieldE = $this->addIdentifierQuotes( $field );
705 $newSeqE = $this->addIdentifierQuotes( $newSeq );
706 $newSeqQ = $this->addQuotes( $newSeq );
707 $this->query(
708 "CREATE $temporary SEQUENCE $newSeqE OWNED BY $newNameE.$fieldE",
709 $fname,
710 self::QUERY_CHANGE_SCHEMA
711 );
712 $this->query(
713 "ALTER TABLE $newNameE ALTER COLUMN $fieldE SET DEFAULT nextval({$newSeqQ}::regclass)",
714 $fname,
715 self::QUERY_CHANGE_SCHEMA
716 );
717 }
718
719 return $ret;
720 }
721
722 protected function doTruncate( array $tables, $fname ) {
723 $encTables = $this->tableNamesN( ...$tables );
724 $sql = "TRUNCATE TABLE " . implode( ',', $encTables ) . " RESTART IDENTITY";
725 $this->query( $sql, $fname, self::QUERY_CHANGE_SCHEMA );
726 }
727
734 public function listTables( $prefix = '', $fname = __METHOD__ ) {
735 $eschemas = implode( ',', array_map( [ $this, 'addQuotes' ], $this->getCoreSchemas() ) );
736 $result = $this->query(
737 "SELECT DISTINCT tablename FROM pg_tables WHERE schemaname IN ($eschemas)",
738 $fname,
739 self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE
740 );
741 $endArray = [];
742
743 foreach ( $result as $table ) {
744 $vars = get_object_vars( $table );
745 $table = array_pop( $vars );
746 if ( $prefix == '' || strpos( $table, $prefix ) === 0 ) {
747 $endArray[] = $table;
748 }
749 }
750
751 return $endArray;
752 }
753
754 public function timestamp( $ts = 0 ) {
755 $ct = new ConvertibleTimestamp( $ts );
756
757 return $ct->getTimestamp( TS_POSTGRES );
758 }
759
778 private function pg_array_parse( $text, &$output, $limit = false, $offset = 1 ) {
779 if ( $limit === false ) {
780 $limit = strlen( $text ) - 1;
781 $output = [];
782 }
783 if ( $text == '{}' ) {
784 return $output;
785 }
786 do {
787 if ( $text[$offset] != '{' ) {
788 preg_match( "/(\\{?\"([^\"\\\\]|\\\\.)*\"|[^,{}]+)+([,}]+)/",
789 $text, $match, 0, $offset );
790 $offset += strlen( $match[0] );
791 $output[] = ( $match[1][0] != '"'
792 ? $match[1]
793 : stripcslashes( substr( $match[1], 1, -1 ) ) );
794 if ( $match[3] == '},' ) {
795 return $output;
796 }
797 } else {
798 $offset = $this->pg_array_parse( $text, $output, $limit, $offset + 1 );
799 }
800 } while ( $limit > $offset );
801
802 return $output;
803 }
804
805 public function aggregateValue( $valuedata, $valuename = 'value' ) {
806 return $valuedata;
807 }
808
809 public function getSoftwareLink() {
810 return '[{{int:version-db-postgres-url}} PostgreSQL]';
811 }
812
820 public function getCurrentSchema() {
821 $res = $this->query(
822 "SELECT current_schema()",
823 __METHOD__,
824 self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE
825 );
826 $row = $this->fetchRow( $res );
827
828 return $row[0];
829 }
830
841 public function getSchemas() {
842 $res = $this->query(
843 "SELECT current_schemas(false)",
844 __METHOD__,
845 self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE
846 );
847 $row = $this->fetchRow( $res );
848 $schemas = [];
849
850 /* PHP pgsql support does not support array type, "{a,b}" string is returned */
851
852 return $this->pg_array_parse( $row[0], $schemas );
853 }
854
864 public function getSearchPath() {
865 $res = $this->query(
866 "SHOW search_path",
867 __METHOD__,
868 self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE
869 );
870 $row = $this->fetchRow( $res );
871
872 /* PostgreSQL returns SHOW values as strings */
873
874 return explode( ",", $row[0] );
875 }
876
884 private function setSearchPath( $search_path ) {
885 $this->query(
886 "SET search_path = " . implode( ", ", $search_path ),
887 __METHOD__,
888 self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_TRX
889 );
890 }
891
906 public function determineCoreSchema( $desiredSchema ) {
907 if ( $this->trxLevel() ) {
908 // We do not want the schema selection to change on ROLLBACK or INSERT SELECT.
909 // See https://www.postgresql.org/docs/8.3/sql-set.html
910 throw new DBUnexpectedError(
911 $this,
912 __METHOD__ . ": a transaction is currently active"
913 );
914 }
915
916 if ( $this->schemaExists( $desiredSchema ) ) {
917 if ( in_array( $desiredSchema, $this->getSchemas() ) ) {
918 $this->coreSchema = $desiredSchema;
919 $this->queryLogger->debug(
920 "Schema \"" . $desiredSchema . "\" already in the search path\n" );
921 } else {
922 // Prepend the desired schema to the search path (T17816)
923 $search_path = $this->getSearchPath();
924 array_unshift( $search_path, $this->addIdentifierQuotes( $desiredSchema ) );
925 $this->setSearchPath( $search_path );
926 $this->coreSchema = $desiredSchema;
927 $this->queryLogger->debug(
928 "Schema \"" . $desiredSchema . "\" added to the search path\n" );
929 }
930 } else {
931 $this->coreSchema = $this->getCurrentSchema();
932 $this->queryLogger->debug(
933 "Schema \"" . $desiredSchema . "\" not found, using current \"" .
934 $this->coreSchema . "\"\n" );
935 }
936 }
937
944 public function getCoreSchema() {
945 return $this->coreSchema;
946 }
947
954 public function getCoreSchemas() {
955 if ( $this->tempSchema ) {
956 return [ $this->tempSchema, $this->getCoreSchema() ];
957 }
958
959 $res = $this->query(
960 "SELECT nspname FROM pg_catalog.pg_namespace n WHERE n.oid = pg_my_temp_schema()",
961 __METHOD__,
962 self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE
963 );
964 $row = $this->fetchObject( $res );
965 if ( $row ) {
966 $this->tempSchema = $row->nspname;
967 return [ $this->tempSchema, $this->getCoreSchema() ];
968 }
969
970 return [ $this->getCoreSchema() ];
971 }
972
973 public function getServerVersion() {
974 if ( !isset( $this->numericVersion ) ) {
975 $conn = $this->getBindingHandle();
976 $versionInfo = pg_version( $conn );
977 if ( version_compare( $versionInfo['client'], '7.4.0', 'lt' ) ) {
978 // Old client, abort install
979 $this->numericVersion = '7.3 or earlier';
980 } elseif ( isset( $versionInfo['server'] ) ) {
981 // Normal client
982 $this->numericVersion = $versionInfo['server'];
983 } else {
984 // T18937: broken pgsql extension from PHP<5.3
985 $this->numericVersion = pg_parameter_status( $conn, 'server_version' );
986 }
987 }
988
990 }
991
1000 private function relationExists( $table, $types, $schema = false ) {
1001 if ( !is_array( $types ) ) {
1002 $types = [ $types ];
1003 }
1004 if ( $schema === false ) {
1005 $schemas = $this->getCoreSchemas();
1006 } else {
1007 $schemas = [ $schema ];
1008 }
1009 $table = $this->realTableName( $table, 'raw' );
1010 $etable = $this->addQuotes( $table );
1011 foreach ( $schemas as $schema ) {
1012 $eschema = $this->addQuotes( $schema );
1013 $sql = "SELECT 1 FROM pg_catalog.pg_class c, pg_catalog.pg_namespace n "
1014 . "WHERE c.relnamespace = n.oid AND c.relname = $etable AND n.nspname = $eschema "
1015 . "AND c.relkind IN ('" . implode( "','", $types ) . "')";
1016 $res = $this->query(
1017 $sql,
1018 __METHOD__,
1019 self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE
1020 );
1021 if ( $res && $res->numRows() ) {
1022 return true;
1023 }
1024 }
1025
1026 return false;
1027 }
1028
1036 public function tableExists( $table, $fname = __METHOD__, $schema = false ) {
1037 return $this->relationExists( $table, [ 'r', 'v' ], $schema );
1038 }
1039
1040 public function sequenceExists( $sequence, $schema = false ) {
1041 return $this->relationExists( $sequence, 'S', $schema );
1042 }
1043
1044 public function triggerExists( $table, $trigger ) {
1045 $q = <<<SQL
1046 SELECT 1 FROM pg_class, pg_namespace, pg_trigger
1047 WHERE relnamespace=pg_namespace.oid AND relkind='r'
1048 AND tgrelid=pg_class.oid
1049 AND nspname=%s AND relname=%s AND tgname=%s
1050SQL;
1051 foreach ( $this->getCoreSchemas() as $schema ) {
1052 $res = $this->query(
1053 sprintf(
1054 $q,
1055 $this->addQuotes( $schema ),
1056 $this->addQuotes( $table ),
1057 $this->addQuotes( $trigger )
1058 ),
1059 __METHOD__,
1060 self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE
1061 );
1062 if ( $res && $res->numRows() ) {
1063 return true;
1064 }
1065 }
1066
1067 return false;
1068 }
1069
1070 public function ruleExists( $table, $rule ) {
1071 $exists = $this->selectField( 'pg_rules', 'rulename',
1072 [
1073 'rulename' => $rule,
1074 'tablename' => $table,
1075 'schemaname' => $this->getCoreSchemas()
1076 ],
1077 __METHOD__
1078 );
1079
1080 return $exists === $rule;
1081 }
1082
1083 public function constraintExists( $table, $constraint ) {
1084 foreach ( $this->getCoreSchemas() as $schema ) {
1085 $sql = sprintf( "SELECT 1 FROM information_schema.table_constraints " .
1086 "WHERE constraint_schema = %s AND table_name = %s AND constraint_name = %s",
1087 $this->addQuotes( $schema ),
1088 $this->addQuotes( $table ),
1089 $this->addQuotes( $constraint )
1090 );
1091 $res = $this->query(
1092 $sql,
1093 __METHOD__,
1094 self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE
1095 );
1096 if ( $res && $res->numRows() ) {
1097 return true;
1098 }
1099 }
1100 return false;
1101 }
1102
1108 public function schemaExists( $schema ) {
1109 if ( !strlen( $schema ) ) {
1110 return false; // short-circuit
1111 }
1112
1113 $res = $this->query(
1114 "SELECT 1 FROM pg_catalog.pg_namespace " .
1115 "WHERE nspname = " . $this->addQuotes( $schema ) . " LIMIT 1",
1116 __METHOD__,
1117 self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE
1118 );
1119
1120 return ( $this->numRows( $res ) > 0 );
1121 }
1122
1128 public function roleExists( $roleName ) {
1129 $res = $this->query(
1130 "SELECT 1 FROM pg_catalog.pg_roles " .
1131 "WHERE rolname = " . $this->addQuotes( $roleName ) . " LIMIT 1",
1132 __METHOD__,
1133 self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE
1134 );
1135
1136 return ( $this->numRows( $res ) > 0 );
1137 }
1138
1144 public function fieldInfo( $table, $field ) {
1145 return PostgresField::fromText( $this, $table, $field );
1146 }
1147
1157 public function fieldType( $res, $index ) {
1158 wfDeprecated( __METHOD__, '1.37' );
1159 return pg_field_type( $res->getInternalResult(), $index );
1160 }
1161
1162 public function encodeBlob( $b ) {
1163 return new PostgresBlob( pg_escape_bytea( $b ) );
1164 }
1165
1166 public function decodeBlob( $b ) {
1167 if ( $b instanceof PostgresBlob ) {
1168 $b = $b->fetch();
1169 } elseif ( $b instanceof Blob ) {
1170 return $b->fetch();
1171 }
1172
1173 return pg_unescape_bytea( $b );
1174 }
1175
1176 public function strencode( $s ) {
1177 // Should not be called by us
1178 return pg_escape_string( $this->getBindingHandle(), (string)$s );
1179 }
1180
1181 public function addQuotes( $s ) {
1182 $conn = $this->getBindingHandle();
1183
1184 if ( $s === null ) {
1185 return 'NULL';
1186 } elseif ( is_bool( $s ) ) {
1187 return (string)intval( $s );
1188 } elseif ( is_int( $s ) ) {
1189 return (string)$s;
1190 } elseif ( $s instanceof Blob ) {
1191 if ( $s instanceof PostgresBlob ) {
1192 $s = $s->fetch();
1193 } else {
1194 $s = pg_escape_bytea( $conn, $s->fetch() );
1195 }
1196 return "'$s'";
1197 } elseif ( $s instanceof NextSequenceValue ) {
1198 return 'DEFAULT';
1199 }
1200
1201 return "'" . pg_escape_string( $conn, (string)$s ) . "'";
1202 }
1203
1204 protected function makeSelectOptions( array $options ) {
1205 $preLimitTail = $postLimitTail = '';
1206 $startOpts = $useIndex = $ignoreIndex = '';
1207
1208 $noKeyOptions = [];
1209 foreach ( $options as $key => $option ) {
1210 if ( is_numeric( $key ) ) {
1211 $noKeyOptions[$option] = true;
1212 }
1213 }
1214
1215 $preLimitTail .= $this->makeGroupByWithHaving( $options );
1216
1217 $preLimitTail .= $this->makeOrderBy( $options );
1218
1219 if ( isset( $options['FOR UPDATE'] ) ) {
1220 $postLimitTail .= ' FOR UPDATE OF ' .
1221 implode( ', ', array_map( [ $this, 'tableName' ], $options['FOR UPDATE'] ) );
1222 } elseif ( isset( $noKeyOptions['FOR UPDATE'] ) ) {
1223 $postLimitTail .= ' FOR UPDATE';
1224 }
1225
1226 if ( isset( $noKeyOptions['DISTINCT'] ) || isset( $noKeyOptions['DISTINCTROW'] ) ) {
1227 $startOpts .= 'DISTINCT';
1228 }
1229
1230 return [ $startOpts, $useIndex, $preLimitTail, $postLimitTail, $ignoreIndex ];
1231 }
1232
1233 public function buildConcat( $stringList ) {
1234 return implode( ' || ', $stringList );
1235 }
1236
1237 public function buildGroupConcatField(
1238 $delim, $table, $field, $conds = '', $join_conds = []
1239 ) {
1240 $fld = "array_to_string(array_agg($field)," . $this->addQuotes( $delim ) . ')';
1241
1242 return '(' . $this->selectSQLText( $table, $fld, $conds, null, [], $join_conds ) . ')';
1243 }
1244
1245 public function buildStringCast( $field ) {
1246 return $field . '::text';
1247 }
1248
1249 public function streamStatementEnd( &$sql, &$newLine ) {
1250 # Allow dollar quoting for function declarations
1251 if ( substr( $newLine, 0, 4 ) == '$mw$' ) {
1252 if ( $this->delimiter ) {
1253 $this->delimiter = false;
1254 } else {
1255 $this->delimiter = ';';
1256 }
1257 }
1258
1259 return parent::streamStatementEnd( $sql, $newLine );
1260 }
1261
1262 public function doLockTables( array $read, array $write, $method ) {
1263 $tablesWrite = [];
1264 foreach ( $write as $table ) {
1265 $tablesWrite[] = $this->tableName( $table );
1266 }
1267 $tablesRead = [];
1268 foreach ( $read as $table ) {
1269 $tablesRead[] = $this->tableName( $table );
1270 }
1271
1272 // Acquire locks for the duration of the current transaction...
1273 if ( $tablesWrite ) {
1274 $this->query(
1275 'LOCK TABLE ONLY ' . implode( ',', $tablesWrite ) . ' IN EXCLUSIVE MODE',
1276 $method,
1277 self::QUERY_CHANGE_ROWS
1278 );
1279 }
1280 if ( $tablesRead ) {
1281 $this->query(
1282 'LOCK TABLE ONLY ' . implode( ',', $tablesRead ) . ' IN SHARE MODE',
1283 $method,
1284 self::QUERY_CHANGE_ROWS
1285 );
1286 }
1287
1288 return true;
1289 }
1290
1291 public function doLockIsFree( string $lockName, string $method ) {
1292 // http://www.postgresql.org/docs/9.2/static/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS
1293 $key = $this->addQuotes( $this->bigintFromLockName( $lockName ) );
1294
1295 $res = $this->query(
1296 "SELECT (CASE(pg_try_advisory_lock($key))
1297 WHEN 'f' THEN 'f' ELSE pg_advisory_unlock($key) END) AS unlocked",
1298 $method,
1299 self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE
1300 );
1301 $row = $this->fetchObject( $res );
1302
1303 return ( $row->unlocked === 't' );
1304 }
1305
1306 public function doLock( string $lockName, string $method, int $timeout ) {
1307 // http://www.postgresql.org/docs/9.2/static/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS
1308 $key = $this->addQuotes( $this->bigintFromLockName( $lockName ) );
1309
1310 $acquired = null;
1311 $loop = new WaitConditionLoop(
1312 function () use ( $lockName, $key, $timeout, $method, &$acquired ) {
1313 $res = $this->query(
1314 "SELECT (CASE WHEN pg_try_advisory_lock($key) " .
1315 "THEN EXTRACT(epoch from clock_timestamp()) " .
1316 "ELSE NULL " .
1317 "END) AS acquired",
1318 $method,
1319 self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_ROWS
1320 );
1321 $row = $this->fetchObject( $res );
1322
1323 if ( $row->acquired !== null ) {
1324 $acquired = (float)$row->acquired;
1325
1326 return WaitConditionLoop::CONDITION_REACHED;
1327 }
1328
1329 return WaitConditionLoop::CONDITION_CONTINUE;
1330 },
1331 $timeout
1332 );
1333 $loop->invoke();
1334
1335 return $acquired;
1336 }
1337
1338 public function doUnlock( string $lockName, string $method ) {
1339 // http://www.postgresql.org/docs/9.2/static/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS
1340 $key = $this->addQuotes( $this->bigintFromLockName( $lockName ) );
1341
1342 $result = $this->query(
1343 "SELECT pg_advisory_unlock($key) AS released",
1344 $method,
1345 self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_ROWS
1346 );
1347 $row = $this->fetchObject( $result );
1348
1349 return ( $row->released === 't' );
1350 }
1351
1352 public function serverIsReadOnly() {
1353 $res = $this->query(
1354 "SHOW default_transaction_read_only",
1355 __METHOD__,
1356 self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE
1357 );
1358 $row = $this->fetchObject( $res );
1359
1360 return $row ? ( strtolower( $row->default_transaction_read_only ) === 'on' ) : false;
1361 }
1362
1363 protected static function getAttributes() {
1364 return [ self::ATTR_SCHEMAS_AS_TABLE_GROUPS => true ];
1365 }
1366
1371 private function bigintFromLockName( $lockName ) {
1372 return \Wikimedia\base_convert( substr( sha1( $lockName ), 0, 15 ), 16, 10 );
1373 }
1374}
1375
1379class_alias( DatabasePostgres::class, 'DatabasePostgres' );
wfDeprecatedMsg( $msg, $version=false, $component=false, $callerOffset=2)
Log a deprecation warning with arbitrary message text.
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that a deprecated feature was used.
Class to handle database/schema/prefix specifications for IDatabase.
getCoreSchemas()
Return schema names for temporary tables and core application tables.
buildConcat( $stringList)
Build a concatenation list to feed into a SQL query.stringto override
buildStringCast( $field)
string 1.28to override
determineCoreSchema( $desiredSchema)
Determine default schema for the current application Adjust this session schema search path if desire...
duplicateTableStructure( $oldName, $newName, $temporary=false, $fname=__METHOD__)
Creates a new table with structure copied from existing table.Note that unlike most database abstract...
buildGroupConcatField( $delim, $table, $field, $conds='', $join_conds=[])
Build a GROUP_CONCAT or equivalent statement for a query.This is useful for combining a field for sev...
doLock(string $lockName, string $method, int $timeout)
insertId()
Get the inserted value of an auto-increment row.
indexAttributes( $index, $schema=false)
databasesAreIndependent()
Returns true if DBs are assumed to be on potentially different servers.In systems like mysql/mariadb,...
setSearchPath( $search_path)
Update search_path, values should already be sanitized Values may contain magic keywords like "$user"...
doUnlock(string $lockName, string $method)
pg_array_parse( $text, &$output, $limit=false, $offset=1)
Posted by cc[plus]php[at]c2se[dot]com on 25-Mar-2009 09:12 to https://www.php.net/manual/en/ref....
null string[] $keywordTableMap
Map of (reserved table name => alternate table name)
streamStatementEnd(&$sql, &$newLine)
Called by sourceStream() to check if we've reached a statement end.
doLockIsFree(string $lockName, string $method)
implicitOrderby()
Returns true if this database does an implicit order by when the column has an index For example: SEL...
nextSequenceValue( $seqName)
Deprecated method, calls should be removed.
doSelectDomain(DatabaseDomain $domain)
timestamp( $ts=0)
Convert a timestamp in one of the formats accepted by ConvertibleTimestamp to the format used for ins...
roleExists( $roleName)
Returns true if a given role (i.e.
selectSQLText( $table, $vars, $conds='', $fname=__METHOD__, $options=[], $join_conds=[])
Take the same arguments as IDatabase::select() and return the SQL it would use.This can be useful for...
makeUpdateOptionsArray( $options)
Make UPDATE options array for Database::makeUpdateOptions.
getSchemas()
Return list of schemas which are accessible without schema name This is list does not contain magic k...
indexInfo( $table, $index, $fname=__METHOD__)
Get information about an index into an object.
schemaExists( $schema)
Query whether a given schema exists.
estimateRowCount( $table, $var=' *', $conds='', $fname=__METHOD__, $options=[], $join_conds=[])
Estimate rows in dataset Returns estimated count, based on EXPLAIN output This is not necessarily an ...
lastError()
Get a description of the last error.
wasConnectionError( $errno)
Do not use this method outside of Database/DBError classes.
addQuotes( $s)
Escape and quote a raw value string for use in a SQL query.stringto override
limitResult( $sql, $limit, $offset=false)
Construct a LIMIT query with optional offset.The SQL should be adjusted so that only the first $limit...
sequenceExists( $sequence, $schema=false)
wasDeadlock()
Determines if the last failure was due to a deadlock.Note that during a deadlock, the prior transacti...
doInsertNonConflicting( $table, array $rows, $fname)
currentSequenceValue( $seqName)
Return the current value of a sequence.
lastErrno()
Get the last error number.
getCoreSchema()
Return schema name for core application tables.
strencode( $s)
Wrapper for addslashes()
isTransactableQuery( $sql)
Determine whether a SQL statement is sensitive to isolation level.
wasLockTimeout()
Determines if the last failure was due to a lock timeout.Note that during a lock wait timeout,...
indexUnique( $table, $index, $fname=__METHOD__)
Determines if a given index is unique.boolto override
open( $server, $user, $password, $db, $schema, $tablePrefix)
Open a new connection to the database (closing any existing one)
getServerVersion()
A string describing the current software version, like from mysql_get_server_info()
fieldType( $res, $index)
pg_field_type() wrapper
doLockTables(array $read, array $write, $method)
Helper function for lockTables() that handles the actual table locking.
textFieldSize( $table, $field)
Returns the size of a text field, or -1 for "unlimited".intto override
getCurrentSchema()
Return current schema (executes SELECT current_schema()) Needs transaction.
getSearchPath()
Return search patch for schemas This is different from getSchemas() since it contain magic keywords (...
tableExists( $table, $fname=__METHOD__, $schema=false)
For backward compatibility, this function checks both tables and views.
decodeBlob( $b)
Some DBMSs return a special placeholder object representing blob fields in result objects....
relationExists( $table, $types, $schema=false)
Query whether a given relation exists (in the given schema, or the default mw one if not given)
encodeBlob( $b)
Some DBMSs have a special format for inserting into blob fields, they don't allow simple quoted strin...
realTableName( $name, $format='quoted')
closeConnection()
Closes underlying database connection.
getType()
Get the RDBMS type of the server (e.g.
listTables( $prefix='', $fname=__METHOD__)
makeSelectOptions(array $options)
Returns an optional USE INDEX clause to go after the table, and a string to go at the end of the quer...
getSoftwareLink()
Returns a wikitext style link to the DB's website (e.g.
aggregateValue( $valuedata, $valuename='value')
Return aggregated value alias.array|string Since 1.33 to override
doInsertSelectNative( $destTable, $srcTable, array $varMap, $conds, $fname, array $insertOptions, array $selectOptions, $selectJoinConds)
INSERT SELECT wrapper $varMap must be an associative array of the form [ 'dest1' => 'source1',...
serverIsReadOnly()
bool Whether the DB is marked as read-only server-side query} 1.28to override
tableName( $name, $format='quoted')
Format a table name ready for use in constructing an SQL query.This does two important things: it quo...
Relational database abstraction object.
Definition Database.php:52
string null $password
Password used to establish the current connection.
Definition Database.php:87
doInsertSelectGeneric( $destTable, $srcTable, array $varMap, $conds, $fname, array $insertOptions, array $selectOptions, $selectJoinConds)
Implementation of insertSelect() based on select() and insert()
restoreErrorHandler()
Restore the previous error handler and return the last PHP error for this DB.
Definition Database.php:938
object resource null $conn
Database connection.
Definition Database.php:77
newExceptionAfterConnectError( $error)
endAtomic( $fname=__METHOD__)
Ends an atomic section of SQL statements.
cancelAtomic( $fname=__METHOD__, AtomicSectionIdentifier $sectionId=null)
Cancel an atomic section of SQL statements.
indexName( $index)
Allows for index remapping in queries where this is not consistent across DBMS.
select( $table, $vars, $conds='', $fname=__METHOD__, $options=[], $join_conds=[])
Execute a SELECT query constructed using the various parameters provided.
int null $affectedRowCount
Rows affected by the last query to query() or its CRUD wrappers.
Definition Database.php:183
int $flags
Current bit field of class DBO_* constants.
Definition Database.php:106
makeGroupByWithHaving( $options)
Returns an optional GROUP BY with an optional HAVING.
numRows( $res)
Get the number of rows in a query result.
Definition Database.php:880
query( $sql, $fname=__METHOD__, $flags=self::QUERY_NORMAL)
Run an SQL query and return the result.
installErrorHandler()
Set a custom error handler for logging errors during database connection.
Definition Database.php:927
tableNamesN(... $tables)
Fetch a number of table names into an zero-indexed numerical array This is handy when you need to con...
close( $fname=__METHOD__, $owner=null)
Close the database connection.
Definition Database.php:990
startAtomic( $fname=__METHOD__, $cancelable=self::ATOMIC_NOT_CANCELABLE)
Begin an atomic section of SQL statements.
fetchRow(IResultWrapper $res)
Fetch the next row from the given result object, in associative array form.
Definition Database.php:876
addIdentifierQuotes( $s)
Escape a SQL identifier (e.g.table, column, database) for use in a SQL queryDepending on the database...
trxLevel()
Gets the current transaction level.
Definition Database.php:599
string null $server
Server that this instance is currently connected to.
Definition Database.php:83
normalizeConditions( $conds, $fname)
getFlag( $flag)
Returns a boolean whether the flag $flag is set for this connection.
Definition Database.php:864
string null $user
User that this instance is currently connected under the name of.
Definition Database.php:85
makeInsertLists(array $rows)
Make SQL lists of columns, row tuples for INSERT/VALUES expressions.
getBindingHandle()
Get the underlying binding connection handle.
makeOrderBy( $options)
Returns an optional ORDER BY.
getDBname()
Get the current database name; null if there isn't one.
fetchObject(IResultWrapper $res)
Fetch the next row from the given result object, in object form.
Definition Database.php:872
selectField( $table, $var, $cond='', $fname=__METHOD__, $options=[], $join_conds=[])
A SELECT wrapper which returns a single field from a single result row.
Used by Database::nextSequenceValue() so Database::insert() can detect values coming from the depreca...
static fromText(DatabasePostgres $db, $table, $field)
foreach( $mmfl['setupFiles'] as $fileName) if($queue) if(empty( $mmfl['quiet'])) $s
return true
Definition router.php:92