PageRenderTime 56ms CodeModel.GetById 28ms RepoModel.GetById 1ms app.codeStats 0ms

/generator/lib/reverse/mysql/MysqlSchemaParser.php

https://bitbucket.org/aagraz/propel
PHP | 427 lines | 301 code | 54 blank | 72 comment | 62 complexity | 148fcb39e119303c85d1c17e9a0feaf5 MD5 | raw file
  1. <?php
  2. /**
  3. * This file is part of the Propel package.
  4. * For the full copyright and license information, please view the LICENSE
  5. * file that was distributed with this source code.
  6. *
  7. * @license MIT License
  8. */
  9. require_once dirname(__FILE__) . '/../BaseSchemaParser.php';
  10. /**
  11. * Mysql database schema parser.
  12. *
  13. * @author Hans Lellelid <hans@xmpl.org>
  14. * @version $Revision$
  15. * @package propel.generator.reverse.mysql
  16. */
  17. class MysqlSchemaParser extends BaseSchemaParser
  18. {
  19. /**
  20. * @var boolean
  21. */
  22. private $addVendorInfo = false;
  23. /**
  24. * Map MySQL native types to Propel types.
  25. * @var array
  26. */
  27. private static $mysqlTypeMap = array(
  28. 'tinyint' => PropelTypes::TINYINT,
  29. 'smallint' => PropelTypes::SMALLINT,
  30. 'mediumint' => PropelTypes::SMALLINT,
  31. 'int' => PropelTypes::INTEGER,
  32. 'integer' => PropelTypes::INTEGER,
  33. 'bigint' => PropelTypes::BIGINT,
  34. 'int24' => PropelTypes::BIGINT,
  35. 'real' => PropelTypes::REAL,
  36. 'float' => PropelTypes::FLOAT,
  37. 'decimal' => PropelTypes::DECIMAL,
  38. 'numeric' => PropelTypes::NUMERIC,
  39. 'double' => PropelTypes::DOUBLE,
  40. 'char' => PropelTypes::CHAR,
  41. 'varchar' => PropelTypes::VARCHAR,
  42. 'date' => PropelTypes::DATE,
  43. 'time' => PropelTypes::TIME,
  44. 'year' => PropelTypes::INTEGER,
  45. 'datetime' => PropelTypes::TIMESTAMP,
  46. 'timestamp' => PropelTypes::TIMESTAMP,
  47. 'tinyblob' => PropelTypes::BINARY,
  48. 'blob' => PropelTypes::BLOB,
  49. 'mediumblob' => PropelTypes::BLOB,
  50. 'longblob' => PropelTypes::BLOB,
  51. 'longtext' => PropelTypes::CLOB,
  52. 'tinytext' => PropelTypes::VARCHAR,
  53. 'mediumtext' => PropelTypes::LONGVARCHAR,
  54. 'text' => PropelTypes::LONGVARCHAR,
  55. 'enum' => PropelTypes::CHAR,
  56. 'set' => PropelTypes::CHAR,
  57. );
  58. protected static $defaultTypeSizes = array(
  59. 'char' => 1,
  60. 'tinyint' => 4,
  61. 'smallint' => 6,
  62. 'int' => 11,
  63. 'bigint' => 20,
  64. 'decimal' => 10,
  65. );
  66. /**
  67. * Gets a type mapping from native types to Propel types
  68. *
  69. * @return array
  70. */
  71. protected function getTypeMapping()
  72. {
  73. return self::$mysqlTypeMap;
  74. }
  75. /**
  76. *
  77. */
  78. public function parse(Database $database, Task $task = null)
  79. {
  80. $this->addVendorInfo = $this->getGeneratorConfig()->getBuildProperty('addVendorInfo');
  81. $stmt = $this->dbh->query("SHOW FULL TABLES");
  82. // First load the tables (important that this happen before filling out details of tables)
  83. $tables = array();
  84. if ($task) {
  85. $task->log("Reverse Engineering Tables", Project::MSG_VERBOSE);
  86. }
  87. while ($row = $stmt->fetch(PDO::FETCH_NUM)) {
  88. $name = $row[0];
  89. $type = $row[1];
  90. if ($name == $this->getMigrationTable() || $type != "BASE TABLE") {
  91. continue;
  92. }
  93. if ($task) {
  94. $task->log(" Adding table '" . $name . "'", Project::MSG_VERBOSE);
  95. }
  96. $table = new Table($name);
  97. $table->setIdMethod($database->getDefaultIdMethod());
  98. $database->addTable($table);
  99. $tables[] = $table;
  100. }
  101. // Now populate only columns.
  102. if ($task) {
  103. $task->log("Reverse Engineering Columns", Project::MSG_VERBOSE);
  104. }
  105. foreach ($tables as $table) {
  106. if ($task) {
  107. $task->log(" Adding columns for table '" . $table->getName() . "'", Project::MSG_VERBOSE);
  108. }
  109. $this->addColumns($table);
  110. }
  111. // Now add indices and constraints.
  112. if ($task) {
  113. $task->log("Reverse Engineering Indices And Constraints", Project::MSG_VERBOSE);
  114. }
  115. foreach ($tables as $table) {
  116. if ($task) {
  117. $task->log(" Adding indices and constraints for table '" . $table->getName() . "'", Project::MSG_VERBOSE);
  118. }
  119. $this->addForeignKeys($table);
  120. $this->addIndexes($table);
  121. $this->addPrimaryKey($table);
  122. $this->addTableVendorInfo($table);
  123. }
  124. return count($tables);
  125. }
  126. /**
  127. * Adds Columns to the specified table.
  128. *
  129. * @param Table $table The Table model class to add columns to.
  130. */
  131. protected function addColumns(Table $table)
  132. {
  133. $stmt = $this->dbh->query("SHOW COLUMNS FROM `" . $table->getName() . "`");
  134. while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
  135. $column = $this->getColumnFromRow($row, $table);
  136. $table->addColumn($column);
  137. }
  138. } // addColumn()
  139. /**
  140. * Factory method creating a Column object
  141. * based on a row from the 'show columns from ' MySQL query result.
  142. *
  143. * @param array $row An associative array with the following keys:
  144. * Field, Type, Null, Key, Default, Extra.
  145. *
  146. * @return Column
  147. */
  148. public function getColumnFromRow($row, Table $table)
  149. {
  150. $name = $row['Field'];
  151. $is_nullable = ($row['Null'] == 'YES');
  152. $autoincrement = (strpos($row['Extra'], 'auto_increment') !== false);
  153. $size = null;
  154. $precision = null;
  155. $scale = null;
  156. $sqlType = false;
  157. $regexp = '/^
  158. (\w+) # column type [1]
  159. [\(] # (
  160. ?([\d,]*) # size or size, precision [2]
  161. [\)] # )
  162. ?\s* # whitespace
  163. (\w*) # extra description (UNSIGNED, CHARACTER SET, ...) [3]
  164. $/x';
  165. if (preg_match($regexp, $row['Type'], $matches)) {
  166. $nativeType = $matches[1];
  167. if ($matches[2]) {
  168. if (($cpos = strpos($matches[2], ',')) !== false) {
  169. $size = (int) substr($matches[2], 0, $cpos);
  170. $precision = $size;
  171. $scale = (int) substr($matches[2], $cpos + 1);
  172. } else {
  173. $size = (int) $matches[2];
  174. }
  175. }
  176. if ($matches[3]) {
  177. $sqlType = $row['Type'];
  178. }
  179. foreach (self::$defaultTypeSizes as $type => $defaultSize) {
  180. if ($nativeType == $type && $size == $defaultSize && $scale === null) {
  181. $size = null;
  182. continue;
  183. }
  184. }
  185. } elseif (preg_match('/^(\w+)\(/', $row['Type'], $matches)) {
  186. $nativeType = $matches[1];
  187. if ($nativeType == 'enum') {
  188. $sqlType = $row['Type'];
  189. }
  190. } else {
  191. $nativeType = $row['Type'];
  192. }
  193. //BLOBs can't have any default values in MySQL
  194. $default = preg_match('~blob|text~', $nativeType) ? null : $row['Default'];
  195. $propelType = $this->getMappedPropelType($nativeType);
  196. if (!$propelType) {
  197. $propelType = Column::DEFAULT_TYPE;
  198. $sqlType = $row['Type'];
  199. $this->warn("Column [" . $table->getName() . "." . $name . "] has a column type (" . $nativeType . ") that Propel does not support.");
  200. }
  201. // Special case for TINYINT(1) which is a BOOLEAN
  202. if (PropelTypes::TINYINT === $propelType && 1 === $size) {
  203. $propelType = PropelTypes::BOOLEAN;
  204. }
  205. $column = new Column($name);
  206. $column->setTable($table);
  207. $column->setDomainForType($propelType);
  208. if ($sqlType) {
  209. $column->getDomain()->replaceSqlType($sqlType);
  210. }
  211. $column->getDomain()->replaceSize($size);
  212. $column->getDomain()->replaceScale($scale);
  213. if ($default !== null) {
  214. if ($propelType == PropelTypes::BOOLEAN) {
  215. if ($default == '1') {
  216. $default = 'true';
  217. }
  218. if ($default == '0') {
  219. $default = 'false';
  220. }
  221. }
  222. if (in_array($default, array('CURRENT_TIMESTAMP'))) {
  223. $type = ColumnDefaultValue::TYPE_EXPR;
  224. } else {
  225. $type = ColumnDefaultValue::TYPE_VALUE;
  226. }
  227. $column->getDomain()->setDefaultValue(new ColumnDefaultValue($default, $type));
  228. }
  229. $column->setAutoIncrement($autoincrement);
  230. $column->setNotNull(!$is_nullable);
  231. if ($this->addVendorInfo) {
  232. $vi = $this->getNewVendorInfoObject($row);
  233. $column->addVendorInfo($vi);
  234. }
  235. return $column;
  236. }
  237. /**
  238. * Load foreign keys for this table.
  239. */
  240. protected function addForeignKeys(Table $table)
  241. {
  242. $database = $table->getDatabase();
  243. $stmt = $this->dbh->query("SHOW CREATE TABLE `" . $table->getName() . "`");
  244. $row = $stmt->fetch(PDO::FETCH_NUM);
  245. $foreignKeys = array(); // local store to avoid duplicates
  246. // Get the information on all the foreign keys
  247. $regEx = '/CONSTRAINT `([^`]+)` FOREIGN KEY \((.+)\) REFERENCES `([^`]*)` \((.+)\)(.*)/';
  248. if (preg_match_all($regEx, $row[1], $matches)) {
  249. $tmpArray = array_keys($matches[0]);
  250. foreach ($tmpArray as $curKey) {
  251. $name = $matches[1][$curKey];
  252. $rawlcol = $matches[2][$curKey];
  253. $ftbl = $matches[3][$curKey];
  254. $rawfcol = $matches[4][$curKey];
  255. $fkey = $matches[5][$curKey];
  256. $lcols = array();
  257. foreach (preg_split('/`, `/', $rawlcol) as $piece) {
  258. $lcols[] = trim($piece, '` ');
  259. }
  260. $fcols = array();
  261. foreach (preg_split('/`, `/', $rawfcol) as $piece) {
  262. $fcols[] = trim($piece, '` ');
  263. }
  264. //typical for mysql is RESTRICT
  265. $fkactions = array(
  266. 'ON DELETE' => ForeignKey::RESTRICT,
  267. 'ON UPDATE' => ForeignKey::RESTRICT,
  268. );
  269. if ($fkey) {
  270. //split foreign key information -> search for ON DELETE and afterwords for ON UPDATE action
  271. foreach (array_keys($fkactions) as $fkaction) {
  272. $result = null;
  273. preg_match('/' . $fkaction . ' (' . ForeignKey::CASCADE . '|' . ForeignKey::SETNULL . ')/', $fkey, $result);
  274. if ($result && is_array($result) && isset($result[1])) {
  275. $fkactions[$fkaction] = $result[1];
  276. }
  277. }
  278. }
  279. // restrict is the default
  280. foreach ($fkactions as $key => $action) {
  281. if ($action == ForeignKey::RESTRICT) {
  282. $fkactions[$key] = null;
  283. }
  284. }
  285. $localColumns = array();
  286. $foreignColumns = array();
  287. $foreignTable = $database->getTable($ftbl, true);
  288. foreach ($fcols as $fcol) {
  289. $foreignColumns[] = $foreignTable->getColumn($fcol);
  290. }
  291. foreach ($lcols as $lcol) {
  292. $localColumns[] = $table->getColumn($lcol);
  293. }
  294. if (!isset($foreignKeys[$name])) {
  295. $fk = new ForeignKey($name);
  296. $fk->setForeignTableCommonName($foreignTable->getCommonName());
  297. $fk->setForeignSchemaName($foreignTable->getSchema());
  298. $fk->setOnDelete($fkactions['ON DELETE']);
  299. $fk->setOnUpdate($fkactions['ON UPDATE']);
  300. $table->addForeignKey($fk);
  301. $foreignKeys[$name] = $fk;
  302. }
  303. for ($i = 0; $i < count($localColumns); $i++) {
  304. $foreignKeys[$name]->addReference($localColumns[$i], $foreignColumns[$i]);
  305. }
  306. }
  307. }
  308. }
  309. /**
  310. * Load indexes for this table
  311. */
  312. protected function addIndexes(Table $table)
  313. {
  314. $stmt = $this->dbh->query("SHOW INDEX FROM `" . $table->getName() . "`");
  315. // Loop through the returned results, grouping the same key_name together
  316. // adding each column for that key.
  317. $indexes = array();
  318. while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
  319. $colName = $row["Column_name"];
  320. $name = $row["Key_name"];
  321. if ($name == "PRIMARY") {
  322. continue;
  323. }
  324. if (!isset($indexes[$name])) {
  325. $isUnique = ($row["Non_unique"] == 0);
  326. if ($isUnique) {
  327. $indexes[$name] = new Unique($name);
  328. } else {
  329. $indexes[$name] = new Index($name);
  330. }
  331. if ($this->addVendorInfo) {
  332. $vi = $this->getNewVendorInfoObject($row);
  333. $indexes[$name]->addVendorInfo($vi);
  334. }
  335. $table->addIndex($indexes[$name]);
  336. }
  337. $indexes[$name]->addColumn($table->getColumn($colName));
  338. }
  339. }
  340. /**
  341. * Loads the primary key for this table.
  342. */
  343. protected function addPrimaryKey(Table $table)
  344. {
  345. $stmt = $this->dbh->query("SHOW KEYS FROM `" . $table->getName() . "`");
  346. // Loop through the returned results, grouping the same key_name together
  347. // adding each column for that key.
  348. while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
  349. // Skip any non-primary keys.
  350. if ($row['Key_name'] !== 'PRIMARY') {
  351. continue;
  352. }
  353. $name = $row["Column_name"];
  354. $table->getColumn($name)->setPrimaryKey(true);
  355. }
  356. }
  357. /**
  358. * Adds vendor-specific info for table.
  359. *
  360. * @param Table $table
  361. */
  362. protected function addTableVendorInfo(Table $table)
  363. {
  364. $stmt = $this->dbh->query("SHOW TABLE STATUS LIKE '" . $table->getName() . "'");
  365. $row = $stmt->fetch(PDO::FETCH_ASSOC);
  366. if (!$this->addVendorInfo) {
  367. //since we depend on `Engine` in the MysqlPlatform, we have always extract this vendor information
  368. $row = array('Engine' => $row['Engine']);
  369. }
  370. $vi = $this->getNewVendorInfoObject($row);
  371. $table->addVendorInfo($vi);
  372. }
  373. }