summaryrefslogtreecommitdiff
path: root/includes/SqlDataUpdate.php
blob: 51188d85c9929fcbe83ba10ccdb840b42bd61803 (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
<?php
/**
 * Base code for update jobs that put some secondary data extracted
 * from article content into the database.
 *
 * 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
 */

/**
 * Abstract base class for update jobs that put some secondary data extracted
 * from article content into the database.
 *
 * @note: subclasses should NOT start or commit transactions in their doUpdate() method,
 *        a transaction will automatically be wrapped around the update. Starting another
 *        one would break the outer transaction bracket. If need be, subclasses can override
 *        the beginTransaction() and commitTransaction() methods.
 */
abstract class SqlDataUpdate extends DataUpdate {

	protected $mDb;            //!< Database connection reference
	protected $mOptions;       //!< SELECT options to be used (array)

	private   $mHasTransaction; //!< bool whether a transaction is open on this object (internal use only!)
	protected $mUseTransaction; //!< bool whether this update should be wrapped in a transaction

	/**
	 * Constructor
	 *
	 * @param bool $withTransaction whether this update should be wrapped in a transaction (default: true).
	 *             A transaction is only started if no transaction is already in progress,
	 *             see beginTransaction() for details.
	 **/
	public function __construct( $withTransaction = true ) {
		global $wgAntiLockFlags;

		parent::__construct();

		if ( $wgAntiLockFlags & ALF_NO_LINK_LOCK ) {
			$this->mOptions = array();
		} else {
			$this->mOptions = array( 'FOR UPDATE' );
		}

		// @todo get connection only when it's needed? make sure that doesn't break anything, especially transactions!
		$this->mDb = wfGetDB( DB_MASTER );

		$this->mWithTransaction = $withTransaction;
		$this->mHasTransaction = false;
	}

	/**
	 * Begin a database transaction, if $withTransaction was given as true in the constructor for this SqlDataUpdate.
	 *
	 * Because nested transactions are not supported by the Database class, this implementation
	 * checks Database::trxLevel() and only opens a transaction if none is already active.
	 */
	public function beginTransaction() {
		if ( !$this->mWithTransaction ) {
			return;
		}

		// NOTE: nested transactions are not supported, only start a transaction if none is open
		if ( $this->mDb->trxLevel() === 0 ) {
			$this->mDb->begin( get_class( $this ) . '::beginTransaction' );
			$this->mHasTransaction = true;
		}
	}

	/**
	 * Commit the database transaction started via beginTransaction (if any).
	 */
	public function commitTransaction() {
		if ( $this->mHasTransaction ) {
			$this->mDb->commit( get_class( $this ) . '::commitTransaction' );
			$this->mHasTransaction = false;
		}
	}

	/**
	 * Abort the database transaction started via beginTransaction (if any).
	 */
	public function abortTransaction() {
		if ( $this->mHasTransaction ) { //XXX: actually... maybe always?
			$this->mDb->rollback( get_class( $this ) . '::abortTransaction' );
			$this->mHasTransaction = false;
		}
	}

	/**
	 * Invalidate the cache of a list of pages from a single namespace.
	 * This is intended for use by subclasses.
	 *
	 * @param $namespace Integer
	 * @param $dbkeys Array
	 */
	protected function invalidatePages( $namespace, array $dbkeys ) {
		if ( $dbkeys === array() ) {
			return;
		}

		/**
		 * Determine which pages need to be updated
		 * This is necessary to prevent the job queue from smashing the DB with
		 * large numbers of concurrent invalidations of the same page
		 */
		$now = $this->mDb->timestamp();
		$ids = array();
		$res = $this->mDb->select( 'page', array( 'page_id' ),
			array(
				'page_namespace' => $namespace,
				'page_title' => $dbkeys,
				'page_touched < ' . $this->mDb->addQuotes( $now )
			), __METHOD__
		);

		foreach ( $res as $row ) {
			$ids[] = $row->page_id;
		}

		if ( $ids === array() ) {
			return;
		}

		/**
		 * Do the update
		 * We still need the page_touched condition, in case the row has changed since
		 * the non-locking select above.
		 */
		$this->mDb->update( 'page', array( 'page_touched' => $now ),
			array(
				'page_id' => $ids,
				'page_touched < ' . $this->mDb->addQuotes( $now )
			), __METHOD__
		);
	}

}