summaryrefslogtreecommitdiff
path: root/www/wiki/includes/libs/rdbms/database/IMaintainableDatabase.php
blob: 18e3cbbc4600bf316bacc4ee5d1684aa4ca5d59e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
<?php

/**
 * This file deals with database interface functions
 * and query specifics/optimisations.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * http://www.gnu.org/copyleft/gpl.html
 *
 * @file
 */
namespace Wikimedia\Rdbms;

use Exception;
use RuntimeException;

/**
 * Advanced database interface for IDatabase handles that include maintenance methods
 *
 * This is useful for type-hints used by installer, upgrader, and background scripts
 * that will make use of lower-level and longer-running queries, including schema changes.
 *
 * @ingroup Database
 * @since 1.28
 */
interface IMaintainableDatabase extends IDatabase {
	/**
	 * Format a table name ready for use in constructing an SQL query
	 *
	 * This does two important things: it quotes the table names to clean them up,
	 * and it adds a table prefix if only given a table name with no quotes.
	 *
	 * All functions of this object which require a table name call this function
	 * themselves. Pass the canonical name to such functions. This is only needed
	 * when calling query() directly.
	 *
	 * @note This function does not sanitize user input. It is not safe to use
	 *   this function to escape user input.
	 * @param string $name Database table name
	 * @param string $format One of:
	 *   quoted - Automatically pass the table name through addIdentifierQuotes()
	 *            so that it can be used in a query.
	 *   raw - Do not add identifier quotes to the table name
	 * @return string Full database name
	 */
	public function tableName( $name, $format = 'quoted' );

	/**
	 * Fetch a number of table names into an array
	 * This is handy when you need to construct SQL for joins
	 *
	 * Example:
	 * list( $user, $watchlist ) = $dbr->tableNames( 'user', 'watchlist' ) );
	 * $sql = "SELECT wl_namespace, wl_title FROM $watchlist, $user
	 *         WHERE wl_user=user_id AND wl_user=$nameWithQuotes";
	 *
	 * @return array
	 */
	public function tableNames();

	/**
	 * Fetch a number of table names into an zero-indexed numerical array
	 * This is handy when you need to construct SQL for joins
	 *
	 * Example:
	 * list( $user, $watchlist ) = $dbr->tableNamesN( 'user', 'watchlist' );
	 * $sql = "SELECT wl_namespace,wl_title FROM $watchlist,$user
	 *         WHERE wl_user=user_id AND wl_user=$nameWithQuotes";
	 *
	 * @return array
	 */
	public function tableNamesN();

	/**
	 * Returns the size of a text field, or -1 for "unlimited"
	 *
	 * @param string $table
	 * @param string $field
	 * @return int
	 */
	public function textFieldSize( $table, $field );

	/**
	 * Read and execute SQL commands from a file.
	 *
	 * Returns true on success, error string or exception on failure (depending
	 * on object's error ignore settings).
	 *
	 * @param string $filename File name to open
	 * @param callable|null $lineCallback Optional function called before reading each line
	 * @param callable|null $resultCallback Optional function called for each MySQL result
	 * @param bool|string $fname Calling function name or false if name should be
	 *   generated dynamically using $filename
	 * @param callable|null $inputCallback Optional function called for each
	 *   complete line sent
	 * @return bool|string
	 * @throws Exception
	 */
	public function sourceFile(
		$filename,
		callable $lineCallback = null,
		callable $resultCallback = null,
		$fname = false,
		callable $inputCallback = null
	);

	/**
	 * Read and execute commands from an open file handle.
	 *
	 * Returns true on success, error string or exception on failure (depending
	 * on object's error ignore settings).
	 *
	 * @param resource $fp File handle
	 * @param callable|null $lineCallback Optional function called before reading each query
	 * @param callable|null $resultCallback Optional function called for each MySQL result
	 * @param string $fname Calling function name
	 * @param callable|null $inputCallback Optional function called for each complete query sent
	 * @return bool|string
	 */
	public function sourceStream(
		$fp,
		callable $lineCallback = null,
		callable $resultCallback = null,
		$fname = __METHOD__,
		callable $inputCallback = null
	);

	/**
	 * Called by sourceStream() to check if we've reached a statement end
	 *
	 * @param string &$sql SQL assembled so far
	 * @param string &$newLine New line about to be added to $sql
	 * @return bool Whether $newLine contains end of the statement
	 */
	public function streamStatementEnd( &$sql, &$newLine );

	/**
	 * Delete a table
	 * @param string $tableName
	 * @param string $fName
	 * @return bool|ResultWrapper
	 */
	public function dropTable( $tableName, $fName = __METHOD__ );

	/**
	 * Perform a deadlock-prone transaction.
	 *
	 * This function invokes a callback function to perform a set of write
	 * queries. If a deadlock occurs during the processing, the transaction
	 * will be rolled back and the callback function will be called again.
	 *
	 * Avoid using this method outside of Job or Maintenance classes.
	 *
	 * Usage:
	 *   $dbw->deadlockLoop( callback, ... );
	 *
	 * Extra arguments are passed through to the specified callback function.
	 * This method requires that no transactions are already active to avoid
	 * causing premature commits or exceptions.
	 *
	 * Returns whatever the callback function returned on its successful,
	 * iteration, or false on error, for example if the retry limit was
	 * reached.
	 *
	 * @return mixed
	 * @throws DBUnexpectedError
	 * @throws Exception
	 */
	public function deadlockLoop();

