diff --git a/formats/datatables/QuerySegmentListProcessor.php b/formats/datatables/QuerySegmentListProcessor.php new file mode 100644 index 000000000..1ca726f57 --- /dev/null +++ b/formats/datatables/QuerySegmentListProcessor.php @@ -0,0 +1,390 @@ +joinConditions and $this->fromTables + * to the original QuerySegmentListProcessor for the + * use with SearchPanes + */ +namespace SRF\DataTables; + +use RuntimeException; +use SMW\MediaWiki\Database; +use SMW\SQLStore\QueryEngine\QuerySegment; +use SMW\SQLStore\TableBuilder\TemporaryTableBuilder; +use SMWQuery as Query; + +class QuerySegmentListProcessor { + /* @var array */ + public $joinConditions = []; + + /* @var array */ + public $fromTables = []; + + /** + * @var Database + */ + private $connection; + + /** + * @var TemporaryTableBuilder + */ + private $temporaryTableBuilder; + + /** + * @var HierarchyTempTableBuilder + */ + private $hierarchyTempTableBuilder; + + /** + * Array of arrays of executed queries, indexed by the temporary table names + * results were fed into. + * + * @var array + */ + private $executedQueries = []; + + /** + * Query mode copied from given query. Some submethods act differently when + * in Query::MODE_DEBUG. + * + * @var int + */ + private $queryMode; + + /** + * @var array + */ + private $querySegmentList = []; + + /** + * @param Database $connection + * @param TemporaryTableBuilder $temporaryTableBuilder + * @param HierarchyTempTableBuilder $hierarchyTempTableBuilder + */ + public function __construct( $connection, TemporaryTableBuilder $temporaryTableBuilder, $hierarchyTempTableBuilder ) { + $this->connection = $connection; + $this->temporaryTableBuilder = $temporaryTableBuilder; + $this->hierarchyTempTableBuilder = $hierarchyTempTableBuilder; + } + + /** + * @since 2.2 + * + * @return array + */ + public function getExecutedQueries() { + return $this->executedQueries; + } + + /** + * @since 2.2 + * + * @param &$querySegmentList + */ + public function setQuerySegmentList( &$querySegmentList ) { + $this->querySegmentList =& $querySegmentList; + } + + /** + * @since 2.2 + * + * @param int $queryMode + */ + public function setQueryMode( $queryMode ) { + $this->queryMode = $queryMode; + } + + /** + * Process stored queries and change store accordingly. The query obj is modified + * so that it contains non-recursive description of a select to execute for getting + * the actual result. + * + * @param int $id + * + * @throws RuntimeException + */ + public function process( $id ) { + $this->hierarchyTempTableBuilder->emptyHierarchyCache(); + $this->executedQueries = []; + + // Should never happen + if ( !isset( $this->querySegmentList[$id] ) ) { + throw new RuntimeException( "$id doesn't exist" ); + } + + $this->segment( $this->querySegmentList[$id] ); + } + + private function segment( QuerySegment &$query ) { + switch ( $query->type ) { + case QuerySegment::Q_TABLE: // . + $this->table( $query ); + break; + case QuerySegment::Q_CONJUNCTION: + $this->conjunction( $query ); + break; + case QuerySegment::Q_DISJUNCTION: + $this->disjunction( $query ); + break; + case QuerySegment::Q_PROP_HIERARCHY: + case QuerySegment::Q_CLASS_HIERARCHY: // make a saturated hierarchy + $this->hierarchy( $query ); + break; + case QuerySegment::Q_VALUE: + break; // nothing to do + } + } + + /** + * Resolves normal queries with possible conjunctive subconditions + */ + private function table( QuerySegment &$query ) { + foreach ( $query->components as $qid => $joinField ) { + $subQuery = $this->querySegmentList[$qid]; + $this->segment( $subQuery ); + $alias = $subQuery->alias; + + if ( $subQuery->joinTable !== '' ) { // Join with jointable.joinfield + $op = $subQuery->not ? '!' : ''; + + $joinType = $subQuery->joinType ? $subQuery->joinType : 'INNER'; + $t = $this->connection->tableName( $subQuery->joinTable ) . " AS $subQuery->alias"; + // If the alias is the same as the table name and if there is a prefix, MediaWiki does not declare the unprefixed alias + $joinTable = $subQuery->joinTable === $subQuery->alias ? $this->connection->tableName( $subQuery->joinTable ) : $subQuery->joinTable; + + if ( $subQuery->from ) { + $t = "($t $subQuery->from)"; + $alias = 'nested' . $subQuery->alias; + $query->fromTables[$alias] = array_merge( (array)$subQuery->fromTables, [ $subQuery->alias => $joinTable ] ); + $query->joinConditions = array_merge( (array)$query->joinConditions, (array)$subQuery->joinConditions ); + + } else { + $query->fromTables[$alias] = $joinTable; + } + + $query->joinConditions[$alias] = [ $joinType . ' JOIN', "$joinField$op=" . $subQuery->joinfield ]; + + $this->fromTables[$subQuery->alias] = $joinTable; + + ksort( $this->fromTables ); + $this->joinConditions[$subQuery->alias] = [ $joinType . ' JOIN', "$joinField$op=" . $subQuery->joinfield ]; + + $query->from .= " $joinType JOIN $t ON $joinField$op=" . $subQuery->joinfield; + if ( $joinType === 'LEFT' ) { + $query->where .= ( ( $query->where === '' ) ? '' : ' AND ' ) . '(' . $subQuery->joinfield . ' IS NULL)'; + } + + } elseif ( $subQuery->joinfield !== '' ) { // Require joinfield as "value" via WHERE. + $condition = ''; + + if ( $subQuery->null === true ) { + $condition .= ( $condition ? ' OR ' : '' ) . "$joinField IS NULL"; + } else { + foreach ( $subQuery->joinfield as $value ) { + $op = $subQuery->not ? '!' : ''; + $condition .= ( $condition ? ' OR ' : '' ) . "$joinField$op=" . $this->connection->addQuotes( $value ); + } + } + + if ( count( $subQuery->joinfield ) > 1 ) { + $condition = "($condition)"; + } + + $query->where .= ( ( $query->where === '' || $subQuery->where === null ) ? '' : ' AND ' ) . $condition; + $query->from .= $subQuery->from; + $query->fromTables = array_merge( (array)$query->fromTables, (array)$subQuery->fromTables ); + $query->joinConditions = array_merge( (array)$query->joinConditions, (array)$subQuery->joinConditions ); + } else { // interpret empty joinfields as impossible condition (empty result) + $query->joinfield = ''; // make whole query false + $query->joinTable = ''; + $query->where = ''; + $query->from = ''; + $query->fromTables = []; + $query->joinConditions = []; + break; + } + + if ( $subQuery->where !== '' && $subQuery->where !== null ) { + if ( $subQuery->joinType === 'LEFT' || $subQuery->joinType == 'LEFT OUTER' ) { + $query->from .= ' AND (' . $subQuery->where . ')'; + $query->joinConditions[$alias][1] .= ' AND (' . $subQuery->where . ')'; + } else { + $query->where .= ( ( $query->where === '' ) ? '' : ' AND ' ) . '(' . $subQuery->where . ')'; + } + } + } + + $query->components = []; + } + + private function conjunction( QuerySegment &$query ) { + reset( $query->components ); + $key = false; + + // Pick one subquery as anchor point ... + foreach ( $query->components as $qkey => $qid ) { + $key = $qkey; + + if ( $this->querySegmentList[$qkey]->joinTable !== '' ) { + break; + } + } + + $result = $this->querySegmentList[$key]; + unset( $query->components[$key] ); + + // Execute it first (may change jointable and joinfield, e.g. when + // making temporary tables) + $this->segment( $result ); + + // ... and append to this query the remaining queries. + foreach ( $query->components as $qid => $joinfield ) { + $result->components[$qid] = $result->joinfield; + } + + // Second execute, now incorporating remaining conditions. + $this->segment( $result ); + + $query = $result; + } + + private function disjunction( QuerySegment &$query ) { + if ( $this->queryMode !== Query::MODE_NONE ) { + $this->temporaryTableBuilder->create( $this->connection->tableName( $query->alias ) ); + } + + $this->executedQueries[$query->alias] = []; + + foreach ( $query->components as $qid => $joinField ) { + $subQuery = $this->querySegmentList[$qid]; + $this->segment( $subQuery ); + $sql = ''; + + if ( $subQuery->joinTable !== '' ) { + $sql = 'INSERT ' . 'IGNORE ' . 'INTO ' . + $this->connection->tableName( $query->alias ) . + " SELECT DISTINCT $subQuery->joinfield FROM " . $this->connection->tableName( $subQuery->joinTable ) . + " AS $subQuery->alias $subQuery->from" . ( $subQuery->where ? " WHERE $subQuery->where" : '' ); + } elseif ( $subQuery->joinfield !== '' ) { + // NOTE: this works only for single "unconditional" values without further + // WHERE or FROM. The execution must take care of not creating any others. + $values = ''; + + // This produces an error on postgres with + // pg_query(): Query failed: ERROR: duplicate key value violates + // unique constraint "sunittest_t3_pkey" DETAIL: Key (id)=(274) already exists. + + foreach ( $subQuery->joinfield as $value ) { + $values .= ( $values ? ',' : '' ) . '(' . $this->connection->addQuotes( $value ) . ')'; + } + + $sql = 'INSERT ' . 'IGNORE ' . 'INTO ' . $this->connection->tableName( $query->alias ) . " (id) VALUES $values"; + } // else: // interpret empty joinfields as impossible condition (empty result), ignore + + if ( $sql ) { + $this->executedQueries[$query->alias][] = $sql; + + if ( $this->queryMode !== Query::MODE_NONE ) { + $this->connection->query( + $sql, + __METHOD__, + ISQLPlatform::QUERY_CHANGE_ROWS + ); + } + } + } + + $query->type = QuerySegment::Q_TABLE; + $query->where = ''; + $query->components = []; + + $query->joinTable = $query->alias; + $query->joinfield = "$query->alias.id"; + $query->sortfields = []; // Make sure we got no sortfields. + + // TODO: currently this eliminates sortkeys, possibly keep them (needs + // different temp table format though, maybe not such a good thing to do) + } + + /** + * Find subproperties or subcategories. This may require iterative computation, + * and temporary tables are used in many cases. + * + * @param QuerySegment &$query + */ + private function hierarchy( QuerySegment &$query ) { + switch ( $query->type ) { + case QuerySegment::Q_PROP_HIERARCHY: + $type = 'property'; + break; + case QuerySegment::Q_CLASS_HIERARCHY: + $type = 'class'; + break; + } + + [ $smwtable, $depth ] = $this->hierarchyTempTableBuilder->getTableDefinitionByType( + $type + ); + + // An individual depth was annotated as part of the query + if ( $query->depth !== null ) { + $depth = $query->depth; + } + + if ( $depth <= 0 ) { // treat as value, no recursion + $query->type = QuerySegment::Q_VALUE; + return; + } + + $values = ''; + $valuecond = ''; + + foreach ( $query->joinfield as $value ) { + $values .= ( $values ? ',' : '' ) . '(' . $this->connection->addQuotes( $value ) . ')'; + $valuecond .= ( $valuecond ? ' OR ' : '' ) . 'o_id=' . $this->connection->addQuotes( $value ); + } + + // Try to safe time (SELECT is cheaper than creating/dropping 3 temp tables): + $res = $this->connection->select( + $smwtable, + 's_id', + $valuecond, + __METHOD__, + [ 'LIMIT' => 1 ] + ); + + if ( !$res->fetchObject() ) { // no subobjects, we are done! + $res->free(); + $query->type = QuerySegment::Q_VALUE; + return; + } + + $res->free(); + $tablename = $this->connection->tableName( $query->alias ); + $this->executedQueries[$query->alias] = [ + "Recursively computed hierarchy for element(s) $values.", + "SELECT s_id FROM $smwtable WHERE $valuecond LIMIT 1" + ]; + + $query->joinTable = $query->alias; + $query->joinfield = "$query->alias.id"; + + $this->hierarchyTempTableBuilder->fillTempTable( + $type, + $tablename, + $values, + $depth + ); + } + + /** + * After querying, make sure no temporary database tables are left. + * + * @todo I might be better to keep the tables and possibly reuse them later + * on. Being temporary, the tables will vanish with the session anyway. + */ + public function cleanUp() { + foreach ( $this->executedQueries as $table => $log ) { + $this->temporaryTableBuilder->drop( $this->connection->tableName( $table ) ); + } + } +} diff --git a/formats/datatables/SearchPanes.php b/formats/datatables/SearchPanes.php index 9c8b47c85..4cdfba65b 100644 --- a/formats/datatables/SearchPanes.php +++ b/formats/datatables/SearchPanes.php @@ -17,15 +17,18 @@ use SMW\DIWikiPage; use SMW\Query\PrintRequest; use SMW\Services\ServicesFactory as ApplicationFactory; +use SMW\SQLStore\QueryEngine\HierarchyTempTableBuilder; use SMW\SQLStore\QueryEngine\QuerySegment; use SMW\SQLStore\QueryEngineFactory; use SMW\SQLStore\SQLStore; use SMW\SQLStore\TableBuilder\FieldType; -use SMWDataItem as DataItem; +use SMW\SQLStore\TableBuilder\TemporaryTableBuilder; use SMWQueryProcessor; use SRF\DataTables; class SearchPanes { + /** @var DataTables */ + private $datatables; private array $searchPanesLog = []; @@ -33,9 +36,58 @@ class SearchPanes { private $connection; - public function __construct( - private DataTables $datatables - ) { + public function __construct( DataTables $datatables ) { + $this->datatables = $datatables; + } + + private function newTemporaryTableBuilder() { + $temporaryTableBuilder = new TemporaryTableBuilder( + $this->datatables->store->getConnection( 'mw.db.queryengine' ) + ); + + $temporaryTableBuilder->setAutoCommitFlag( + ApplicationFactory::getInstance()->getSettings()->get( 'smwgQTemporaryTablesAutoCommitMode' ) + ); + + return $temporaryTableBuilder; + } + + /** + * @see SMW\SQLStore\QueryEngineFactory + * @return QuerySegmentListProcessor + */ + public function newQuerySegmentListProcessor() { + $settings = ApplicationFactory::getInstance()->getSettings(); + + $connection = $this->datatables->store->getConnection( 'mw.db.queryengine' ); + $temporaryTableBuilder = $this->newTemporaryTableBuilder(); + + $hierarchyTempTableBuilder = new HierarchyTempTableBuilder( + $connection, + $temporaryTableBuilder + ); + + $hierarchyTempTableBuilder->setTableDefinitions( + [ + 'property' => [ + 'table' => $this->datatables->store->findPropertyTableID( new DIProperty( '_SUBP' ) ), + 'depth' => $settings->get( 'smwgQSubpropertyDepth' ) + ], + 'class' => [ + 'table' => $this->datatables->store->findPropertyTableID( new DIProperty( '_SUBC' ) ), + 'depth' => $settings->get( 'smwgQSubcategoryDepth' ) + ] + + ] + ); + + $querySegmentListProcessor = new QuerySegmentListProcessor( + $connection, + $temporaryTableBuilder, + $hierarchyTempTableBuilder + ); + + return $querySegmentListProcessor; } public function getSearchPanes( array $printRequests, array $searchPanesOptions ): array { @@ -117,7 +169,8 @@ private function getPanesOptions( QuerySegment::$qnum = 0; $querySegmentList = $conditionBuilder->getQuerySegmentList(); - $querySegmentListProcessor = $this->queryEngineFactory->newQuerySegmentListProcessor(); + // $querySegmentListProcessor = $this->queryEngineFactory->newQuerySegmentListProcessor(); + $querySegmentListProcessor = $this->newQuerySegmentListProcessor(); $querySegmentListProcessor->setQuerySegmentList( $querySegmentList ); @@ -126,30 +179,46 @@ private function getPanesOptions( $qobj = $querySegmentList[$rootid]; + $tables = $querySegmentListProcessor->fromTables; + $joins = $querySegmentListProcessor->joinConditions; + + $tables[$qobj->alias] = $qobj->joinTable; + + $conds = $qobj->where; + $property = new DIProperty( DIProperty::newFromUserLabel( $printRequest->getCanonicalLabel() ) ); $propTypeid = $property->findPropertyValueType(); if ( $isCategory ) { - // data-length without the GROUP BY clause - $sql_options = [ 'LIMIT' => 1 ]; + $sql_options_ = [ 'LIMIT' => 1 ]; + + $tables_ = $tables; + $tables_['insts'] = 'smw_fpt_inst'; + $fields_ = [ 'count' => 'COUNT(*)' ]; + $conds_ = $conds; + $joins_ = $joins; + $joins_['insts'] = [ 'JOIN', [ "$qobj->alias.smw_id = insts.s_id" ] ]; - $dataLength = (int)$this->connection->selectField( - $this->connection->tableName( $qobj->joinTable ) . " AS $qobj->alias" . $qobj->from - . ' JOIN ' . $this->connection->tableName( 'smw_fpt_inst' ) . " AS insts ON $qobj->alias.smw_id = insts.s_id", - "COUNT(*) AS count", - $qobj->where, + $res = $this->connection->select( + $tables_, + $fields_, + $conds_, __METHOD__, - $sql_options + $sql_options_, + $joins_ ); + $row = $res->fetchRow(); + $dataLength = (int)( $row['count'] ?? 0 ); + if ( !$dataLength ) { return []; } $groupBy = "i.smw_id"; $orderBy = "count DESC, $groupBy ASC"; - $sql_options = [ + $sql_options_ = [ 'GROUP BY' => $groupBy, // $this->query->getOption( 'count' ), 'LIMIT' => $dataLength, @@ -167,15 +236,22 @@ private function getPanesOptions( HAVING COUNT(i.smw_id) >= 1 ORDER BY COUNT(i.smw_id) DESC */ + $tables_ = $tables; + $tables_['insts'] = 'smw_fpt_inst'; + $tables_['i'] = SQLStore::ID_TABLE; + $joins_ = $joins; + $joins_['insts'] = [ 'JOIN', [ "$qobj->alias.smw_id = insts.s_id" ] ]; + $joins_['i'] = [ 'JOIN', [ 'i.smw_id = insts.o_id' ] ]; + $conds_ = $conds; + $fields_ = "COUNT($groupBy) AS count, i.smw_id, i.smw_title, i.smw_namespace, i.smw_iw, i.smw_sort, i.smw_subobject"; + $res = $this->connection->select( - $this->connection->tableName( $qobj->joinTable ) . " AS $qobj->alias" . $qobj->from - // @see https://github.com/SemanticMediaWiki/SemanticDrilldown/blob/master/includes/Sql/SqlProvider.php - . ' JOIN ' . $this->connection->tableName( 'smw_fpt_inst' ) . " AS insts ON $qobj->alias.smw_id = insts.s_id" - . ' JOIN ' . $this->connection->tableName( SQLStore::ID_TABLE ) . " AS i ON i.smw_id = insts.o_id", - "COUNT($groupBy) AS count, i.smw_id, i.smw_title, i.smw_namespace, i.smw_iw, i.smw_sort, i.smw_subobject", - $qobj->where, + $tables_, + $fields_, + $conds_, __METHOD__, - $sql_options + $sql_options_, + $joins_ ); $isIdField = true; @@ -203,25 +279,34 @@ private function getPanesOptions( } // data-length without the GROUP BY clause - $sql_options = [ 'LIMIT' => 1 ]; + $sql_options_ = [ 'LIMIT' => 1 ]; // SELECT COUNT(*) as count FROM `smw_object_ids` AS t0 // INNER JOIN (`smw_fpt_mdat` AS t2 INNER JOIN `smw_di_wikipage` AS t3 ON t2.s_id=t3.s_id) ON t0.smw_id=t2.s_id // WHERE ((t3.p_id=517)) LIMIT 500 - $dataLength = (int)$this->connection->selectField( - $this->connection->tableName( $qobj->joinTable ) . " AS $qobj->alias" . $qobj->from, - "COUNT(*) as count", - $qobj->where, + $tables_ = $tables; + $fields_ = [ 'count' => 'COUNT(*)' ]; + $conds_ = $conds; + $joins_ = $joins; + + $res = $this->connection->select( + $tables_, + $fields_, + $conds_, __METHOD__, - $sql_options + $sql_options_, + $joins_ ); + $row = $res->fetchRow(); + $dataLength = (int)( $row['count'] ?? 0 ); + if ( !$dataLength ) { return []; } - [ $diType, $isIdField, $fields, $groupBy, $orderBy ] = $this->fetchValuesByGroup( $property, $p_alias, $propTypeid ); + [ $diType, $isIdField, $fields_, $groupBy, $orderBy ] = $this->fetchValuesByGroup( $property, $p_alias, $propTypeid ); /* ---GENERATED FROM DATATABLES @@ -231,7 +316,7 @@ private function getPanesOptions( SELECT i.smw_id,i.smw_title,i.smw_namespace,i.smw_iw,i.smw_subobject,i.smw_hash,i.smw_sort,COUNT( p.o_id ) as count FROM `smw_object_ids` `o` INNER JOIN `smw_di_wikipage` `p` ON ((p.s_id=o.smw_id)) JOIN `smw_object_ids` `i` ON ((p.o_id=i.smw_id)) WHERE o.smw_hash IN ('1_-_A','1_-_Ab','1_-_Abc','10_-_Abcd','11_-_Abc') AND (o.smw_iw!=':smw') AND (o.smw_iw!=':smw-delete') AND p.p_id = 517 GROUP BY p.o_id, i.smw_id ORDER BY count DESC, i.smw_sort ASC */ - $sql_options = [ + $sql_options_ = [ 'GROUP BY' => $groupBy, // the following implies that if the user sets a threshold // close or equal to 1, and there are too many unique values, @@ -243,18 +328,26 @@ private function getPanesOptions( ]; // @see QueryEngine + $tables_ = $tables; + $joins_ = $joins; + $conds_ = $conds; + + if ( $isIdField ) { + $tables_['i'] = SQLStore::ID_TABLE; + $joins_['i'] = [ 'JOIN', "$p_alias.o_id = i.smw_id" ]; + $conds_ .= !empty( $conds_ ) ? ' AND' : ''; + $conds_ .= ' i.smw_iw != ' . $this->connection->addQuotes( SMW_SQL3_SMWIW_OUTDATED ); + $conds_ .= ' AND i.smw_iw != ' . $this->connection->addQuotes( SMW_SQL3_SMWDELETEIW ); + } + $res = $this->connection->select( - $this->connection->tableName( $qobj->joinTable ) . " AS $qobj->alias" . $qobj->from - . ( !$isIdField ? '' - : " JOIN " . $this->connection->tableName( SQLStore::ID_TABLE ) . " AS `i` ON ($p_alias.o_id = i.smw_id)" ), - implode( ',', $fields ), - $qobj->where . ( !$isIdField ? '' : ( !empty( $qobj->where ) ? ' AND' : '' ) - . ' i.smw_iw!=' . $this->connection->addQuotes( SMW_SQL3_SMWIW_OUTDATED ) - . ' AND i.smw_iw!=' . $this->connection->addQuotes( SMW_SQL3_SMWDELETEIW ) ), + $tables_, + $fields_, + $conds_, __METHOD__, - $sql_options + $sql_options_, + $joins_ ); - } // verify uniqueRatio @@ -307,7 +400,6 @@ private function getPanesOptions( $isSubject = false; $groups = []; foreach ( $res as $row ) { - if ( $isIdField ) { $dbKeys = [ $row->smw_title, @@ -352,9 +444,7 @@ private function getPanesOptions( $dataValue->setOutputFormat( $outputFormat ); } -/* - - + /* // @see DIBlobHandler // $isKeyword = $dataItem->getOption( 'is.keyword' ); @@ -484,7 +574,6 @@ private function getPanesOptions( if ( $uniqueRatio > $threshold ) { return []; } - } arsort( $groups, SORT_NUMERIC ); @@ -619,7 +708,8 @@ private function searchPanesMainlabel( PrintRequest $printRequest, array $search QuerySegment::$qnum = 0; $querySegmentList = $conditionBuilder->getQuerySegmentList(); - $querySegmentListProcessor = $this->queryEngineFactory->newQuerySegmentListProcessor(); + // $querySegmentListProcessor = $this->queryEngineFactory->newQuerySegmentListProcessor(); + $querySegmentListProcessor = $this->newQuerySegmentListProcessor(); $querySegmentListProcessor->setQuerySegmentList( $querySegmentList ); @@ -628,7 +718,14 @@ private function searchPanesMainlabel( PrintRequest $printRequest, array $search $qobj = $querySegmentList[$rootid]; - $sql_options = [ + $tables = $querySegmentListProcessor->fromTables; + $joins = $querySegmentListProcessor->joinConditions; + + $tables[$qobj->alias] = $qobj->joinTable; + + $conds = $qobj->where; + + $sql_options_ = [ // *** should we set a limit here ? // it makes sense to show the pane for // mainlabel only when page titles are grouped @@ -639,21 +736,29 @@ private function searchPanesMainlabel( PrintRequest $printRequest, array $search // Selecting those is required in standard SQL (but MySQL does not require it). $sortfields = implode( ',', $qobj->sortfields ); - $sortfields = $sortfields ? ',' . $sortfields : ''; + // $sortfields = $sortfields ? ',' . $sortfields : ''; // @see QueryEngine + $tables_ = $tables; + $fields_ = []; + $fields_['id'] = "$qobj->alias.smw_id"; + $fields_['t'] = "$qobj->alias.smw_title"; + $fields_['ns'] = "$qobj->alias.smw_namespace"; + $fields_['iw'] = "$qobj->alias.smw_iw"; + $fields_['so'] = "$qobj->alias.smw_subobject"; + $fields_['sortkey'] = "$qobj->alias.smw_sortkey"; + $fields_[] = $sortfields; + + $conds_ = $conds; + $joins_ = $joins; + $res = $this->connection->select( - $this->connection->tableName( $qobj->joinTable ) . " AS $qobj->alias" . $qobj->from, - "$qobj->alias.smw_id AS id," . - "$qobj->alias.smw_title AS t," . - "$qobj->alias.smw_namespace AS ns," . - "$qobj->alias.smw_iw AS iw," . - "$qobj->alias.smw_subobject AS so," . - "$qobj->alias.smw_sortkey AS sortkey" . - "$sortfields", - $qobj->where, + $tables_, + $fields_, + $conds_, __METHOD__, - $sql_options + $sql_options_, + $joins_ ); $diHandler = $this->datatables->store->getDataItemHandlerForDIType( diff --git a/i18n/ar.json b/i18n/ar.json index 8760725b4..37df76454 100644 --- a/i18n/ar.json +++ b/i18n/ar.json @@ -205,6 +205,7 @@ "srf-ui-datatables-label-filters": "المرشحات", "srf-ui-datatables-label-information": "معلومات", "srf-ui-datatables-panel-disclaimer": "يمكن تغيير الوسائط والشروط، ولكن أي تغيير مؤقت ويتم التخلي عنه بعد تحديث الصفحة.", + "srf-ui-datatables-refresh-button-title": "تحديث الجدول", "srf-ui-datatables-label-update-success": "تم تحديث الجدول بنجاح", "srf-ui-datatables-label-update-error": "فشل تحديث الجدول.", "srf-ui-datatables-label-placeholder-column-search": "بحث...", @@ -229,6 +230,7 @@ "srf-ui-datatables-label-oPaginate-sPrevious": "السابق", "srf-ui-datatables-label-oAria-sSortAscending": ": تفعيل لفرز العمود تصاعديا", "srf-ui-datatables-label-oAria-sSortDescending": ": تفعيل لفرز العمود تنازليا", + "srf-ui-datatables-label-rows": "{{PLURAL:$1| صف|$1 صفوف|$1 صف}}", "srf-ui-datatables-label-rows-all": "كل الصفوف", "srf-printername-tree": "زود بعمود", "srf-printername-ultree": "Ultree", @@ -356,5 +358,6 @@ "srf-printername-gantt": "جانت", "srf-paramdesc-nodelabel": "استخدام تسمية عقدة الرسم البياني، القيم المسموح بها: عنوان العرض.", "srf-printername-mode": "وضع", - "srf-printername-range": "النطاق" + "srf-printername-range": "النطاق", + "srf-printername-variance": "التباين" } diff --git a/i18n/de-formal.json b/i18n/de-formal.json deleted file mode 100644 index 2e30633f9..000000000 --- a/i18n/de-formal.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Kghbln" - ] - }, - "srf-prefs-intro-text": "Die Erweiterung „Semantic Result Formats“ ist installiert. Besuchen Sie bitte die [https://www.semantic-mediawiki.org/wiki/Help:Ergebnisformate Hilfeseite zu Ergebnisformaten] für weitere Informationen.", - "srf-ui-common-label-ajax-error": "Der Server meldete eine fehlgeschlagene Kommunikation für diesen $1. Bitte versuchen Sie, die Seite neu zu laden oder wenden Sie sich an $2." -} diff --git a/i18n/vi.json b/i18n/vi.json index 6a38556f9..ca030661c 100644 --- a/i18n/vi.json +++ b/i18n/vi.json @@ -29,12 +29,9 @@ "srf-paramdesc-pagelength": "Xác định độ dài trang", "srf-paramdesc-lengthmenu": "Xác định độ dài menu", "srf-paramdesc-datatables-columnstype": "Xác định loại cột dữ liệu", - "srf_printername_carousel": "băng chuyền", "srf-gallery-navigation-previous": "Trước", "srf-gallery-navigation-next": "Tiếp theo", "srf-paramdesc-graphname": "Đặt tiêu đề của biểu đồ", - "srf-ui-datatables-refresh-button-title": "làm mới bảng", - "srf-ui-datatables-panel-switch-button-title": "bảng chuyển đổi", "srf-filtered-noscript-error": "Không thể hiển thị kết quả vì JavaScript chưa được bật. Chuyển đến $1 .", "srf-paramdesc-carousel-class": "Chỉ định lớp CSS cho băng chuyền", "srf-paramdesc-carousel-width": "Chỉ định chiều rộng của băng chuyền", @@ -43,7 +40,5 @@ "srf-paramdesc-carousel-titleproperty": "Chỉ định tên của thuộc tính ngữ nghĩa có trên các trang được truy vấn để sử dụng làm tiêu đề", "srf-paramdesc-carousel-linkproperty": "Chỉ định tên của thuộc tính ngữ nghĩa có trên các trang được truy vấn để sử dụng cho các liên kết", "srf-paramdesc-carousel-imageproperty": "Chỉ định tên của thuộc tính ngữ nghĩa có trên các trang được truy vấn để sử dụng cho hình ảnh", - "srf-paramdesc-carousel-slick-option": "Chỉ định xem băng chuyền có nên sử dụng các tùy chọn được cung cấp bởi 'Slick' hay không", - "srf-ui-slideshow-slide-button-play": "chơi", - "srf-ui-slideshow-slide-button-pause": "tạm ngừng" + "srf-paramdesc-carousel-slick-option": "Chỉ định xem băng chuyền có nên sử dụng các tùy chọn được cung cấp bởi 'Slick' hay không" }