Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
11.54% |
6 / 52 |
|
16.67% |
1 / 6 |
CRAP | |
0.00% |
0 / 1 |
SqliteCreateDatabaseTask | |
11.54% |
6 / 52 |
|
16.67% |
1 / 6 |
193.22 | |
0.00% |
0 / 1 |
getName | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getAliases | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
execute | |
0.00% |
0 / 32 |
|
0.00% |
0 / 1 |
42 | |||
makeStubDBFile | |
0.00% |
0 / 11 |
|
0.00% |
0 / 1 |
20 | |||
getSqliteUtils | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
createDataDir | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
3 |
1 | <?php |
2 | |
3 | namespace MediaWiki\Installer\Task; |
4 | |
5 | use MediaWiki\MainConfigNames; |
6 | use MediaWiki\Status\Status; |
7 | use Wikimedia\Rdbms\DatabaseFactory; |
8 | use Wikimedia\Rdbms\DatabaseSqlite; |
9 | use Wikimedia\Rdbms\DBConnectionError; |
10 | |
11 | /** |
12 | * Create the SQLite database files |
13 | * |
14 | * @internal For use by the installer |
15 | */ |
16 | class SqliteCreateDatabaseTask extends Task { |
17 | public function getName() { |
18 | return 'database'; |
19 | } |
20 | |
21 | public function getAliases() { |
22 | return 'schema'; |
23 | } |
24 | |
25 | public function execute(): Status { |
26 | $dir = $this->getConfigVar( MainConfigNames::SQLiteDataDir ); |
27 | |
28 | # Double check (Only available in web installation). We checked this before but maybe someone |
29 | # deleted the data dir between then and now |
30 | $dir_status = $this->getSqliteUtils()->checkDataDir( $dir ); |
31 | if ( $dir_status->isGood() ) { |
32 | $res = $this->createDataDir( $dir ); |
33 | if ( !$res->isGood() ) { |
34 | return $res; |
35 | } |
36 | } else { |
37 | return $dir_status; |
38 | } |
39 | |
40 | $db = $this->getConfigVar( MainConfigNames::DBname ); |
41 | |
42 | # Make the main and cache stub DB files |
43 | $status = Status::newGood(); |
44 | $status->merge( $this->makeStubDBFile( $dir, $db ) ); |
45 | $status->merge( $this->makeStubDBFile( $dir, "wikicache" ) ); |
46 | $status->merge( $this->makeStubDBFile( $dir, "{$db}_l10n_cache" ) ); |
47 | $status->merge( $this->makeStubDBFile( $dir, "{$db}_jobqueue" ) ); |
48 | if ( !$status->isOK() ) { |
49 | return $status; |
50 | } |
51 | |
52 | # Create the l10n cache DB |
53 | try { |
54 | $conn = ( new DatabaseFactory() )->create( |
55 | 'sqlite', [ 'dbname' => "{$db}_l10n_cache", 'dbDirectory' => $dir ] ); |
56 | # @todo: don't duplicate l10n_cache definition, though it's very simple |
57 | $sql = |
58 | <<<EOT |
59 | CREATE TABLE l10n_cache ( |
60 | lc_lang BLOB NOT NULL, |
61 | lc_key TEXT NOT NULL, |
62 | lc_value BLOB NOT NULL, |
63 | PRIMARY KEY (lc_lang, lc_key) |
64 | ); |
65 | EOT; |
66 | $conn->query( $sql, __METHOD__ ); |
67 | $conn->query( "PRAGMA journal_mode=WAL", __METHOD__ ); // this is permanent |
68 | $conn->close( __METHOD__ ); |
69 | } catch ( DBConnectionError $e ) { |
70 | return Status::newFatal( 'config-sqlite-connection-error', $e->getMessage() ); |
71 | } |
72 | |
73 | # Create the job queue DB |
74 | try { |
75 | $conn = ( new DatabaseFactory() )->create( |
76 | 'sqlite', [ 'dbname' => "{$db}_jobqueue", 'dbDirectory' => $dir ] ); |
77 | # @todo: don't duplicate job definition, though it's very static |
78 | $sql = |
79 | <<<EOT |
80 | CREATE TABLE job ( |
81 | job_id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, |
82 | job_cmd BLOB NOT NULL default '', |
83 | job_namespace INTEGER NOT NULL, |
84 | job_title TEXT NOT NULL, |
85 | job_timestamp BLOB NULL default NULL, |
86 | job_params BLOB NOT NULL, |
87 | job_random integer NOT NULL default 0, |
88 | job_attempts integer NOT NULL default 0, |
89 | job_token BLOB NOT NULL default '', |
90 | job_token_timestamp BLOB NULL default NULL, |
91 | job_sha1 BLOB NOT NULL default '' |
92 | ); |
93 | CREATE INDEX job_sha1 ON job (job_sha1); |
94 | CREATE INDEX job_cmd_token ON job (job_cmd,job_token,job_random); |
95 | CREATE INDEX job_cmd_token_id ON job (job_cmd,job_token,job_id); |
96 | CREATE INDEX job_cmd ON job (job_cmd, job_namespace, job_title, job_params); |
97 | CREATE INDEX job_timestamp ON job (job_timestamp); |
98 | EOT; |
99 | $conn->query( $sql, __METHOD__ ); |
100 | $conn->query( "PRAGMA journal_mode=WAL", __METHOD__ ); // this is permanent |
101 | $conn->close( __METHOD__ ); |
102 | } catch ( DBConnectionError $e ) { |
103 | return Status::newFatal( 'config-sqlite-connection-error', $e->getMessage() ); |
104 | } |
105 | |
106 | # Open the main DB |
107 | $mainConnStatus = $this->getConnection( ITaskContext::CONN_CREATE_TABLES ); |
108 | // Use WAL mode. This has better performance |
109 | // when the DB is being read and written concurrently. |
110 | // This causes the DB to be created in this mode |
111 | // so we only have to do this on creation. |
112 | $mainConnStatus->getDB()->query( "PRAGMA journal_mode=WAL", __METHOD__ ); |
113 | return $mainConnStatus; |
114 | } |
115 | |
116 | /** |
117 | * @param string $dir |
118 | * @param string $db |
119 | * @return Status |
120 | */ |
121 | protected function makeStubDBFile( $dir, $db ) { |
122 | $file = DatabaseSqlite::generateFileName( $dir, $db ); |
123 | |
124 | if ( file_exists( $file ) ) { |
125 | if ( !is_writable( $file ) ) { |
126 | return Status::newFatal( 'config-sqlite-readonly', $file ); |
127 | } |
128 | return Status::newGood(); |
129 | } |
130 | |
131 | $oldMask = umask( 0177 ); |
132 | if ( file_put_contents( $file, '' ) === false ) { |
133 | umask( $oldMask ); |
134 | return Status::newFatal( 'config-sqlite-cant-create-db', $file ); |
135 | } |
136 | umask( $oldMask ); |
137 | |
138 | return Status::newGood(); |
139 | } |
140 | |
141 | private function getSqliteUtils() { |
142 | return new SqliteUtils; |
143 | } |
144 | |
145 | /** |
146 | * @param string $dir Path to the data directory |
147 | * @return Status Return good Status if without error |
148 | */ |
149 | private function createDataDir( $dir ): Status { |
150 | if ( !is_dir( $dir ) ) { |
151 | // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged |
152 | $ok = @mkdir( $dir, 0700, true ); |
153 | if ( !$ok ) { |
154 | return Status::newFatal( 'config-sqlite-mkdir-error', $dir ); |
155 | } |
156 | } |
157 | # Put a .htaccess file in case the user didn't take our advice |
158 | file_put_contents( "$dir/.htaccess", "Require all denied\n" ); |
159 | return Status::newGood(); |
160 | } |
161 | |
162 | } |