Passed
Push — master ( 109b6e...716545 )
by Alaa
58s queued 11s
created

DatabaseTermIdsResolver::lookupType()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

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