You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

359 lines
9.2 KiB

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