Completed
Pull Request — master (#30)
by Alaa
01:52
created

DatabaseTermIdsResolver::addResultTerms()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 6
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 2
1
<?php
2
3
namespace Wikibase\TermStore\MediaWiki\PackagePrivate;
4
5
use InvalidArgumentException;
6
use stdClass;
7
use Wikimedia\Rdbms\IDatabase;
8
use Wikimedia\Rdbms\ILoadBalancer;
9
use Wikimedia\Rdbms\IResultWrapper;
10
11
/**
12
 * Term ID resolver using the normalized database schema.
13
 *
14
 * @license GPL-2.0-or-later
15
 */
16
class DatabaseTermIdsResolver implements TermIdsResolver {
17
18
	/** @var TypeIdsResolver */
19
	private $typeIdsResolver;
20
21
	/** @var ILoadBalancer */
22
	private $lb;
23
24
	/** @var IDatabase */
25
	private $dbr = null;
26
27
	/** @var IDatabase */
28
	private $dbw = null;
29
30
	/** @var string[] stash of data returned from the {@link TypeIdsResolver} */
31
	private $typeNames = [];
32
33
	public function __construct(
34
		TypeIdsResolver $typeIdsResolver,
35
		ILoadBalancer $lb
36
	) {
37
		$this->typeIdsResolver = $typeIdsResolver;
38
		$this->lb = $lb;
39
	}
40
41
	/*
42
	 * Term data is first read from the replica; if that returns less rows than we asked for,
43
	 * then there are some new rows in the master that were not yet replicated, and we fall back
44
	 * to the master. As the internal relations of the term store never change (for example,
45
	 * a term_in_lang row will never suddenly point to a different text_in_lang), a master fallback
46
	 * should never be necessary in any other case. However, callers need to consider where they
47
	 * got the list of term IDs they pass into this method from: if it’s from a replica, they may
48
	 * still see outdated data overall.
49
	 */
50
	public function resolveTermIds( array $termIds ): array {
51
		$terms = [];
52
53
		$replicaResult = $this->selectTerms( $this->getDbr(), $termIds );
54
		$this->preloadTypes( $replicaResult );
55
		$replicaTermIds = [];
56
57
		foreach ( $replicaResult as $row ) {
58
			$replicaTermIds[] = $row->wbtl_id;
59
			$this->addResultTerms( $terms, $row );
60
		}
61
62
		if ( count( $replicaTermIds ) !== count( $termIds ) ) {
63
			$masterTermIds = array_values( array_diff( $termIds, $replicaTermIds ) );
64
			$masterResult = $this->selectTerms( $this->getDbw(), $masterTermIds );
65
			$this->preloadTypes( $masterResult );
66
			foreach ( $masterResult as $row ) {
67
				$this->addResultTerms( $terms, $row );
68
			}
69
		}
70
71
		return $terms;
72
	}
73
74
	private function selectTerms( IDatabase $db, array $termIds ): IResultWrapper {
75
		return $db->select(
76
			[ 'wbt_term_in_lang', 'wbt_text_in_lang', 'wbt_text' ],
77
			[ 'wbtl_id', 'wbtl_type_id', 'wbxl_language', 'wbx_text' ],
78
			[
79
				'wbtl_id' => $termIds,
80
				// join conditions
81
				'wbtl_text_in_lang_id=wbxl_id',
82
				'wbxl_text_id=wbx_id',
83
			],
84
			__METHOD__
85
		);
86
	}
87
88
	private function preloadTypes( IResultWrapper $result ) {
89
		$typeIds = [];
90
		foreach ( $result as $row ) {
91
			$typeId = $row->wbtl_type_id;
92
			if ( !array_key_exists( $typeId, $this->typeNames ) ) {
93
				$typeIds[$typeId] = true;
94
			}
95
		}
96
		$this->typeNames += $this->typeIdsResolver->resolveTypeIds( array_keys( $typeIds ) );
97
	}
98
99
	private function addResultTerms( array &$terms, stdClass $row ) {
100
		$type = $this->lookupType( $row->wbtl_type_id );
101
		$lang = $row->wbxl_language;
102
		$text = $row->wbx_text;
103
		$terms[$type][$lang][] = $text;
104
	}
105
106
	private function lookupType( $typeId ) {
107
		$typeName = $this->typeNames[$typeId] ?? null;
108
		if ( $typeName === null ) {
109
			throw new InvalidArgumentException(
110
				'Type ID ' . $typeId . ' was requested but not preloaded!' );
111
		}
112
		return $typeName;
113
	}
114
115
	private function getDbr() {
116
		if ( $this->dbr === null ) {
117
			$this->dbr = $this->lb->getConnection( ILoadBalancer::DB_REPLICA );
118
		}
119
120
		return $this->dbr;
121
	}
122
123
	private function getDbw() {
124
		if ( $this->dbw === null ) {
125
			$this->dbw = $this->lb->getConnection( ILoadBalancer::DB_MASTER );
126
		}
127
128
		return $this->dbw;
129
	}
130
131
}
132