MediaWiki master
SchemaGenerator.php
Go to the documentation of this file.
1<?php
2
4
5use Doctrine\SqlFormatter\NullHighlighter;
6use Doctrine\SqlFormatter\SqlFormatter;
7use JsonException;
11
23 public function validateAndGetSchema( string $jsonPath ): array {
24 $json = file_get_contents( $jsonPath );
25
26 if ( !$json ) {
27 throw new AbstractSchemaValidationError( "'$jsonPath' does not exist!" );
28 }
29
30 try {
31 $abstractSchema = json_decode( $json, true, 512, JSON_THROW_ON_ERROR );
32 } catch ( JsonException $e ) {
33 throw new AbstractSchemaValidationError( "Invalid JSON schema: " . $e->getMessage(), 0, $e );
34 }
35
36 $validator = new AbstractSchemaValidator();
37 $validator->validate( $jsonPath );
38
39 return $abstractSchema;
40 }
41
45 public function generateSchema( string $platform, string $jsonPath ): string {
46 $abstractSchemaChange = $this->validateAndGetSchema( $jsonPath );
47
48 $sql = $this->makeSQLComment( 'generateSchemaSql.php', $jsonPath );
49
50 $schemaBuilder = ( new DoctrineSchemaBuilderFactory() )->getSchemaBuilder( $platform );
51
52 foreach ( $abstractSchemaChange as $table ) {
53 $schemaBuilder->addTable( $table );
54 }
55 $tableSqls = $schemaBuilder->getSql();
56
57 $sql .= $this->cleanupSqlArray( $platform, $tableSqls );
58
59 return $sql;
60 }
61
65 public function generateSchemaChange( string $platform, string $jsonPath ): string {
66 $abstractSchemaChange = $this->validateAndGetSchema( $jsonPath );
67
68 $sql = $this->makeSQLComment( 'generateSchemaChangeSql.php', $jsonPath );
69
70 $schemaChangeBuilder = ( new DoctrineSchemaBuilderFactory() )->getSchemaChangeBuilder( $platform );
71
72 $schemaChangeSqls = $schemaChangeBuilder->getSchemaChangeSql( $abstractSchemaChange );
73 if ( !$schemaChangeSqls ) {
74 throw new AbstractSchemaValidationError( "No schema changes detected!" );
75 }
76
77 $sql .= $this->cleanupSqlArray( $platform, $schemaChangeSqls );
78
79 return $sql;
80 }
81
82 private function makeSQLComment( string $scriptName, string $jsonPath ): string {
84
85 $canonicalJsonPath = $this->normalizePath( $jsonPath, $IP, $wgExtensionDirectory );
86
87 return "-- This file is automatically generated using maintenance/$scriptName.\n" .
88 "-- Source: $canonicalJsonPath\n" .
89 "-- Do not modify this file directly.\n" .
90 "-- See https://www.mediawiki.org/wiki/Manual:Schema_changes\n";
91 }
92
101 public static function normalizePath( string $jsonPath, string $IP, string $extensionDirectory ): string {
102 $installPath = realpath( $IP );
103 $jsonPath = realpath( $jsonPath );
104 $extensionDirectory = realpath( $extensionDirectory );
105
106 // For windows
107 if ( DIRECTORY_SEPARATOR === '\\' ) {
108 $installPath = strtr( $installPath, '\\', '/' );
109 $jsonPath = strtr( $jsonPath, '\\', '/' );
110 $extensionDirectory = strtr( $extensionDirectory, '\\', '/' );
111 }
112
113 if ( str_starts_with( $jsonPath, $extensionDirectory ) ) {
114 // Strip $wgExtensionDirectory which might be configured outside of $IP
115 // /path/to/mw-ext-all/Example/bar -> Example/bar
116 $canonicalJsonPath = str_replace( "$extensionDirectory/", '', $jsonPath );
117
118 // Strip the extension name
119 // Example/bar -> bar
120 $canonicalJsonPath = explode( '/', $canonicalJsonPath, 2 )[1];
121 } else {
122 // For mediawiki/core we strip $IP
123 // /path/to/mediawiki/foo/bar -> foo/bar
124 $canonicalJsonPath = str_replace( "$installPath/", '', $jsonPath );
125 }
126
127 return $canonicalJsonPath;
128 }
129
138 private function cleanupSqlArray( string $platform, array $sqlArray ): string {
139 if ( !$sqlArray ) {
140 return '';
141 }
142
143 // Temporary
144 $sql = implode( ";\n\n", $sqlArray ) . ';';
145 $sql = ( new SqlFormatter( new NullHighlighter() ) )->format( $sql );
146
147 // Postgres hacks
148 if ( $platform === 'postgres' ) {
149 // FIXME: Fix a lot of weird formatting issues caused by
150 // presence of partial index's WHERE clause, this should probably
151 // be done in some better way, but for now this can work temporarily
152 $sql = str_replace(
153 [ "WHERE\n ", "\n /*_*/\n ", " ", " );", "KEY(\n " ],
154 [ "WHERE", ' ', " ", ');', "KEY(\n " ],
155 $sql
156 );
157 }
158
159 // Temporary fixes until the linting issues are resolved upstream.
160 // https://github.com/doctrine/sql-formatter/issues/53
161
162 $sql = preg_replace( "!\s+/\*_\*/\s+!", " /*_*/", $sql );
163 $sql = preg_replace(
164 '!\s+/\*\$wgDBTableOptions\*/\s+;!',
165 ' /*$wgDBTableOptions*/;',
166 $sql
167 );
168
169 $sql = str_replace( ";\n\nCREATE TABLE ", ";\n\n\nCREATE TABLE ", $sql );
170 $sql = preg_replace( '/^(CREATE|DROP|ALTER)\s+(TABLE|VIEW|INDEX)\s+/m', '$1 $2 ', $sql );
171 $sql = preg_replace( '/(?<!\s|;)\s+(ADD|DROP|ALTER|MODIFY|CHANGE|RENAME)\s+/', "\n \$1 ", $sql );
172
173 if ( !str_ends_with( $sql, "\n" ) ) {
174 $sql .= "\n";
175 }
176
177 // Sqlite hacks
178 if ( $platform === 'sqlite' ) {
179 // Doctrine prepends __temp__ to the table name and we set the table with the schema prefix causing invalid
180 // sqlite.
181 $sql = preg_replace( '/__temp__\s*\/\*_\*\//', '/*_*/__temp__', $sql );
182 $sql = str_replace( "\n /*_*/", ' /*_*/', $sql );
183 }
184
185 return $sql;
186 }
187}
if(!defined('MEDIAWIKI')) if(!defined( 'MW_ENTRY_POINT')) global $IP
Environment checks.
Definition Setup.php:90
if(!defined('MW_SETUP_CALLBACK'))
Definition WebStart.php:69
Validate abstract schema json files against their JSON schema.
Helper to generate abstract schema and schema changes in maintenance scripts.
generateSchemaChange(string $platform, string $jsonPath)
generateSchema(string $platform, string $jsonPath)
validateAndGetSchema(string $jsonPath)
Fetches the abstract schema.
static normalizePath(string $jsonPath, string $IP, string $extensionDirectory)
Make absolute path relative to the root of $IP or extension.
$wgExtensionDirectory
Config variable stub for the ExtensionDirectory setting, for use by phpdoc and IDEs.
Update the CREDITS list by merging in the list of git commit authors.