From 5126585314c282c836c90695ef93768591f691d3 Mon Sep 17 00:00:00 2001 From: thomas-topway-it Date: Sun, 24 Aug 2025 03:15:51 +0400 Subject: [PATCH 01/17] use structured queries --- formats/datatables/SearchPanes.php | 224 +++++++++++++++++++++++------ 1 file changed, 181 insertions(+), 43 deletions(-) diff --git a/formats/datatables/SearchPanes.php b/formats/datatables/SearchPanes.php index 9c8b47c85..d4e6bb5fe 100644 --- a/formats/datatables/SearchPanes.php +++ b/formats/datatables/SearchPanes.php @@ -70,6 +70,92 @@ public function getLog(): array { return $this->searchPanesLog; } + private function parseQuerySegment( $querySegment ) { +/* +parse QuerySegment in this form: +( + [type] => 1 + [depth] => + [fingerprint] => + [null] => + [not] => + [joinType] => + [joinTable] => smw_object_ids + [joinfield] => t0.smw_id + [indexField] => + [from] => INNER JOIN (`smw_fpt_mdat` AS t2 INNER JOIN `smw_di_blob` AS t3 ON t2.s_id=t3.s_id) ON t0.smw_id=t2.s_id + [fromTables] => Array + ( + [nestedt2] => Array + ( + [t3] => smw_di_blob + [t2] => smw_fpt_mdat + ) + + ) + + [joinConditions] => Array + ( + [t3] => Array + ( + [0] => INNER JOIN + [1] => t2.s_id=t3.s_id + ) + + [nestedt2] => Array + ( + [0] => INNER JOIN + [1] => t0.smw_id=t2.s_id + ) + + ) + + [where] => ((t3.p_id=519)) + [sortIndexField] => + [components] => Array + ( + ) + + [alias] => t0 + [sortfields] => Array + ( + [] => t0.smw_sort + ) + + [queryNumber] => 0 +) +*/ + $tables = [ + $querySegment['alias'] => $querySegment['joinTable'] + ]; + $joins = []; + $conds = $querySegment['where'] ?: []; + + if ( !empty( $querySegment['joinConditions'] ) ) { + foreach ( $querySegment['joinConditions'] as $alias => $joinInfo ) { + $aliasFixed = preg_replace( '/^nested/', '', $alias ); + $joins[$aliasFixed] = [ $joinInfo[0], [$joinInfo[1]] ]; + } + } + + if ( !empty( $querySegment['fromTables'] ) ) { + foreach ( $querySegment['fromTables'] as $nestedAlias => $nested ) { + if ( is_array($nested ) ) { + foreach ( $nested as $alias => $table ) { + $aliasFixed = preg_replace('/^nested/', '', $alias); + $tables[$aliasFixed] = $table; + } + } else { + $tables[$nestedAlias] = $nested; + } + } + } + + ksort($tables); + + return [ $tables, $joins, $conds ]; + } + private function getPanesOptions( PrintRequest $printRequest, string $canonicalLabel, @@ -126,21 +212,29 @@ private function getPanesOptions( $qobj = $querySegmentList[$rootid]; + [ $tables, $joins, $conds ] = $this->parseQuerySegment((array)$qobj); + $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, - __METHOD__, - $sql_options + $tables_, + $fields_, + $conds_, + __METHOD__, + $sql_options_, + $joins_ ); if ( !$dataLength ) { @@ -149,7 +243,7 @@ private function getPanesOptions( $groupBy = "i.smw_id"; $orderBy = "count DESC, $groupBy ASC"; - $sql_options = [ + $sql_options_ = [ 'GROUP BY' => $groupBy, // $this->query->getOption( 'count' ), 'LIMIT' => $dataLength, @@ -167,6 +261,7 @@ private function getPanesOptions( HAVING COUNT(i.smw_id) >= 1 ORDER BY COUNT(i.smw_id) DESC */ +/* $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 @@ -177,6 +272,24 @@ private function getPanesOptions( __METHOD__, $sql_options ); +*/ + $tables_ = $tables; + $tables_['insts'] = 'smw_fpt_inst'; + $tables_[SQLStore::ID_TABLE] = 'i'; + $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( + $tables_, + $fields_, + $conds_, + __METHOD__, + $sql_options_, + $joins_ + ); $isIdField = true; @@ -203,25 +316,31 @@ 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 + $tables_ = $tables; + $fields_ = [ 'count' => 'COUNT(*)' ]; + $conds_ = $conds; + $joins_ = $joins; + $dataLength = (int)$this->connection->selectField( - $this->connection->tableName( $qobj->joinTable ) . " AS $qobj->alias" . $qobj->from, - "COUNT(*) as count", - $qobj->where, - __METHOD__, - $sql_options + $tables_, + $fields_, + $conds_, + __METHOD__, + $sql_options_, + $joins_ ); 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 +350,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 +362,27 @@ 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); + } + + // perform the select $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 @@ -605,9 +733,9 @@ private function searchPanesMainlabel( PrintRequest $printRequest, array $search 'threshold' => $threshold, ]; - if ( $threshold < 1 ) { - return []; - } + // if ( $threshold < 1 ) { + // return []; + // } $query = $this->datatables->query; $queryDescription = $query->getDescription(); @@ -628,7 +756,9 @@ private function searchPanesMainlabel( PrintRequest $printRequest, array $search $qobj = $querySegmentList[$rootid]; - $sql_options = [ + [ $tables, $joins, $conds ] = $this->parseQuerySegment((array)$qobj); + + $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 +769,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, - __METHOD__, - $sql_options + $tables_, + $fields_, + $conds_, + __METHOD__, + $sql_options_, + $joins_ ); $diHandler = $this->datatables->store->getDataItemHandlerForDIType( From 84e6afbb16d973dc2a1318eb3b25554d55e242f0 Mon Sep 17 00:00:00 2001 From: thomas-topway-it Date: Sun, 24 Aug 2025 03:30:12 +0400 Subject: [PATCH 02/17] fix phpcs --- formats/datatables/SearchPanes.php | 72 +++++++++++++----------------- 1 file changed, 30 insertions(+), 42 deletions(-) diff --git a/formats/datatables/SearchPanes.php b/formats/datatables/SearchPanes.php index d4e6bb5fe..2135754c1 100644 --- a/formats/datatables/SearchPanes.php +++ b/formats/datatables/SearchPanes.php @@ -134,7 +134,7 @@ private function parseQuerySegment( $querySegment ) { if ( !empty( $querySegment['joinConditions'] ) ) { foreach ( $querySegment['joinConditions'] as $alias => $joinInfo ) { $aliasFixed = preg_replace( '/^nested/', '', $alias ); - $joins[$aliasFixed] = [ $joinInfo[0], [$joinInfo[1]] ]; + $joins[$aliasFixed] = [ $joinInfo[0], [ $joinInfo[1] ] ]; } } @@ -142,7 +142,7 @@ private function parseQuerySegment( $querySegment ) { foreach ( $querySegment['fromTables'] as $nestedAlias => $nested ) { if ( is_array($nested ) ) { foreach ( $nested as $alias => $table ) { - $aliasFixed = preg_replace('/^nested/', '', $alias); + $aliasFixed = preg_replace( '/^nested/', '', $alias ); $tables[$aliasFixed] = $table; } } else { @@ -151,7 +151,7 @@ private function parseQuerySegment( $querySegment ) { } } - ksort($tables); + ksort( $tables ); return [ $tables, $joins, $conds ]; } @@ -212,7 +212,7 @@ private function getPanesOptions( $qobj = $querySegmentList[$rootid]; - [ $tables, $joins, $conds ] = $this->parseQuerySegment((array)$qobj); + [ $tables, $joins, $conds ] = $this->parseQuerySegment( (array)$qobj ); $property = new DIProperty( DIProperty::newFromUserLabel( $printRequest->getCanonicalLabel() ) ); $propTypeid = $property->findPropertyValueType(); @@ -226,15 +226,15 @@ private function getPanesOptions( $fields_ = [ 'count' => 'COUNT(*)' ]; $conds_ = $conds; $joins_ = $joins; - $joins_['insts'] = [ 'JOIN', [ "$qobj->alias.smw_id = insts.s_id" ] ]; - + $joins_['insts'] = [ 'JOIN', [ "$qobj->alias.smw_id = insts.s_id" ] ]; + $dataLength = (int)$this->connection->selectField( - $tables_, - $fields_, - $conds_, - __METHOD__, - $sql_options_, - $joins_ + $tables_, + $fields_, + $conds_, + __METHOD__, + $sql_options_, + $joins_ ); if ( !$dataLength ) { @@ -261,24 +261,12 @@ private function getPanesOptions( HAVING COUNT(i.smw_id) >= 1 ORDER BY COUNT(i.smw_id) DESC */ -/* - $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, - __METHOD__, - $sql_options - ); -*/ $tables_ = $tables; $tables_['insts'] = 'smw_fpt_inst'; $tables_[SQLStore::ID_TABLE] = 'i'; $joins_ = $joins; - $joins_['insts'] = [ 'JOIN', [ "$qobj->alias.smw_id = insts.s_id" ] ]; - $joins_['i'] = [ 'JOIN', [ 'i.smw_id = insts.o_id' ] ]; + $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"; @@ -328,12 +316,12 @@ private function getPanesOptions( $joins_ = $joins; $dataLength = (int)$this->connection->selectField( - $tables_, - $fields_, - $conds_, - __METHOD__, - $sql_options_, - $joins_ + $tables_, + $fields_, + $conds_, + __METHOD__, + $sql_options_, + $joins_ ); if ( !$dataLength ) { @@ -366,12 +354,12 @@ private function getPanesOptions( $joins_ = $joins; $conds_ = $conds; - if ($isIdField) { + 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); + $conds_ .= ' i.smw_iw != ' . $this->connection->addQuotes( SMW_SQL3_SMWIW_OUTDATED ); + $conds_ .= ' AND i.smw_iw != ' . $this->connection->addQuotes( SMW_SQL3_SMWDELETEIW ); } // perform the select @@ -756,7 +744,7 @@ private function searchPanesMainlabel( PrintRequest $printRequest, array $search $qobj = $querySegmentList[$rootid]; - [ $tables, $joins, $conds ] = $this->parseQuerySegment((array)$qobj); + [ $tables, $joins, $conds ] = $this->parseQuerySegment( (array)$qobj ); $sql_options_ = [ // *** should we set a limit here ? @@ -786,12 +774,12 @@ private function searchPanesMainlabel( PrintRequest $printRequest, array $search $joins_ = $joins; $res = $this->connection->select( - $tables_, - $fields_, - $conds_, - __METHOD__, - $sql_options_, - $joins_ + $tables_, + $fields_, + $conds_, + __METHOD__, + $sql_options_, + $joins_ ); $diHandler = $this->datatables->store->getDataItemHandlerForDIType( From a30867f95839336a084d0d7ca744c92dae765711 Mon Sep 17 00:00:00 2001 From: thomas-topway-it Date: Sun, 24 Aug 2025 03:36:24 +0400 Subject: [PATCH 03/17] fix phpcs --- formats/datatables/SearchPanes.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/formats/datatables/SearchPanes.php b/formats/datatables/SearchPanes.php index 2135754c1..6c90654b0 100644 --- a/formats/datatables/SearchPanes.php +++ b/formats/datatables/SearchPanes.php @@ -140,7 +140,7 @@ private function parseQuerySegment( $querySegment ) { if ( !empty( $querySegment['fromTables'] ) ) { foreach ( $querySegment['fromTables'] as $nestedAlias => $nested ) { - if ( is_array($nested ) ) { + if ( is_array( $nested ) ) { foreach ( $nested as $alias => $table ) { $aliasFixed = preg_replace( '/^nested/', '', $alias ); $tables[$aliasFixed] = $table; From 775dde35991e07c534837c128a72a918a86256d2 Mon Sep 17 00:00:00 2001 From: thomas-topway-it Date: Sun, 24 Aug 2025 03:49:17 +0400 Subject: [PATCH 04/17] remove commented line --- formats/datatables/SearchPanes.php | 1 - 1 file changed, 1 deletion(-) diff --git a/formats/datatables/SearchPanes.php b/formats/datatables/SearchPanes.php index 6c90654b0..213573c65 100644 --- a/formats/datatables/SearchPanes.php +++ b/formats/datatables/SearchPanes.php @@ -757,7 +757,6 @@ 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 : ''; // @see QueryEngine $tables_ = $tables; From 787829aad0e5e364c12beb8a467f7f10e1a2a284 Mon Sep 17 00:00:00 2001 From: "translatewiki.net" Date: Thu, 25 Sep 2025 14:16:16 +0200 Subject: [PATCH 05/17] Localisation updates from https://translatewiki.net. --- i18n/de-formal.json | 9 --------- i18n/vi.json | 7 +------ 2 files changed, 1 insertion(+), 15 deletions(-) delete mode 100644 i18n/de-formal.json 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" } From d78ba61df88db68cb3d1e0c1c5ef5c75f6523c52 Mon Sep 17 00:00:00 2001 From: "translatewiki.net" Date: Mon, 29 Sep 2025 14:15:55 +0200 Subject: [PATCH 06/17] Localisation updates from https://translatewiki.net. --- i18n/ar.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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": "التباين" } From 6c7cdc650cff21238dc72c086f5f0a446101aaf3 Mon Sep 17 00:00:00 2001 From: thomas-topway-it Date: Tue, 30 Sep 2025 03:03:39 +0400 Subject: [PATCH 07/17] use QuerySegmentListProcessor --- formats/datatables/SearchPanes.php | 229 +++++++++++++---------------- 1 file changed, 105 insertions(+), 124 deletions(-) diff --git a/formats/datatables/SearchPanes.php b/formats/datatables/SearchPanes.php index 213573c65..027f6f7ac 100644 --- a/formats/datatables/SearchPanes.php +++ b/formats/datatables/SearchPanes.php @@ -24,8 +24,12 @@ use SMWDataItem as DataItem; use SMWQueryProcessor; use SRF\DataTables; +use SMW\SQLStore\QueryEngine\HierarchyTempTableBuilder; +use SMW\SQLStore\TableBuilder\TemporaryTableBuilder; class SearchPanes { + /** @const DataTables */ + private $datatables; private array $searchPanesLog = []; @@ -33,9 +37,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 { @@ -70,92 +123,6 @@ public function getLog(): array { return $this->searchPanesLog; } - private function parseQuerySegment( $querySegment ) { -/* -parse QuerySegment in this form: -( - [type] => 1 - [depth] => - [fingerprint] => - [null] => - [not] => - [joinType] => - [joinTable] => smw_object_ids - [joinfield] => t0.smw_id - [indexField] => - [from] => INNER JOIN (`smw_fpt_mdat` AS t2 INNER JOIN `smw_di_blob` AS t3 ON t2.s_id=t3.s_id) ON t0.smw_id=t2.s_id - [fromTables] => Array - ( - [nestedt2] => Array - ( - [t3] => smw_di_blob - [t2] => smw_fpt_mdat - ) - - ) - - [joinConditions] => Array - ( - [t3] => Array - ( - [0] => INNER JOIN - [1] => t2.s_id=t3.s_id - ) - - [nestedt2] => Array - ( - [0] => INNER JOIN - [1] => t0.smw_id=t2.s_id - ) - - ) - - [where] => ((t3.p_id=519)) - [sortIndexField] => - [components] => Array - ( - ) - - [alias] => t0 - [sortfields] => Array - ( - [] => t0.smw_sort - ) - - [queryNumber] => 0 -) -*/ - $tables = [ - $querySegment['alias'] => $querySegment['joinTable'] - ]; - $joins = []; - $conds = $querySegment['where'] ?: []; - - if ( !empty( $querySegment['joinConditions'] ) ) { - foreach ( $querySegment['joinConditions'] as $alias => $joinInfo ) { - $aliasFixed = preg_replace( '/^nested/', '', $alias ); - $joins[$aliasFixed] = [ $joinInfo[0], [ $joinInfo[1] ] ]; - } - } - - if ( !empty( $querySegment['fromTables'] ) ) { - foreach ( $querySegment['fromTables'] as $nestedAlias => $nested ) { - if ( is_array( $nested ) ) { - foreach ( $nested as $alias => $table ) { - $aliasFixed = preg_replace( '/^nested/', '', $alias ); - $tables[$aliasFixed] = $table; - } - } else { - $tables[$nestedAlias] = $nested; - } - } - } - - ksort( $tables ); - - return [ $tables, $joins, $conds ]; - } - private function getPanesOptions( PrintRequest $printRequest, string $canonicalLabel, @@ -203,7 +170,8 @@ private function getPanesOptions( QuerySegment::$qnum = 0; $querySegmentList = $conditionBuilder->getQuerySegmentList(); - $querySegmentListProcessor = $this->queryEngineFactory->newQuerySegmentListProcessor(); + // $querySegmentListProcessor = $this->queryEngineFactory->newQuerySegmentListProcessor(); + $querySegmentListProcessor = $this->newQuerySegmentListProcessor(); $querySegmentListProcessor->setQuerySegmentList( $querySegmentList ); @@ -212,7 +180,12 @@ private function getPanesOptions( $qobj = $querySegmentList[$rootid]; - [ $tables, $joins, $conds ] = $this->parseQuerySegment( (array)$qobj ); + $tables = $querySegmentListProcessor->fromTables; + $joins = $querySegmentListProcessor->joinConditions; + + $tables[$qobj->alias] = $qobj->joinTable; + + $conds = $qobj->where; $property = new DIProperty( DIProperty::newFromUserLabel( $printRequest->getCanonicalLabel() ) ); $propTypeid = $property->findPropertyValueType(); @@ -226,17 +199,20 @@ private function getPanesOptions( $fields_ = [ 'count' => 'COUNT(*)' ]; $conds_ = $conds; $joins_ = $joins; - $joins_['insts'] = [ 'JOIN', [ "$qobj->alias.smw_id = insts.s_id" ] ]; - - $dataLength = (int)$this->connection->selectField( - $tables_, - $fields_, - $conds_, - __METHOD__, - $sql_options_, - $joins_ + $joins_['insts'] = [ 'JOIN', [ "$qobj->alias.smw_id = insts.s_id" ] ]; + + $res = $this->connection->select( + $tables_, + $fields_, + $conds_, + __METHOD__, + $sql_options_, + $joins_ ); + $row = $res->fetchRow(); + $dataLength = (int)( $row['count'] ?? 0 ); + if ( !$dataLength ) { return []; } @@ -263,10 +239,10 @@ private function getPanesOptions( $tables_ = $tables; $tables_['insts'] = 'smw_fpt_inst'; - $tables_[SQLStore::ID_TABLE] = 'i'; + $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' ] ]; + $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"; @@ -315,7 +291,7 @@ private function getPanesOptions( $conds_ = $conds; $joins_ = $joins; - $dataLength = (int)$this->connection->selectField( + $res = $this->connection->select( $tables_, $fields_, $conds_, @@ -324,6 +300,9 @@ private function getPanesOptions( $joins_ ); + $row = $res->fetchRow(); + $dataLength = (int)( $row['count'] ?? 0 ); + if ( !$dataLength ) { return []; } @@ -354,15 +333,14 @@ private function getPanesOptions( $joins_ = $joins; $conds_ = $conds; - if ( $isIdField ) { + 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 ); + $conds_ .= ' i.smw_iw != ' . $this->connection->addQuotes(SMW_SQL3_SMWIW_OUTDATED); + $conds_ .= ' AND i.smw_iw != ' . $this->connection->addQuotes(SMW_SQL3_SMWDELETEIW); } - // perform the select $res = $this->connection->select( $tables_, $fields_, @@ -423,7 +401,6 @@ private function getPanesOptions( $isSubject = false; $groups = []; foreach ( $res as $row ) { - if ( $isIdField ) { $dbKeys = [ $row->smw_title, @@ -468,9 +445,7 @@ private function getPanesOptions( $dataValue->setOutputFormat( $outputFormat ); } -/* - - + /* // @see DIBlobHandler // $isKeyword = $dataItem->getOption( 'is.keyword' ); @@ -600,7 +575,6 @@ private function getPanesOptions( if ( $uniqueRatio > $threshold ) { return []; } - } arsort( $groups, SORT_NUMERIC ); @@ -721,9 +695,9 @@ private function searchPanesMainlabel( PrintRequest $printRequest, array $search 'threshold' => $threshold, ]; - // if ( $threshold < 1 ) { - // return []; - // } + if ( $threshold < 1 ) { + return []; + } $query = $this->datatables->query; $queryDescription = $query->getDescription(); @@ -735,7 +709,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 ); @@ -744,7 +719,12 @@ private function searchPanesMainlabel( PrintRequest $printRequest, array $search $qobj = $querySegmentList[$rootid]; - [ $tables, $joins, $conds ] = $this->parseQuerySegment( (array)$qobj ); + $tables = $querySegmentListProcessor->fromTables; + $joins = $querySegmentListProcessor->joinConditions; + + $tables[$qobj->alias] = $qobj->joinTable; + + $conds = $qobj->where; $sql_options_ = [ // *** should we set a limit here ? @@ -757,6 +737,7 @@ 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 : ''; // @see QueryEngine $tables_ = $tables; @@ -773,12 +754,12 @@ private function searchPanesMainlabel( PrintRequest $printRequest, array $search $joins_ = $joins; $res = $this->connection->select( - $tables_, - $fields_, - $conds_, - __METHOD__, - $sql_options_, - $joins_ + $tables_, + $fields_, + $conds_, + __METHOD__, + $sql_options_, + $joins_ ); $diHandler = $this->datatables->store->getDataItemHandlerForDIType( From c4e30c6900ce0da0fe6ff0461333c1aaa400c67e Mon Sep 17 00:00:00 2001 From: thomas-topway-it Date: Tue, 30 Sep 2025 03:04:20 +0400 Subject: [PATCH 08/17] Create QuerySegmentListProcessor.php --- .../datatables/QuerySegmentListProcessor.php | 396 ++++++++++++++++++ 1 file changed, 396 insertions(+) create mode 100644 formats/datatables/QuerySegmentListProcessor.php diff --git a/formats/datatables/QuerySegmentListProcessor.php b/formats/datatables/QuerySegmentListProcessor.php new file mode 100644 index 000000000..f66c8ec21 --- /dev/null +++ b/formats/datatables/QuerySegmentListProcessor.php @@ -0,0 +1,396 @@ +joinConditions and $this->fromTables + * to the original QuerySegmentListProcessor for the + * use with SearchPanes + * + * @author thomas-topway-it for KM-A + */ +namespace SRF\DataTables; + +use RuntimeException; +use SMW\MediaWiki\Database; +use SMW\SQLStore\TableBuilder\TemporaryTableBuilder; +use SMWQuery as Query; +use SMW\SQLStore\QueryEngine\QuerySegment; + +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 integer + */ + 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 ) ); + } + } + +} From ceb85abf26fde44518c1fdca2727f606b6962cae Mon Sep 17 00:00:00 2001 From: thomas-topway-it Date: Tue, 30 Sep 2025 03:10:00 +0400 Subject: [PATCH 09/17] remove comment --- formats/datatables/SearchPanes.php | 1 - 1 file changed, 1 deletion(-) diff --git a/formats/datatables/SearchPanes.php b/formats/datatables/SearchPanes.php index 027f6f7ac..9d98737dc 100644 --- a/formats/datatables/SearchPanes.php +++ b/formats/datatables/SearchPanes.php @@ -737,7 +737,6 @@ 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 : ''; // @see QueryEngine $tables_ = $tables; From 6785fca0eefa8a23629c79be10d65b72d0b06246 Mon Sep 17 00:00:00 2001 From: thomas-topway-it Date: Tue, 30 Sep 2025 03:10:42 +0400 Subject: [PATCH 10/17] remove author --- formats/datatables/QuerySegmentListProcessor.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/formats/datatables/QuerySegmentListProcessor.php b/formats/datatables/QuerySegmentListProcessor.php index f66c8ec21..9203be7ad 100644 --- a/formats/datatables/QuerySegmentListProcessor.php +++ b/formats/datatables/QuerySegmentListProcessor.php @@ -3,8 +3,6 @@ * adds $this->joinConditions and $this->fromTables * to the original QuerySegmentListProcessor for the * use with SearchPanes - * - * @author thomas-topway-it for KM-A */ namespace SRF\DataTables; From afbee6b1a571e3cb9157867510d4a06dfb445a25 Mon Sep 17 00:00:00 2001 From: thomas-topway-it Date: Tue, 30 Sep 2025 04:17:36 +0400 Subject: [PATCH 11/17] fix phpcs --- formats/datatables/QuerySegmentListProcessor.php | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/formats/datatables/QuerySegmentListProcessor.php b/formats/datatables/QuerySegmentListProcessor.php index 9203be7ad..acb24061f 100644 --- a/formats/datatables/QuerySegmentListProcessor.php +++ b/formats/datatables/QuerySegmentListProcessor.php @@ -8,9 +8,9 @@ use RuntimeException; use SMW\MediaWiki\Database; +use SMW\SQLStore\QueryEngine\QuerySegment; use SMW\SQLStore\TableBuilder\TemporaryTableBuilder; use SMWQuery as Query; -use SMW\SQLStore\QueryEngine\QuerySegment; class QuerySegmentListProcessor { /* @var array */ @@ -60,7 +60,7 @@ class QuerySegmentListProcessor { * @param TemporaryTableBuilder $temporaryTableBuilder * @param HierarchyTempTableBuilder $hierarchyTempTableBuilder */ - public function __construct( $connection, TemporaryTableBuilder $temporaryTableBuilder, $hierarchyTempTableBuilder ) { + public function __construct( $connection, TemporaryTableBuilder $temporaryTableBuilder, $hierarchyTempTableBuilder ) { $this->connection = $connection; $this->temporaryTableBuilder = $temporaryTableBuilder; $this->hierarchyTempTableBuilder = $hierarchyTempTableBuilder; @@ -87,7 +87,7 @@ public function setQuerySegmentList( &$querySegmentList ) { /** * @since 2.2 * - * @param integer + * @param integer $queryMode */ public function setQueryMode( $queryMode ) { $this->queryMode = $queryMode; @@ -132,8 +132,6 @@ private function segment( QuerySegment &$query ) { case QuerySegment::Q_VALUE: break; // nothing to do } - - } /** @@ -158,20 +156,19 @@ private function table( QuerySegment &$query ) { $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; + $query->from .= " $joinType JOIN $t ON $joinField$op=" . $subQuery->joinfield; if ( $joinType === 'LEFT' ) { $query->where .= ( ( $query->where === '' ) ? '' : ' AND ' ) . '(' . $subQuery->joinfield . ' IS NULL)'; } From 904fc4ed5fc6acf630d5f83c02241d659511a295 Mon Sep 17 00:00:00 2001 From: thomas-topway-it Date: Tue, 30 Sep 2025 04:22:52 +0400 Subject: [PATCH 12/17] fix phpcs --- formats/datatables/SearchPanes.php | 34 +++++++++++++++--------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/formats/datatables/SearchPanes.php b/formats/datatables/SearchPanes.php index 027f6f7ac..13689bc1c 100644 --- a/formats/datatables/SearchPanes.php +++ b/formats/datatables/SearchPanes.php @@ -11,21 +11,21 @@ namespace SRF\DataTables; +use DataItem as SMWDataItem; use SMW\DataTypeRegistry; use SMW\DataValueFactory; use SMW\DIProperty; 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; -use SMW\SQLStore\QueryEngine\HierarchyTempTableBuilder; -use SMW\SQLStore\TableBuilder\TemporaryTableBuilder; class SearchPanes { /** @const DataTables */ @@ -182,7 +182,7 @@ private function getPanesOptions( $tables = $querySegmentListProcessor->fromTables; $joins = $querySegmentListProcessor->joinConditions; - + $tables[$qobj->alias] = $qobj->joinTable; $conds = $qobj->where; @@ -199,15 +199,15 @@ private function getPanesOptions( $fields_ = [ 'count' => 'COUNT(*)' ]; $conds_ = $conds; $joins_ = $joins; - $joins_['insts'] = [ 'JOIN', [ "$qobj->alias.smw_id = insts.s_id" ] ]; - + $joins_['insts'] = [ 'JOIN', [ "$qobj->alias.smw_id = insts.s_id" ] ]; + $res = $this->connection->select( - $tables_, - $fields_, - $conds_, - __METHOD__, - $sql_options_, - $joins_ + $tables_, + $fields_, + $conds_, + __METHOD__, + $sql_options_, + $joins_ ); $row = $res->fetchRow(); @@ -241,8 +241,8 @@ private function getPanesOptions( $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' ] ]; + $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"; @@ -333,12 +333,12 @@ private function getPanesOptions( $joins_ = $joins; $conds_ = $conds; - if ($isIdField) { + 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); + $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( From 29f30444a9783781f32c821f7be1c8996a6ecba0 Mon Sep 17 00:00:00 2001 From: thomas-topway-it Date: Tue, 30 Sep 2025 04:32:41 +0400 Subject: [PATCH 13/17] fix phpcs --- formats/datatables/SearchPanes.php | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/formats/datatables/SearchPanes.php b/formats/datatables/SearchPanes.php index 13689bc1c..4cdfba65b 100644 --- a/formats/datatables/SearchPanes.php +++ b/formats/datatables/SearchPanes.php @@ -11,7 +11,6 @@ namespace SRF\DataTables; -use DataItem as SMWDataItem; use SMW\DataTypeRegistry; use SMW\DataValueFactory; use SMW\DIProperty; @@ -28,7 +27,7 @@ use SRF\DataTables; class SearchPanes { - /** @const DataTables */ + /** @var DataTables */ private $datatables; private array $searchPanesLog = []; @@ -696,7 +695,7 @@ private function searchPanesMainlabel( PrintRequest $printRequest, array $search ]; if ( $threshold < 1 ) { - return []; + return []; } $query = $this->datatables->query; @@ -721,7 +720,7 @@ private function searchPanesMainlabel( PrintRequest $printRequest, array $search $tables = $querySegmentListProcessor->fromTables; $joins = $querySegmentListProcessor->joinConditions; - + $tables[$qobj->alias] = $qobj->joinTable; $conds = $qobj->where; @@ -754,12 +753,12 @@ private function searchPanesMainlabel( PrintRequest $printRequest, array $search $joins_ = $joins; $res = $this->connection->select( - $tables_, - $fields_, - $conds_, - __METHOD__, - $sql_options_, - $joins_ + $tables_, + $fields_, + $conds_, + __METHOD__, + $sql_options_, + $joins_ ); $diHandler = $this->datatables->store->getDataItemHandlerForDIType( From 05384b5297d471239a4ece39c2e739011544cc77 Mon Sep 17 00:00:00 2001 From: thomas-topway-it Date: Tue, 30 Sep 2025 04:33:43 +0400 Subject: [PATCH 14/17] fix phpcs --- formats/datatables/QuerySegmentListProcessor.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/formats/datatables/QuerySegmentListProcessor.php b/formats/datatables/QuerySegmentListProcessor.php index acb24061f..5e592c18a 100644 --- a/formats/datatables/QuerySegmentListProcessor.php +++ b/formats/datatables/QuerySegmentListProcessor.php @@ -87,7 +87,7 @@ public function setQuerySegmentList( &$querySegmentList ) { /** * @since 2.2 * - * @param integer $queryMode + * @param int $queryMode */ public function setQueryMode( $queryMode ) { $this->queryMode = $queryMode; @@ -165,7 +165,7 @@ private function table( QuerySegment &$query ) { $this->fromTables[$subQuery->alias] = $joinTable; - ksort($this->fromTables); + ksort( $this->fromTables ); $this->joinConditions[$subQuery->alias] = [ $joinType . ' JOIN', "$joinField$op=" . $subQuery->joinfield ]; $query->from .= " $joinType JOIN $t ON $joinField$op=" . $subQuery->joinfield; From b566ab80d8c242f3eba3f80c340796fd6c1e262e Mon Sep 17 00:00:00 2001 From: thomas-topway-it Date: Tue, 30 Sep 2025 04:42:08 +0400 Subject: [PATCH 15/17] fix phpcs --- formats/datatables/SearchPanes.php | 1 - 1 file changed, 1 deletion(-) diff --git a/formats/datatables/SearchPanes.php b/formats/datatables/SearchPanes.php index 523e9083f..4cdfba65b 100644 --- a/formats/datatables/SearchPanes.php +++ b/formats/datatables/SearchPanes.php @@ -11,7 +11,6 @@ namespace SRF\DataTables; -use DataItem as SMWDataItem; use SMW\DataTypeRegistry; use SMW\DataValueFactory; use SMW\DIProperty; From fba8f5fc1a7d71d2e2f62cd975cbacde3abdbd30 Mon Sep 17 00:00:00 2001 From: thomas-topway-it Date: Tue, 30 Sep 2025 04:42:37 +0400 Subject: [PATCH 16/17] fix phpcs --- formats/datatables/QuerySegmentListProcessor.php | 1 - 1 file changed, 1 deletion(-) diff --git a/formats/datatables/QuerySegmentListProcessor.php b/formats/datatables/QuerySegmentListProcessor.php index 49d3d98d1..5e592c18a 100644 --- a/formats/datatables/QuerySegmentListProcessor.php +++ b/formats/datatables/QuerySegmentListProcessor.php @@ -389,4 +389,3 @@ public function cleanUp() { } } - From 0af0b3a12c2471b696df7a2afef2d32d29c08bcf Mon Sep 17 00:00:00 2001 From: thomas-topway-it Date: Tue, 30 Sep 2025 13:25:27 +0400 Subject: [PATCH 17/17] fix phpcs --- formats/datatables/QuerySegmentListProcessor.php | 1 - 1 file changed, 1 deletion(-) diff --git a/formats/datatables/QuerySegmentListProcessor.php b/formats/datatables/QuerySegmentListProcessor.php index 5e592c18a..1ca726f57 100644 --- a/formats/datatables/QuerySegmentListProcessor.php +++ b/formats/datatables/QuerySegmentListProcessor.php @@ -387,5 +387,4 @@ public function cleanUp() { $this->temporaryTableBuilder->drop( $this->connection->tableName( $table ) ); } } - }