MediaWiki REL1_40
QueryPage.php
Go to the documentation of this file.
1<?php
35
45abstract class QueryPage extends SpecialPage {
47 protected $listoutput = false;
48
50 protected $offset = 0;
51
53 protected $limit = 0;
54
62 protected $numRows;
63
67 protected $cachedTimestamp = null;
68
72 protected $shownavigation = true;
73
75 private $loadBalancer = null;
76
78 private $linkBatchFactory = null;
79
90 public static function getPages() {
91 static $qp = null;
92
93 if ( $qp === null ) {
94 $qp = [
95 [ SpecialAncientPages::class, 'Ancientpages' ],
96 [ SpecialBrokenRedirects::class, 'BrokenRedirects' ],
97 [ SpecialDeadendPages::class, 'Deadendpages' ],
98 [ SpecialDoubleRedirects::class, 'DoubleRedirects' ],
99 [ SpecialListDuplicatedFiles::class, 'ListDuplicatedFiles' ],
100 [ SpecialLinkSearch::class, 'LinkSearch' ],
101 [ SpecialListRedirects::class, 'Listredirects' ],
102 [ SpecialLonelyPages::class, 'Lonelypages' ],
103 [ SpecialLongPages::class, 'Longpages' ],
104 [ SpecialMediaStatistics::class, 'MediaStatistics', SpecialMediaStatistics::MAX_LIMIT ],
105 [ SpecialMIMESearch::class, 'MIMEsearch' ],
106 [ SpecialMostCategories::class, 'Mostcategories' ],
107 [ SpecialMostImages::class, 'Mostimages' ],
108 [ SpecialMostInterwikis::class, 'Mostinterwikis' ],
109 [ SpecialMostLinkedCategories::class, 'Mostlinkedcategories' ],
110 [ SpecialMostLinkedTemplates::class, 'Mostlinkedtemplates' ],
111 [ SpecialMostLinked::class, 'Mostlinked' ],
112 [ SpecialMostRevisions::class, 'Mostrevisions' ],
113 [ SpecialFewestRevisions::class, 'Fewestrevisions' ],
114 [ SpecialShortPages::class, 'Shortpages' ],
115 [ SpecialUncategorizedCategories::class, 'Uncategorizedcategories' ],
116 [ SpecialUncategorizedPages::class, 'Uncategorizedpages' ],
117 [ SpecialUncategorizedImages::class, 'Uncategorizedimages' ],
118 [ SpecialUncategorizedTemplates::class, 'Uncategorizedtemplates' ],
119 [ SpecialUnusedCategories::class, 'Unusedcategories' ],
120 [ SpecialUnusedImages::class, 'Unusedimages' ],
121 [ SpecialWantedCategories::class, 'Wantedcategories' ],
122 [ SpecialWantedFiles::class, 'Wantedfiles' ],
123 [ SpecialWantedPages::class, 'Wantedpages' ],
124 [ SpecialWantedTemplates::class, 'Wantedtemplates' ],
125 [ SpecialUnwatchedPages::class, 'Unwatchedpages' ],
126 [ SpecialUnusedTemplates::class, 'Unusedtemplates' ],
127 [ SpecialWithoutInterwiki::class, 'Withoutinterwiki' ],
128 ];
129 Hooks::runner()->onWgQueryPages( $qp );
130 }
131
132 return $qp;
133 }
134
139 final protected function setLinkBatchFactory( LinkBatchFactory $linkBatchFactory ) {
140 $this->linkBatchFactory = $linkBatchFactory;
141 }
142
147 final protected function getLinkBatchFactory(): LinkBatchFactory {
148 if ( $this->linkBatchFactory === null ) {
149 // Fallback if not provided
150 // TODO Change to wfWarn in a future release
151 $this->linkBatchFactory = MediaWikiServices::getInstance()->getLinkBatchFactory();
152 }
153 return $this->linkBatchFactory;
154 }
155
161 public static function getDisabledQueryPages( Config $config ) {
162 $disableQueryPageUpdate = $config->get( MainConfigNames::DisableQueryPageUpdate );
163
164 if ( !is_array( $disableQueryPageUpdate ) ) {
165 return [];
166 }
167
168 $pages = [];
169 foreach ( $disableQueryPageUpdate as $name => $runMode ) {
170 if ( is_int( $name ) ) {
171 // The run mode may be omitted
172 $pages[$runMode] = 'disabled';
173 } else {
174 $pages[$name] = $runMode;
175 }
176 }
177 return $pages;
178 }
179
185 protected function setListoutput( $bool ) {
186 $this->listoutput = $bool;
187 }
188
216 public function getQueryInfo() {
217 // @phan-suppress-next-line PhanTypeMismatchReturnProbablyReal null needed for b/c checks
218 return null;
219 }
220
228 protected function getSQL() {
229 wfDeprecated( __METHOD__, '1.39' );
230 throw new MWException( "Bug in a QueryPage: doesn't implement getQueryInfo() nor "
231 . "getQuery() properly" );
232 }
233
242 protected function getOrderFields() {
243 return [ 'value' ];
244 }
245
257 public function usesTimestamps() {
258 return false;
259 }
260
267 protected function sortDescending() {
268 return true;
269 }
270
291 public function isExpensive() {
292 return $this->getConfig()->get( MainConfigNames::DisableQueryPages );
293 }
294
303 public function isCacheable() {
304 return true;
305 }
306
314 public function isCached() {
315 return $this->isExpensive() && $this->getConfig()->get( MainConfigNames::MiserMode );
316 }
317
324 public function isSyndicated() {
325 return true;
326 }
327
337 abstract protected function formatResult( $skin, $result );
338
345 protected function getPageHeader() {
346 return '';
347 }
348
356 protected function showEmptyText() {
357 $this->getOutput()->addWikiMsg( 'specialpage-empty' );
358 }
359
368 protected function linkParameters() {
369 return [];
370 }
371
382 public function recache( $limit, $ignoreErrors = true ) {
383 if ( !$this->isCacheable() ) {
384 return 0;
385 }
386
387 $fname = static::class . '::recache';
388 $dbw = $this->getDBLoadBalancer()->getConnectionRef( ILoadBalancer::DB_PRIMARY );
389
390 try {
391 // Do query
392 $res = $this->reallyDoQuery( $limit, false );
393 $num = false;
394 if ( $res ) {
395 $num = $res->numRows();
396 // Fetch results
397 $vals = [];
398 foreach ( $res as $i => $row ) {
399 if ( isset( $row->value ) ) {
400 if ( $this->usesTimestamps() ) {
401 $value = (int)wfTimestamp( TS_UNIX, $row->value );
402 } else {
403 $value = intval( $row->value ); // T16414
404 }
405 } else {
406 $value = $i;
407 }
408
409 $vals[] = [
410 'qc_type' => $this->getName(),
411 'qc_namespace' => $row->namespace,
412 'qc_title' => $row->title,
413 'qc_value' => $value
414 ];
415 }
416
417 $dbw->doAtomicSection(
418 $fname,
419 function ( IDatabase $dbw, $fname ) use ( $vals ) {
420 // Clear out any old cached data
421 $dbw->delete( 'querycache',
422 [ 'qc_type' => $this->getName() ],
423 $fname
424 );
425 // Save results into the querycache table on the primary DB
426 if ( count( $vals ) ) {
427 $dbw->insert( 'querycache', $vals, $fname );
428 }
429 // Update the querycache_info record for the page
430 $dbw->upsert(
431 'querycache_info',
432 [
433 'qci_type' => $this->getName(),
434 'qci_timestamp' => $dbw->timestamp(),
435 ],
436 'qci_type',
437 [
438 'qci_timestamp' => $dbw->timestamp(),
439 ],
440 $fname
441 );
442 }
443 );
444 }
445 } catch ( DBError $e ) {
446 if ( !$ignoreErrors ) {
447 throw $e; // report query error
448 }
449 $num = false; // set result to false to indicate error
450 }
451
452 return $num;
453 }
454
460 protected function getRecacheDB() {
461 return $this->getDBLoadBalancer()
462 ->getConnectionRef( ILoadBalancer::DB_REPLICA, 'vslow' );
463 }
464
471 public function delete( LinkTarget $title ) {
472 if ( $this->isCached() ) {
473 $dbw = $this->getDBLoadBalancer()->getConnectionRef( ILoadBalancer::DB_PRIMARY );
474 $dbw->delete( 'querycache', [
475 'qc_type' => $this->getName(),
476 'qc_namespace' => $title->getNamespace(),
477 'qc_title' => $title->getDBkey(),
478 ], __METHOD__ );
479 }
480 }
481
487 public function deleteAllCachedData() {
488 $fname = static::class . '::' . __FUNCTION__;
489 $dbw = $this->getDBLoadBalancer()->getConnectionRef( ILoadBalancer::DB_PRIMARY );
490 $dbw->delete( 'querycache',
491 [ 'qc_type' => $this->getName() ],
492 $fname
493 );
494 $dbw->delete( 'querycachetwo',
495 [ 'qcc_type' => $this->getName() ],
496 $fname
497 );
498 $dbw->delete( 'querycache_info',
499 [ 'qci_type' => $this->getName() ],
500 $fname
501 );
502 }
503
512 public function reallyDoQuery( $limit, $offset = false ) {
513 $fname = static::class . '::reallyDoQuery';
514 $dbr = $this->getRecacheDB();
515 $query = $this->getQueryInfo();
516 $order = $this->getOrderFields();
517
518 if ( $this->sortDescending() ) {
519 foreach ( $order as &$field ) {
520 $field .= ' DESC';
521 }
522 }
523
524 if ( is_array( $query ) ) {
525 $tables = isset( $query['tables'] ) ? (array)$query['tables'] : [];
526 $fields = isset( $query['fields'] ) ? (array)$query['fields'] : [];
527 $conds = isset( $query['conds'] ) ? (array)$query['conds'] : [];
528 $options = isset( $query['options'] ) ? (array)$query['options'] : [];
529 $join_conds = isset( $query['join_conds'] ) ? (array)$query['join_conds'] : [];
530
531 if ( $order ) {
532 $options['ORDER BY'] = $order;
533 }
534
535 if ( $limit !== false ) {
536 $options['LIMIT'] = intval( $limit );
537 }
538
539 if ( $offset !== false ) {
540 $options['OFFSET'] = intval( $offset );
541 }
542
543 $res = $dbr->select( $tables, $fields, $conds, $fname,
544 $options, $join_conds
545 );
546 } else {
547 // Old-fashioned raw SQL style, deprecated
548 MWDebug::detectDeprecatedOverride(
549 $this,
550 __CLASS__,
551 'getSQL',
552 '1.39'
553 );
554 $sql = $this->getSQL();
555 $sql .= ' ORDER BY ' . implode( ', ', $order );
556 $sql = $dbr->limitResult( $sql, $limit, $offset );
557 $res = $dbr->query( $sql, $fname );
558 }
559
560 return $res;
561 }
562
569 public function doQuery( $offset = false, $limit = false ) {
570 if ( $this->isCached() && $this->isCacheable() ) {
571 return $this->fetchFromCache( $limit, $offset );
572 } else {
573 return $this->reallyDoQuery( $limit, $offset );
574 }
575 }
576
586 public function fetchFromCache( $limit, $offset = false ) {
587 $dbr = $this->getDBLoadBalancer()->getConnectionRef( ILoadBalancer::DB_REPLICA );
588 $options = [];
589
590 if ( $limit !== false ) {
591 $options['LIMIT'] = intval( $limit );
592 }
593
594 if ( $offset !== false ) {
595 $options['OFFSET'] = intval( $offset );
596 }
597
598 $order = $this->getCacheOrderFields();
599 if ( $this->sortDescending() ) {
600 foreach ( $order as &$field ) {
601 $field .= " DESC";
602 }
603 }
604 if ( $order ) {
605 $options['ORDER BY'] = $order;
606 }
607
608 return $dbr->select( 'querycache',
609 [ 'qc_type',
610 'namespace' => 'qc_namespace',
611 'title' => 'qc_title',
612 'value' => 'qc_value' ],
613 [ 'qc_type' => $this->getName() ],
614 __METHOD__,
615 $options
616 );
617 }
618
626 protected function getCacheOrderFields() {
627 return [ 'value' ];
628 }
629
633 public function getCachedTimestamp() {
634 if ( $this->cachedTimestamp === null ) {
635 $dbr = $this->getDBLoadBalancer()->getConnectionRef( ILoadBalancer::DB_REPLICA );
636 $fname = static::class . '::getCachedTimestamp';
637 $this->cachedTimestamp = $dbr->selectField( 'querycache_info', 'qci_timestamp',
638 [ 'qci_type' => $this->getName() ], $fname );
639 }
640 return $this->cachedTimestamp;
641 }
642
655 protected function getLimitOffset() {
656 [ $limit, $offset ] = $this->getRequest()
657 ->getLimitOffsetForUser( $this->getUser() );
658 if ( $this->getConfig()->get( MainConfigNames::MiserMode ) ) {
659 $maxResults = $this->getMaxResults();
660 // Can't display more than max results on a page
661 $limit = min( $limit, $maxResults );
662 // Can't skip over more than the end of $maxResults
663 $offset = min( $offset, $maxResults + 1 );
664 }
665 return [ $limit, $offset ];
666 }
667
676 protected function getDBLimit( $uiLimit, $uiOffset ) {
677 $maxResults = $this->getMaxResults();
678 if ( $this->getConfig()->get( MainConfigNames::MiserMode ) ) {
679 $limit = min( $uiLimit + 1, $maxResults - $uiOffset );
680 return max( $limit, 0 );
681 } else {
682 return $uiLimit + 1;
683 }
684 }
685
696 protected function getMaxResults() {
697 // Max of 10000, unless we store more than 10000 in query cache.
698 return max( $this->getConfig()->get( MainConfigNames::QueryCacheLimit ), 10000 );
699 }
700
707 public function execute( $par ) {
708 $this->checkPermissions();
709
710 $this->setHeaders();
711 $this->outputHeader();
712
713 $out = $this->getOutput();
714
715 if ( $this->isCached() && !$this->isCacheable() ) {
716 $out->addWikiMsg( 'querypage-disabled' );
717 return;
718 }
719
720 $out->setSyndicated( $this->isSyndicated() );
721
722 if ( $this->limit == 0 && $this->offset == 0 ) {
723 [ $this->limit, $this->offset ] = $this->getLimitOffset();
724 }
725 $dbLimit = $this->getDBLimit( $this->limit, $this->offset );
726 // @todo Use doQuery()
727 if ( !$this->isCached() ) {
728 // select one extra row for navigation
729 $res = $this->reallyDoQuery( $dbLimit, $this->offset );
730 } else {
731 // Get the cached result, select one extra row for navigation
732 $res = $this->fetchFromCache( $dbLimit, $this->offset );
733 if ( !$this->listoutput ) {
734 // Fetch the timestamp of this update
735 $ts = $this->getCachedTimestamp();
736 $lang = $this->getLanguage();
737 $maxResults = $lang->formatNum( $this->getConfig()->get(
738 MainConfigNames::QueryCacheLimit ) );
739
740 if ( $ts ) {
741 $user = $this->getUser();
742 $updated = $lang->userTimeAndDate( $ts, $user );
743 $updateddate = $lang->userDate( $ts, $user );
744 $updatedtime = $lang->userTime( $ts, $user );
745 $out->addMeta( 'Data-Cache-Time', $ts );
746 $out->addJsConfigVars( 'dataCacheTime', $ts );
747 $out->addWikiMsg( 'perfcachedts', $updated, $updateddate, $updatedtime, $maxResults );
748 } else {
749 $out->addWikiMsg( 'perfcached', $maxResults );
750 }
751
752 // If updates on this page have been disabled, let the user know
753 // that the data set won't be refreshed for now
754 $disabledQueryPages = self::getDisabledQueryPages( $this->getConfig() );
755 if ( isset( $disabledQueryPages[$this->getName()] ) ) {
756 $runMode = $disabledQueryPages[$this->getName()];
757 if ( $runMode === 'disabled' ) {
758 $out->wrapWikiMsg(
759 "<div class=\"mw-querypage-no-updates\">\n$1\n</div>",
760 'querypage-no-updates'
761 );
762 } else {
763 // Messages used here: querypage-updates-periodical
764 $out->wrapWikiMsg(
765 "<div class=\"mw-querypage-updates-" . $runMode . "\">\n$1\n</div>",
766 'querypage-updates-' . $runMode
767 );
768 }
769 }
770 }
771 }
772
773 $this->numRows = $res->numRows();
774
775 $dbr = $this->getRecacheDB();
776 $this->preprocessResults( $dbr, $res );
777
778 $out->addHTML( Xml::openElement( 'div', [ 'class' => 'mw-spcontent' ] ) );
779
780 // Top header and navigation
781 if ( $this->shownavigation ) {
782 $out->addHTML( $this->getPageHeader() );
783 if ( $this->numRows > 0 ) {
784 $out->addHTML( $this->msg( 'showingresultsinrange' )->numParams(
785 min( $this->numRows, $this->limit ), // do not show the one extra row, if exist
786 $this->offset + 1, ( min( $this->numRows, $this->limit ) + $this->offset ) )->parseAsBlock() );
787 // Disable the "next" link when we reach the end
788 $miserMaxResults = $this->getConfig()->get( MainConfigNames::MiserMode )
789 && ( $this->offset + $this->limit >= $this->getMaxResults() );
790 $atEnd = ( $this->numRows <= $this->limit ) || $miserMaxResults;
791 $paging = $this->buildPrevNextNavigation( $this->offset,
792 $this->limit, $this->linkParameters(), $atEnd, $par );
793 $out->addHTML( '<p>' . $paging . '</p>' );
794 } else {
795 // No results to show, so don't bother with "showing X of Y" etc.
796 // -- just let the user know and give up now
797 $this->showEmptyText();
798 $out->addHTML( Xml::closeElement( 'div' ) );
799 return;
800 }
801 }
802
803 // The actual results; specialist subclasses will want to handle this
804 // with more than a straight list, so we hand them the info, plus
805 // an OutputPage, and let them get on with it
806 $this->outputResults( $out,
807 $this->getSkin(),
808 $dbr, // Should use IResultWrapper for this
809 $res,
810 min( $this->numRows, $this->limit ), // do not format the one extra row, if exist
811 $this->offset );
812
813 // Repeat the paging links at the bottom
814 if ( $this->shownavigation ) {
815 // @phan-suppress-next-line PhanPossiblyUndeclaredVariable paging is set when used here
816 $out->addHTML( '<p>' . $paging . '</p>' );
817 }
818
819 $out->addHTML( Xml::closeElement( 'div' ) );
820 }
821
835 protected function outputResults( $out, $skin, $dbr, $res, $num, $offset ) {
836 if ( $num > 0 ) {
837 $html = [];
838 if ( !$this->listoutput ) {
839 $html[] = $this->openList( $offset );
840 }
841
842 // $res might contain the whole 1,000 rows, so we read up to
843 // $num [should update this to use a Pager]
844 for ( $i = 0; $i < $num && $row = $res->fetchObject(); $i++ ) {
845 $line = $this->formatResult( $skin, $row );
846 if ( $line ) {
847 $html[] = $this->listoutput
848 ? $line
849 : "<li>{$line}</li>\n";
850 }
851 }
852
853 if ( !$this->listoutput ) {
854 $html[] = $this->closeList();
855 }
856
857 $html = $this->listoutput
858 ? $this->getContentLanguage()->listToText( $html )
859 : implode( '', $html );
860
861 $out->addHTML( $html );
862 }
863 }
864
869 protected function openList( $offset ) {
870 return "\n<ol start='" . ( $offset + 1 ) . "' class='special'>\n";
871 }
872
876 protected function closeList() {
877 return "</ol>\n";
878 }
879
886 protected function preprocessResults( $db, $res ) {
887 }
888
901 protected function executeLBFromResultWrapper( IResultWrapper $res, $ns = null ) {
902 if ( !$res->numRows() ) {
903 return;
904 }
905
906 $batch = $this->getLinkBatchFactory()->newLinkBatch();
907 foreach ( $res as $row ) {
908 $batch->add( $ns ?? (int)$row->namespace, $row->title );
909 }
910 $batch->execute();
911
912 $res->seek( 0 );
913 }
914
919 final protected function setDBLoadBalancer( ILoadBalancer $loadBalancer ) {
920 $this->loadBalancer = $loadBalancer;
921 }
922
927 final protected function getDBLoadBalancer(): ILoadBalancer {
928 if ( $this->loadBalancer === null ) {
929 // Fallback if not provided
930 // TODO Change to wfWarn in a future release
931 $this->loadBalancer = MediaWikiServices::getInstance()->getDBLoadBalancer();
932 }
933 return $this->loadBalancer;
934 }
935}
getUser()
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that a deprecated feature was used.
if(!defined('MW_SETUP_CALLBACK'))
The persistent session ID (if any) loaded at startup.
Definition WebStart.php:88
MediaWiki exception.
A class containing constants representing the names of configuration variables.
Service locator for MediaWiki core services.
A special page that lists most used images.
Querypage that lists the most wanted files.
A special page that lists most linked pages that does not exist.
This is a class for doing query pages; since they're almost all the same, we factor out some of the f...
Definition QueryPage.php:45
isExpensive()
Should this query page only be updated offline on large wikis?
linkParameters()
If using extra form wheely-dealies, return a set of parameters here as an associative array.
getMaxResults()
Get max number of results we can return in miser mode.
doQuery( $offset=false, $limit=false)
Somewhat deprecated, you probably want to be using execute()
executeLBFromResultWrapper(IResultWrapper $res, $ns=null)
Creates a new LinkBatch object, adds all pages from the passed result wrapper (MUST include title and...
setListoutput( $bool)
A mutator for $this->listoutput;.
static getDisabledQueryPages(Config $config)
Get a list of disabled query pages and their run mode.
recache( $limit, $ignoreErrors=true)
Clear the cache and save new results.
setDBLoadBalancer(ILoadBalancer $loadBalancer)
fetchFromCache( $limit, $offset=false)
Fetch the query results from the query cache.
int $offset
The offset and limit in use, as passed to the query() function.
Definition QueryPage.php:50
outputResults( $out, $skin, $dbr, $res, $num, $offset)
Format and output report results using the given information plus OutputPage.
isCached()
Whether or not the output of the page in question is retrieved from the database cache.
sortDescending()
Override to sort by increasing values.
deleteAllCachedData()
Remove all cached value This is needed when the page is no longer using the cache.
formatResult( $skin, $result)
Formats the results of the query for display.
isSyndicated()
Sometimes we don't want to build rss / atom feeds.
getCachedTimestamp()
static getPages()
Get a list of query page classes and their associated special pages, for periodic updates.
Definition QueryPage.php:90
bool $shownavigation
Whether to show prev/next links.
Definition QueryPage.php:72
reallyDoQuery( $limit, $offset=false)
Run the query and return the result.
isCacheable()
Is the output of this query cacheable? Non-cacheable expensive pages will be disabled in miser mode a...
getOrderFields()
Subclasses return an array of fields to order by here.
getLinkBatchFactory()
showEmptyText()
Outputs some kind of an informative message (via OutputPage) to let the user know that the query retu...
usesTimestamps()
Does this query return timestamps rather than integers in its 'value' field? If true,...
getCacheOrderFields()
Return the order fields for fetchFromCache.
getQueryInfo()
Subclasses return an SQL query here, formatted as an array with the following keys: tables => Table(s...
openList( $offset)
getDBLimit( $uiLimit, $uiOffset)
What is limit to fetch from DB.
int $numRows
The number of rows returned by the query.
Definition QueryPage.php:62
preprocessResults( $db, $res)
Do any necessary preprocessing of the result object.
setLinkBatchFactory(LinkBatchFactory $linkBatchFactory)
getSQL()
For back-compat, subclasses may return a raw SQL query here, as a string.
getPageHeader()
The content returned by this function will be output before any result.
bool $listoutput
Whether or not we want plain listoutput rather than an ordered list.
Definition QueryPage.php:47
execute( $par)
This is the actual workhorse.
string null false $cachedTimestamp
Definition QueryPage.php:67
getDBLoadBalancer()
getLimitOffset()
Returns limit and offset, as returned by $this->getRequest()->getLimitOffsetForUser().
getRecacheDB()
Get a DB connection to be used for slow recache queries.
Parent class for all special pages.
Database error base class.
Definition DBError.php:31
Interface for configuration instances.
Definition Config.php:30
get( $name)
Get a configuration variable such as "Sitename" or "UploadMaintenance.".
Basic database interface for live and lazy-loaded relation database handles.
Definition IDatabase.php:36
delete( $table, $conds, $fname=__METHOD__)
Delete all rows in a table that match a condition.
upsert( $table, array $rows, $uniqueKeys, array $set, $fname=__METHOD__)
Upsert row(s) into a table, in the provided order, while updating conflicting rows.
insert( $table, $rows, $fname=__METHOD__, $options=[])
Insert row(s) into a table, in the provided order.
This class is a delegate to ILBFactory for a given database cluster.
Result wrapper for grabbing data queried from an IDatabase object.
timestamp( $ts=0)
Convert a timestamp in one of the formats accepted by ConvertibleTimestamp to the format used for ins...
if(!isset( $args[0])) $lang