	/**
	 * Lists all the VIEWs in the database
	 *
	 * @param string $prefix Only show VIEWs with this prefix, eg. unit_test_
	 * @param string $fname Name of calling function
	 * @throws RuntimeException
	 * @return array
	 */
	public function listViews( $prefix = null, $fname = __METHOD__ );

	/**
	 * Creates a new table with structure copied from existing table
	 *
	 * Note that unlike most database abstraction functions, this function does not
	 * automatically append database prefix, because it works at a lower abstraction level.
	 * The table names passed to this function shall not be quoted (this function calls
	 * addIdentifierQuotes() when needed).
	 *
	 * @param string $oldName Name of table whose structure should be copied
	 * @param string $newName Name of table to be created
	 * @param bool $temporary Whether the new table should be temporary
	 * @param string $fname Calling function name
	 * @return bool True if operation was successful
	 * @throws RuntimeException
	 */
	public function duplicateTableStructure(
		$oldName, $newName, $temporary = false, $fname = __METHOD__
	);

	/**
	 * Checks if table locks acquired by lockTables() are transaction-bound in their scope
	 *
	 * Transaction-bound table locks will be released when the current transaction terminates.
	 * Table locks that are not bound to a transaction are not effected by BEGIN/COMMIT/ROLLBACK
	 * and will last until either lockTables()/unlockTables() is called or the TCP connection to
	 * the database is closed.
	 *
	 * @return bool
	 * @since 1.29
	 */
	public function tableLocksHaveTransactionScope();

	/**
	 * Lock specific tables
	 *
	 * Any pending transaction should be resolved before calling this method, since:
	 *   a) Doing so resets any REPEATABLE-READ snapshot of the data to a fresh one.
	 *   b) Previous row and table locks from the transaction or session may be released
	 *      by LOCK TABLES, which may be unsafe for the changes in such a transaction.
	 *   c) The main use case of lockTables() is to avoid deadlocks and timeouts by locking
	 *      entire tables in order to do long-running, batched, and lag-aware, updates. Batching
	 *      and replication lag checks do not work when all the updates happen in a transaction.
	 *
	 * Always get all relevant table locks up-front in one call, since LOCK TABLES might release
	 * any prior table locks on some RDBMes (e.g MySQL).
	 *
	 * For compatibility, callers should check tableLocksHaveTransactionScope() before using
	 * this method. If locks are scoped specifically to transactions then caller must either:
	 *   - a) Start a new transaction and acquire table locks for the scope of that transaction,
	 *        doing all row updates within that transaction. It will not be possible to update
	 *        rows in batches; this might result in high replication lag.
	 *   - b) Forgo table locks entirely and avoid calling this method. Careful use of hints like
	 *        LOCK IN SHARE MODE and FOR UPDATE and the use of query batching may be preferrable
	 *        to using table locks with a potentially large transaction. Use of MySQL and Postges
	 *        style REPEATABLE-READ (Snapshot Isolation with or without First-Committer-Rule) can
	 *        also be considered for certain tasks that require a consistent view of entire tables.
	 *
	 * If session scoped locks are not supported, then calling lockTables() will trigger
	 * startAtomic(), with unlockTables() triggering endAtomic(). This will automatically
	 * start a transaction if one is not already present and cause the locks to be released
	 * when the transaction finishes (normally during the unlockTables() call).
	 *
	 * In any case, avoid using begin()/commit() in code that runs while such table locks are
	 * acquired, as that breaks in case when a transaction is needed. The startAtomic() and
	 * endAtomic() methods are safe, however, since they will join any existing transaction.
	 *
	 * @param array $read Array of tables to lock for read access
	 * @param array $write Array of tables to lock for write access
	 * @param string $method Name of caller
	 * @return bool
	 * @since 1.29
	 */
	public function lockTables( array $read, array $write, $method );

	/**
	 * Unlock all tables locked via lockTables()
	 *
	 * If table locks are scoped to transactions, then locks might not be released until the
	 * transaction ends, which could happen after this method is called.
	 *
	 * @param string $method The caller
	 * @return bool
	 * @since 1.29
	 */
	public function unlockTables( $method );

	/**
	 * List all tables on the database
	 *
	 * @param string $prefix Only show tables with this prefix, e.g. mw_
	 * @param string $fname Calling function name
	 * @throws DBError
	 * @return array
	 */
	public function listTables( $prefix = null, $fname = __METHOD__ );

	/**
	 * Determines if a given index is unique
	 *
	 * @param string $table
	 * @param string $index
	 *
	 * @return bool
	 */
	public function indexUnique( $table, $index );

	/**
	 * mysql_fetch_field() wrapper
	 * Returns false if the field doesn't exist
	 *
	 * @param string $table Table name
	 * @param string $field Field name
	 *
	 * @return Field
	 */
	public function fieldInfo( $table, $field );
}

class_alias( IMaintainableDatabase::class, 'IMaintainableDatabase' );