MediaWiki REL1_40
SchemaMaintenance.php
Go to the documentation of this file.
1<?php
2
27
28require_once __DIR__ . '/../Maintenance.php';
29
30abstract class SchemaMaintenance extends Maintenance {
31 public const SUPPORTED_PLATFORMS = [
32 'mysql',
33 'sqlite',
34 'postgres'
35 ];
36
41 protected $scriptName;
42
43 public function __construct() {
44 parent::__construct();
45
46 $types = implode( ', ', array_map( static function ( string $type ): string {
47 return "'$type'";
49
50 $this->addOption(
51 'json',
52 'Path to the json file. Default: tables.json',
53 false,
54 true
55 );
56 $this->addOption(
57 'sql',
58 'Path to output. If --type=all is given, ' .
59 'the output will be placed in a directory named after the dbms. ' .
60 'For mysql, a directory will only be used if it already exists. Default: tables-generated.sql',
61 false,
62 true
63 );
64 $this->addOption(
65 'type',
66 "Can be either $types, or 'all'. Default: mysql",
67 false,
68 true
69 );
70 $this->addOption(
71 'validate',
72 'Validate the schema instead of generating sql files.'
73 );
74 }
75
76 public function execute() {
77 global $IP;
78
79 $platform = $this->getOption( 'type', 'mysql' );
80 $jsonPath = $this->getOption( 'json', dirname( __DIR__ ) );
81
82 $installPath = $IP;
83
84 // For windows
85 if ( DIRECTORY_SEPARATOR === '\\' ) {
86 $installPath = strtr( $installPath, '\\', '/' );
87 $jsonPath = strtr( $jsonPath, '\\', '/' );
88 }
89
90 if ( $this->hasOption( 'validate' ) ) {
91 $this->getSchema( $jsonPath );
92
93 return;
94 }
95
96 // Allow to specify a folder and use a default name
97 if ( is_dir( $jsonPath ) ) {
98 $jsonPath .= '/tables.json';
99 }
100
101 $relativeJsonPath = str_replace(
102 [ "$installPath/extensions/", "$installPath/" ],
103 '',
104 $jsonPath
105 );
106
107 if ( in_array( $platform, self::SUPPORTED_PLATFORMS, true ) ) {
108 $platforms = [ $platform ];
109 } elseif ( $platform === 'all' ) {
110 $platforms = self::SUPPORTED_PLATFORMS;
111 } else {
112 $this->fatalError( "'$platform' is not a supported platform!" );
113 }
114
115 foreach ( $platforms as $platform ) {
116 $sqlPath = $this->getOption( 'sql', dirname( $jsonPath ) );
117
118 // MediaWiki, and some extensions place mysql .sql files in the directory root, instead of a dedicated
119 // sub directory. If mysql/ doesn't exist, assume that the .sql files should be in the directory root.
120 if ( $platform === 'mysql' && !is_dir( $sqlPath . '/mysql' ) ) {
121 // Allow to specify a folder and build the name from the json filename
122 if ( is_dir( $sqlPath ) ) {
123 $sqlPath = $this->getSqlPathWithFileName( $relativeJsonPath, $sqlPath );
124 }
125 } else {
126 // Allow to specify a folder and build the name from the json filename
127 if ( is_dir( $sqlPath ) ) {
128 $sqlPath .= '/' . $platform;
129 $directory = $sqlPath;
130 $sqlPath = $this->getSqlPathWithFileName( $relativeJsonPath, $sqlPath );
131 } elseif ( count( $platforms ) > 1 ) {
132 $directory = dirname( $sqlPath ) . '/' . $platform;
133 $sqlPath = $directory . '/' . pathinfo( $sqlPath, PATHINFO_FILENAME ) . '.sql';
134 } else {
135 $directory = false;
136 }
137
138 // The directory for the platform might not exist.
139 if ( $directory !== false && !is_dir( $directory )
140 && !mkdir( $directory ) && !is_dir( $directory )
141 ) {
142 $this->error( "Cannot create $directory for $platform" );
143
144 continue;
145 }
146 }
147
148 $this->writeSchema( $platform, $jsonPath, $relativeJsonPath, $sqlPath );
149 }
150 }
151
161 private function getSqlPathWithFileName( string $relativeJsonPath, string $sqlPath ): string {
162 $jsonFilename = pathinfo( $relativeJsonPath, PATHINFO_FILENAME );
163 if ( str_starts_with( $jsonFilename, 'tables' ) ) {
164 $sqlFileName = $jsonFilename . '-generated.sql';
165 } else {
166 $sqlFileName = $jsonFilename . '.sql';
167 }
168
169 return $sqlPath . '/' . $sqlFileName;
170 }
171
172 private function writeSchema(
173 string $platform,
174 string $jsonPath,
175 string $relativeJsonPath,
176 string $sqlPath
177 ): void {
178 $abstractSchemaChange = $this->getSchema( $jsonPath );
179
180 $sql =
181 "-- This file is automatically generated using maintenance/$this->scriptName.\n" .
182 "-- Source: $relativeJsonPath\n" .
183 "-- Do not modify this file directly.\n" .
184 "-- See https://www.mediawiki.org/wiki/Manual:Schema_changes\n";
185
186 $sql .= $this->generateSchema( $platform, $abstractSchemaChange );
187
188 // Give a hint, if nothing changed
189 if ( is_readable( $sqlPath ) ) {
190 $oldSql = file_get_contents( $sqlPath );
191 if ( $oldSql === $sql ) {
192 $this->output( "Schema change is unchanged.\n" );
193 }
194 }
195
196 file_put_contents( $sqlPath, $sql );
197 $this->output( 'Schema change generated and written to ' . $sqlPath . "\n" );
198 }
199
200 abstract protected function generateSchema( string $platform, array $schema ): string;
201
208 private function getSchema( string $jsonPath ): array {
209 $json = file_get_contents( $jsonPath );
210
211 if ( !$json ) {
212 $this->fatalError(
213 "'$jsonPath' does not exist!\n"
214 );
215 }
216
217 $abstractSchema = json_decode( $json, true );
218
219 if ( json_last_error() !== JSON_ERROR_NONE ) {
220 $this->fatalError(
221 "'$jsonPath' seems to be invalid json. Check the syntax and try again!\n" . json_last_error_msg()
222 );
223 }
224
225 $validator = new AbstractSchemaValidator( function ( string $msg ): void {
226 $this->fatalError( $msg );
227 } );
228 try {
229 $validator->validate( $jsonPath );
230 } catch ( AbstractSchemaValidationError $e ) {
231 $this->fatalError( $e->getMessage() );
232 }
233
234 return $abstractSchema;
235 }
236}
if(!defined( 'MEDIAWIKI')) if(ini_get('mbstring.func_overload')) if(!defined( 'MW_ENTRY_POINT')) global $IP
Environment checks.
Definition Setup.php:93
Abstract maintenance class for quickly writing and churning out maintenance scripts with minimal effo...
error( $err, $die=0)
Throw an error to the user.
hasOption( $name)
Checks to see if a particular option was set.
addOption( $name, $description, $required=false, $withArg=false, $shortName=false, $multiOccurrence=false)
Add a parameter to the script.
getOption( $name, $default=null)
Get an option, or return the default.
fatalError( $msg, $exitCode=1)
Output a message and terminate the current script.
Validate abstract schema json files against their JSON schema.
execute()
Do the actual work.
string $scriptName
Name of the script.
generateSchema(string $platform, array $schema)
__construct()
Default constructor.