Completed
Pull Request — master (#30)
by
unknown
03:06
created

DatabaseTermIdsResolver::connectDbr()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 5
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 0
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
		$this->connectDbr();
53
54
		$replicaResult = $this->selectTerms( $this->dbr, $termIds );
55
		$this->preloadTypes( $replicaResult );
56
		$replicaTermIds = [];
57
58
		foreach ( $replicaResult as $row ) {
59
			$replicaTermIds[] = $row->wbtl_id;
60
			$this->addResultTerms( $terms, $row );
61
		}
62
63
		if ( count( $replicaTermIds ) !== count( $termIds ) ) {
64
			$masterTermIds = array_values( array_diff( $termIds, $replicaTermIds ) );
65
			$this->connectDbw();
66
			$masterResult = $this->selectTerms( $this->dbw, $masterTermIds );
67
			$this->preloadTypes( $masterResult );
68
			foreach ( $masterResult as $row ) {
69
				$this->addResultTerms( $terms, $row );
70
			}
71
		}
72
73
		return $terms;
74
	}
75
76
	private function selectTerms( IDatabase $db, array $termIds ): IResultWrapper {
77
		return $db->select(
78
			[ 'wbt_term_in_lang', 'wbt_text_in_lang', 'wbt_text' ],
79
			[ 'wbtl_id', 'wbtl_type_id', 'wbxl_language', 'wbx_text' ],
80
			[
81
				'wbtl_id' => $termIds,
82
				// join conditions
83
				'wbtl_text_in_lang_id=wbxl_id',
84
				'wbxl_text_id=wbx_id',
85
			],
86
			__METHOD__
87
		);
88
	}
89
90
	private function preloadTypes( IResultWrapper $result ) {
91
		$typeIds = [];
92
		foreach ( $result as $row ) {
93
			$typeId = $row->wbtl_type_id;
94
			if ( !array_key_exists( $typeId, $this->typeNames ) ) {
95
				$typeIds[$typeId] = true;
96
			}
97
		}
98
		$this->typeNames += $this->typeIdsResolver->resolveTypeIds( array_keys( $typeIds ) );
99
	}
100
101
	private function addResultTerms( array &$terms, stdClass $row ) {
102
		$type = $this->lookupType( $row->wbtl_type_id );
103
		$lang = $row->wbxl_language;
104
		$text = $row->wbx_text;
105
		$terms[$type][$lang][] = $text;
106
	}
107
108
	private function lookupType( $typeId ) {
109
		$typeName = $this->typeNames[$typeId] ?? null;
110
		if ( $typeName === null ) {
111
			throw new InvalidArgumentException(
112
				'Type ID ' . $typeId . ' was requested but not preloaded!' );
113
		}
114
		return $typeName;
115
	}
116
117
	private function connectDbr() {
118
		if ( $this->dbr === null ) {
119
			$this->dbr = $this->lb->getConnection( ILoadBalancer::DB_REPLICA );
120
		}
121
	}
122
123
	private function connectDbw() {
124
		if ( $this->dbw === null ) {
125
			$this->dbw = $this->lb->getConnection( ILoadBalancer::DB_MASTER );
126
		}
127
	}
128
129
}
130