MediaWiki REL1_39
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 $platforms = [];
113 $this->fatalError( "'$platform' is not a supported platform!" );
114 }
115
116 foreach ( $platforms as $platform ) {
117 $sqlPath = $this->getOption( 'sql', dirname( $jsonPath ) );
118
119 // MediaWiki, and some extensions place mysql .sql files in the directory root, instead of a dedicated
120 // sub directory. If mysql/ doesn't exist, assume that the .sql files should be in the directory root.
121 if ( $platform === 'mysql' && !is_dir( $sqlPath . '/mysql' ) ) {
122 // Allow to specify a folder and build the name from the json filename
123 if ( is_dir( $sqlPath ) ) {
124 $sqlPath = $this->getSqlPathWithFileName( $relativeJsonPath, $sqlPath );
125 }
126 } else {
127 // Allow to specify a folder and build the name from the json filename
128 if ( is_dir( $sqlPath ) ) {
129 $sqlPath .= '/' . $platform;
130 $directory = $sqlPath;
131 $sqlPath = $this->getSqlPathWithFileName( $relativeJsonPath, $sqlPath );
132 } elseif ( count( $platforms ) > 1 ) {
133 $directory = dirname( $sqlPath ) . '/' . $platform;
134 $sqlPath = $directory . '/' . pathinfo( $sqlPath, PATHINFO_FILENAME ) . '.sql';
135 } else {
136 $directory = false;
137 }
138
139 // The directory for the platform might not exist.
140 if ( $directory !== false && !is_dir( $directory )
141 && !mkdir( $directory ) && !is_dir( $directory )
142 ) {
143 $this->error( "Cannot create $directory for $platform" );
144
145 continue;
146 }
147 }
148
149 $this->writeSchema( $platform, $jsonPath, $relativeJsonPath, $sqlPath );
150 }
151 }
152
162 private function getSqlPathWithFileName( string $relativeJsonPath, string $sqlPath ): string {
163 $jsonFilename = pathinfo( $relativeJsonPath, PATHINFO_FILENAME );
164 if ( str_starts_with( $jsonFilename, 'tables' ) ) {
165 $sqlFileName = $jsonFilename . '-generated.sql';
166 } else {
167 $sqlFileName = $jsonFilename . '.sql';
168 }
169
170 return $sqlPath . '/' . $sqlFileName;
171 }
172
173 private function writeSchema(
174 string $platform,
175 string $jsonPath,
176 string $relativeJsonPath,
177 string $sqlPath
178 ): void {
179 $abstractSchemaChange = $this->getSchema( $jsonPath );
180
181 $sql =
182 "-- This file is automatically generated using maintenance/$this->scriptName.\n" .
183 "-- Source: $relativeJsonPath\n" .
184 "-- Do not modify this file directly.\n" .
185 "-- See https://www.mediawiki.org/wiki/Manual:Schema_changes\n";
186
187 $sql .= $this->generateSchema( $platform, $abstractSchemaChange );
188
189 // Give a hint, if nothing changed
190 if ( is_readable( $sqlPath ) ) {
191 $oldSql = file_get_contents( $sqlPath );
192 if ( $oldSql === $sql ) {
193 $this->output( "Schema change is unchanged.\n" );
194 }
195 }
196
197 file_put_contents( $sqlPath, $sql );
198 $this->output( 'Schema change generated and written to ' . $sqlPath . "\n" );
199 }
200
201 abstract protected function generateSchema( string $platform, array $schema ): string;
202
209 private function getSchema( string $jsonPath ): array {
210 $json = file_get_contents( $jsonPath );
211
212 if ( !$json ) {
213 $this->fatalError(
214 "'$jsonPath' does not exist!\n"
215 );
216 }
217
218 $abstractSchema = json_decode( $json, true );
219
220 if ( json_last_error() !== JSON_ERROR_NONE ) {
221 $this->fatalError(
222 "'$jsonPath' seems to be invalid json. Check the syntax and try again!\n" . json_last_error_msg()
223 );
224 }
225
226 $validator = new AbstractSchemaValidator( function ( string $msg ): void {
227 $this->fatalError( $msg );
228 } );
229 try {
230 $validator->validate( $jsonPath );
231 } catch ( AbstractSchemaValidationError $e ) {
232 $this->fatalError( $e->getMessage() );
233 }
234
235 return $abstractSchema;
236 }
237}
if(!defined( 'MEDIAWIKI')) if(ini_get('mbstring.func_overload')) if(!defined( 'MW_ENTRY_POINT')) global $IP
Environment checks.
Definition Setup.php:91
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.