1<?php 2/** 3 * @author Bart Visscher <bartv@thisnet.nl> 4 * @author Morris Jobke <hey@morrisjobke.de> 5 * @author Oliver Gasser <oliver.gasser@gmail.com> 6 * @author Robin Appelman <icewind@owncloud.com> 7 * @author Robin McCorkell <robin@mccorkell.me.uk> 8 * @author Thomas Müller <thomas.mueller@tmit.eu> 9 * @author Victor Dubiniuk <dubiniuk@owncloud.com> 10 * @author Vincent Petry <pvince81@owncloud.com> 11 * 12 * @copyright Copyright (c) 2018, ownCloud GmbH 13 * @license AGPL-3.0 14 * 15 * This code is free software: you can redistribute it and/or modify 16 * it under the terms of the GNU Affero General Public License, version 3, 17 * as published by the Free Software Foundation. 18 * 19 * This program is distributed in the hope that it will be useful, 20 * but WITHOUT ANY WARRANTY; without even the implied warranty of 21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 * GNU Affero General Public License for more details. 23 * 24 * You should have received a copy of the GNU Affero General Public License, version 3, 25 * along with this program. If not, see <http://www.gnu.org/licenses/> 26 * 27 */ 28 29namespace OC\DB; 30 31use Doctrine\DBAL\Platforms\AbstractPlatform; 32use Doctrine\DBAL\Schema\Schema; 33use OCP\IConfig; 34 35class MDB2SchemaReader { 36 37 /** @var string $DBTABLEPREFIX */ 38 protected $DBTABLEPREFIX; 39 40 /** @var \Doctrine\DBAL\Platforms\AbstractPlatform $platform */ 41 protected $platform; 42 43 /** 44 * @param \OCP\IConfig $config 45 * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform 46 */ 47 public function __construct(IConfig $config, AbstractPlatform $platform) { 48 $this->platform = $platform; 49 $this->DBTABLEPREFIX = $config->getSystemValue('dbtableprefix', 'oc_'); 50 } 51 52 /** 53 * @param string $file 54 * @param Schema $schema 55 * @return Schema 56 */ 57 public function loadSchemaFromFile($file, Schema $schema) { 58 $loadEntities = \libxml_disable_entity_loader(false); 59 $xml = \simplexml_load_file($file); 60 \libxml_disable_entity_loader($loadEntities); 61 foreach ($xml->children() as $child) { 62 /** 63 * @var \SimpleXMLElement $child 64 */ 65 switch ($child->getName()) { 66 case 'name': 67 case 'create': 68 case 'overwrite': 69 case 'charset': 70 break; 71 case 'table': 72 $this->loadTable($schema, $child); 73 break; 74 default: 75 throw new \DomainException('Unknown element: ' . $child->getName()); 76 77 } 78 } 79 return $schema; 80 } 81 82 /** 83 * @param \Doctrine\DBAL\Schema\Schema $schema 84 * @param \SimpleXMLElement $xml 85 * @throws \DomainException 86 */ 87 private function loadTable($schema, $xml) { 88 $table = null; 89 foreach ($xml->children() as $child) { 90 /** 91 * @var \SimpleXMLElement $child 92 */ 93 switch ($child->getName()) { 94 case 'name': 95 $name = (string)$child; 96 $name = \str_replace('*dbprefix*', $this->DBTABLEPREFIX, $name); 97 $name = $this->platform->quoteIdentifier($name); 98 $table = $schema->createTable($name); 99 break; 100 case 'create': 101 case 'overwrite': 102 case 'charset': 103 break; 104 case 'declaration': 105 if ($table === null) { 106 throw new \DomainException('Table declaration before table name'); 107 } 108 $this->loadDeclaration($table, $child); 109 break; 110 default: 111 throw new \DomainException('Unknown element: ' . $child->getName()); 112 113 } 114 } 115 } 116 117 /** 118 * @param \Doctrine\DBAL\Schema\Table $table 119 * @param \SimpleXMLElement $xml 120 * @throws \DomainException 121 */ 122 private function loadDeclaration($table, $xml) { 123 foreach ($xml->children() as $child) { 124 /** 125 * @var \SimpleXMLElement $child 126 */ 127 switch ($child->getName()) { 128 case 'field': 129 $this->loadField($table, $child); 130 break; 131 case 'index': 132 $this->loadIndex($table, $child); 133 break; 134 default: 135 throw new \DomainException('Unknown element: ' . $child->getName()); 136 137 } 138 } 139 } 140 141 /** 142 * @param \Doctrine\DBAL\Schema\Table $table 143 * @param \SimpleXMLElement $xml 144 * @throws \DomainException 145 */ 146 private function loadField($table, $xml) { 147 $options = ['notnull' => false]; 148 $primary = null; 149 foreach ($xml->children() as $child) { 150 /** 151 * @var \SimpleXMLElement $child 152 */ 153 switch ($child->getName()) { 154 case 'name': 155 $name = (string)$child; 156 $name = $this->platform->quoteIdentifier($name); 157 break; 158 case 'type': 159 $type = (string)$child; 160 switch ($type) { 161 case 'text': 162 $type = 'string'; 163 break; 164 case 'clob': 165 $type = 'text'; 166 break; 167 case 'timestamp': 168 $type = 'datetime'; 169 break; 170 case 'numeric': 171 $type = 'decimal'; 172 break; 173 } 174 break; 175 case 'length': 176 $length = (string)$child; 177 $options['length'] = $length; 178 break; 179 case 'unsigned': 180 $unsigned = $this->asBool($child); 181 $options['unsigned'] = $unsigned; 182 break; 183 case 'notnull': 184 $notnull = $this->asBool($child); 185 $options['notnull'] = $notnull; 186 break; 187 case 'autoincrement': 188 $autoincrement = $this->asBool($child); 189 $options['autoincrement'] = $autoincrement; 190 break; 191 case 'default': 192 $default = (string)$child; 193 $options['default'] = $default; 194 break; 195 case 'comments': 196 $comment = (string)$child; 197 $options['comment'] = $comment; 198 break; 199 case 'primary': 200 $primary = $this->asBool($child); 201 break; 202 case 'precision': 203 $precision = (string)$child; 204 $options['precision'] = $precision; 205 break; 206 case 'scale': 207 $scale = (string)$child; 208 $options['scale'] = $scale; 209 break; 210 default: 211 throw new \DomainException('Unknown element: ' . $child->getName()); 212 213 } 214 } 215 if (isset($name, $type)) { 216 if (isset($options['default']) && empty($options['default'])) { 217 if (empty($options['notnull']) || !$options['notnull']) { 218 unset($options['default']); 219 $options['notnull'] = false; 220 } else { 221 $options['default'] = ''; 222 } 223 if ($type == 'integer' || $type == 'decimal') { 224 $options['default'] = 0; 225 } elseif ($type == 'boolean') { 226 $options['default'] = false; 227 } 228 if (!empty($options['autoincrement']) && $options['autoincrement']) { 229 unset($options['default']); 230 } 231 } 232 if ($type === 'integer' && isset($options['default'])) { 233 $options['default'] = (int)$options['default']; 234 } 235 if ($type === 'integer' && isset($options['length'])) { 236 $length = $options['length']; 237 if ($length < 4) { 238 $type = 'smallint'; 239 } elseif ($length > 4) { 240 $type = 'bigint'; 241 } 242 } 243 if ($type === 'boolean' && isset($options['default'])) { 244 $options['default'] = $this->asBool($options['default']); 245 } 246 if (!empty($options['autoincrement']) 247 && !empty($options['notnull']) 248 ) { 249 $primary = true; 250 } 251 252 $table->addColumn($name, $type, $options); 253 if ($primary) { 254 $table->setPrimaryKey([$name]); 255 } 256 } 257 } 258 259 /** 260 * @param \Doctrine\DBAL\Schema\Table $table 261 * @param \SimpleXMLElement $xml 262 * @throws \DomainException 263 */ 264 private function loadIndex($table, $xml) { 265 $name = null; 266 $fields = []; 267 foreach ($xml->children() as $child) { 268 /** 269 * @var \SimpleXMLElement $child 270 */ 271 switch ($child->getName()) { 272 case 'name': 273 $name = (string)$child; 274 break; 275 case 'primary': 276 $primary = $this->asBool($child); 277 break; 278 case 'unique': 279 $unique = $this->asBool($child); 280 break; 281 case 'field': 282 foreach ($child->children() as $field) { 283 /** 284 * @var \SimpleXMLElement $field 285 */ 286 switch ($field->getName()) { 287 case 'name': 288 $field_name = (string)$field; 289 $field_name = $this->platform->quoteIdentifier($field_name); 290 $fields[] = $field_name; 291 break; 292 case 'sorting': 293 break; 294 default: 295 throw new \DomainException('Unknown element: ' . $field->getName()); 296 297 } 298 } 299 break; 300 default: 301 throw new \DomainException('Unknown element: ' . $child->getName()); 302 303 } 304 } 305 if (!empty($fields)) { 306 if (isset($primary) && $primary) { 307 if ($table->hasPrimaryKey()) { 308 return; 309 } 310 $table->setPrimaryKey($fields, $name); 311 } else { 312 if (isset($unique) && $unique) { 313 $table->addUniqueIndex($fields, $name); 314 } else { 315 $table->addIndex($fields, $name); 316 } 317 } 318 } else { 319 throw new \DomainException('Empty index definition: ' . $name . ' options:' . \print_r($fields, true)); 320 } 321 } 322 323 /** 324 * @param \SimpleXMLElement|string $xml 325 * @return bool 326 */ 327 private function asBool($xml) { 328 $result = (string)$xml; 329 if ($result == 'true') { 330 $result = true; 331 } elseif ($result == 'false') { 332 $result = false; 333 } 334 return (bool)$result; 335 } 336} 337