PageRenderTime 32ms CodeModel.GetById 13ms RepoModel.GetById 1ms app.codeStats 0ms

/core/src/main/php/rdbms/mysql/MySQLDBAdapter.class.php

https://github.com/treuter/xp-framework
PHP | 353 lines | 214 code | 30 blank | 109 comment | 25 complexity | 25e47404bbc44fea8036ac462a235217 MD5 | raw file
  1. <?php
  2. /* This class is part of the XP framework
  3. *
  4. * $Id$
  5. */
  6. uses('rdbms.DBAdapter');
  7. /**
  8. * Adapter for MySQL
  9. *
  10. * @test xp://net.xp_framework.unittest.rdbms.mysql.TableDescriptionTest
  11. * @see xp://rdbms.DBAdapter
  12. * @see xp://rdbms.mysql.MySQLConnection
  13. */
  14. class MySQLDBAdapter extends DBAdapter {
  15. public static $map= array(
  16. 'varchar' => DB_ATTRTYPE_VARCHAR,
  17. 'varbinary' => DB_ATTRTYPE_VARCHAR,
  18. 'char' => DB_ATTRTYPE_CHAR,
  19. 'int' => DB_ATTRTYPE_INT,
  20. 'bigint' => DB_ATTRTYPE_NUMERIC,
  21. 'mediumint' => DB_ATTRTYPE_SMALLINT,
  22. 'smallint' => DB_ATTRTYPE_SMALLINT,
  23. 'tinyint' => DB_ATTRTYPE_TINYINT,
  24. 'bit' => DB_ATTRTYPE_TINYINT,
  25. 'date' => DB_ATTRTYPE_DATE,
  26. 'datetime' => DB_ATTRTYPE_DATETIME,
  27. 'timestamp' => DB_ATTRTYPE_TIMESTAMP,
  28. 'tinytext' => DB_ATTRTYPE_TEXT,
  29. 'mediumtext' => DB_ATTRTYPE_TEXT,
  30. 'text' => DB_ATTRTYPE_TEXT,
  31. 'longtext' => DB_ATTRTYPE_TEXT,
  32. 'enum' => DB_ATTRTYPE_ENUM,
  33. 'decimal' => DB_ATTRTYPE_DECIMAL,
  34. 'float' => DB_ATTRTYPE_FLOAT,
  35. 'double' => DB_ATTRTYPE_FLOAT,
  36. 'tinyblob' => DB_ATTRTYPE_TEXT,
  37. 'blob' => DB_ATTRTYPE_TEXT,
  38. 'mediumblob' => DB_ATTRTYPE_TEXT,
  39. 'longblob' => DB_ATTRTYPE_TEXT,
  40. 'time' => DB_ATTRTYPE_TEXT
  41. );
  42. /**
  43. * Get databases
  44. *
  45. * @return string[] databases
  46. */
  47. public function getDatabases() {
  48. $dbs= array();
  49. $q= $this->conn->query('show databases');
  50. while ($name= $q->next()) {
  51. $dbs[]= $name[key($name)];
  52. }
  53. return $dbs;
  54. }
  55. /**
  56. * Get tables by database
  57. *
  58. * @param string database default NULL if omitted, uses current database
  59. * @return rdbms.DBTable[] array of DBTable objects
  60. */
  61. public function getTables($database= NULL) {
  62. $t= array();
  63. $database= $this->database($database);
  64. $q= $this->conn->query(
  65. 'show tables from %c',
  66. $database
  67. );
  68. while ($table= $q->next()) {
  69. $t[]= $this->getTable($table[key($table)], $database);
  70. }
  71. return $t;
  72. }
  73. /**
  74. * Creates a table attribute from a "describe {table}" result
  75. *
  76. * Example:
  77. * <pre>
  78. * +-------------+--------------+------+-----+---------------------+----------------+
  79. * | Field | Type | Null | Key | Default | Extra |
  80. * +-------------+--------------+------+-----+---------------------+----------------+
  81. * | contract_id | int(8) | | PRI | NULL | auto_increment |
  82. * | user_id | int(8) | | | 0 | |
  83. * | mandant_id | int(4) | | | 0 | |
  84. * | description | varchar(255) | | | | |
  85. * | comment | varchar(255) | | | | |
  86. * | bz_id | int(6) | | | 0 | |
  87. * | lastchange | datetime | | | 0000-00-00 00:00:00 | |
  88. * | changedby | varchar(16) | | | | |
  89. * +-------------+--------------+------+-----+---------------------+----------------+
  90. * 8 rows in set (0.00 sec)
  91. * </pre>
  92. *
  93. * @param [:string] record
  94. * @return rdbms.DBTableAttribute
  95. */
  96. public static function tableAttributeFrom($record) {
  97. preg_match('#^([a-z]+)(\(([0-9,]+)\))?#', $record['Type'], $regs);
  98. return new DBTableAttribute(
  99. $record['Field'], // name
  100. self::$map[$regs[1]], // type
  101. FALSE !== strpos($record['Extra'], 'auto_increment'), // identity
  102. !(empty($record['Null']) || ('NO' == $record['Null'])), // nullable
  103. (int)$regs[3], // length
  104. 0, // precision
  105. 0 // scale
  106. );
  107. }
  108. /**
  109. * Get table by name
  110. *
  111. * @param string table
  112. * @param string database default NULL if omitted, uses current database
  113. * @return rdbms.DBTable a DBTable object
  114. */
  115. public function getTable($table, $database= NULL) {
  116. $t= new DBTable($table);
  117. $q= $this->conn->query('describe %c', $this->qualifiedTablename($table, $database));
  118. while ($record= $q->next()) {
  119. $t->addAttribute(self::tableAttributeFrom($record));
  120. }
  121. // Get keys
  122. // +----------+------------+---------------+--------------+-------------+-----------+-------------+----------+--------+---------+
  123. // | Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Comment |
  124. // +----------+------------+---------------+--------------+-------------+-----------+-------------+----------+--------+---------+
  125. // | contract | 0 | PRIMARY | 1 | contract_id | A | 6 | NULL | NULL | |
  126. // | contract | 0 | contract_id_2 | 1 | contract_id | A | 6 | NULL | NULL | |
  127. // | contract | 1 | contract_id | 1 | contract_id | A | 6 | NULL | NULL | |
  128. // | contract | 1 | contract_id | 2 | user_id | A | 6 | NULL | NULL | |
  129. // +----------+------------+---------------+--------------+-------------+-----------+-------------+----------+--------+---------+
  130. $q= $this->conn->query('show keys from %c', $this->qualifiedTablename($table, $database));
  131. $key= NULL;
  132. while ($record= $q->next()) {
  133. if ($record['Key_name'] != $key) {
  134. $index= $t->addIndex(new DBIndex(
  135. $record['Key_name'],
  136. array()
  137. ));
  138. $key= $record['Key_name'];
  139. }
  140. $index->unique= ('0' == $record['Non_unique']);
  141. $index->primary= ('PRIMARY' == $record['Key_name']);
  142. $index->keys[]= $record['Column_name'];
  143. }
  144. // Get foreign key constraints
  145. // in mysql the only way is to parse the creat statement
  146. $createTableString= $this->conn->query('show create table %c', $this->qualifiedTablename($table, $database))->next('Create Table');
  147. for ($i= 0; $i < strlen($createTableString); $i++) {
  148. switch ($createTableString{$i}) {
  149. case '`':
  150. $this->parseQuoteString($createTableString, $i, '`');
  151. break;
  152. case '"':
  153. $this->parseQuoteString($createTableString, $i, '"');
  154. break;
  155. case '(':
  156. $tableConstraints= $this->filterConstraints($this->extractParams($this->parseBracerString($createTableString, $i)));
  157. foreach ($tableConstraints as $tableConstraint) {
  158. if (strstr($tableConstraint, 'FOREIGN KEY') === FALSE) continue;
  159. $t->addForeignKeyConstraint($this->parseForeignKeyString($tableConstraint));
  160. }
  161. break;
  162. }
  163. }
  164. return $t;
  165. }
  166. /**
  167. * Get full table name with database if possible
  168. *
  169. * @param string table
  170. * @param string database default NULL if omitted, uses current database
  171. * @return string full table name
  172. */
  173. private function qualifiedTablename($table, $database= NULL) {
  174. $database= $this->database($database);
  175. if (NULL !== $database) return $database.'.'.$table;
  176. return $table;
  177. }
  178. /**
  179. * Get the current database
  180. *
  181. * @param string database default NULL if omitted, uses current database
  182. * @return string full table name
  183. */
  184. private function database($database= NULL) {
  185. if (NULL !== $database) return $database;
  186. return $this->conn->query('select database() as db')->next('db');
  187. }
  188. /**
  189. * get the foreign key object from a string
  190. *
  191. * @param string parsestring
  192. * @return rdbms.DBForeignKeyConstraint
  193. */
  194. private function parseForeignKeyString($string) {
  195. $constraint= new DBForeignKeyConstraint();
  196. $quotstrings= array();
  197. $bracestrings= array();
  198. $attributes= array();
  199. $pos= 10;
  200. while (++$pos < strlen($string)) {
  201. switch ($string{$pos}) {
  202. case '`':
  203. $quotstrings[]= $this->parseQuoteString($string, $pos, '`');
  204. break;
  205. case '"':
  206. $quotstrings[]= $this->parseQuoteString($string, $pos, '"');
  207. break;
  208. case '(':
  209. $bracestrings[]= $this->parseBracerString($string, $pos);
  210. break;
  211. }
  212. }
  213. foreach ($bracestrings as $bracestring) {
  214. $params= $this->extractParams($bracestring);
  215. foreach ($params as $i => $param) $params[$i]= substr($param, 1, -1);
  216. $attributes[]= $params;
  217. }
  218. $constraint->setKeys(array_combine($attributes[0], $attributes[1]));
  219. $constraint->setName($quotstrings[0]);
  220. $constraint->setSource($quotstrings[1]);
  221. return $constraint;
  222. }
  223. /**
  224. * get the text inner a quotation
  225. *
  226. * @param string parsestring
  227. * @param &int position where the quoted string begins
  228. * @param string quotation character
  229. * @return string inner quotation
  230. */
  231. private function parseQuoteString($string, &$pos, $quot) {
  232. $quotedString= '';
  233. while ($pos++ < strlen($string)) {
  234. switch ($string{$pos}) {
  235. case $quot:
  236. return $quotedString;
  237. default:
  238. $quotedString.= $string{$pos};
  239. }
  240. }
  241. return $quotedString;
  242. }
  243. /**
  244. * get the text inner bracers
  245. *
  246. * @param string parsestring
  247. * @param &int position where the bracered string begins
  248. * @return string inner bracers
  249. */
  250. private function parseBracerString($string, &$pos) {
  251. $braceredString= '';
  252. while ($pos++ < strlen($string)) {
  253. switch ($string{$pos}) {
  254. case ')':
  255. return $braceredString;
  256. break;
  257. case '(':
  258. $braceredString.= $string{$pos};
  259. $braceredString.= $this->parseBracerString($string, $pos).')';
  260. break;
  261. case '`':
  262. $braceredString.= $string{$pos};
  263. $braceredString.= $this->parseQuoteString($string, $pos, '`').'`';
  264. break;
  265. case '"':
  266. $braceredString.= $string{$pos};
  267. $braceredString.= $this->parseQuoteString($string, $pos, '"').'"';
  268. break;
  269. default:
  270. $braceredString.= $string{$pos};
  271. }
  272. }
  273. return $braceredString;
  274. }
  275. /**
  276. * get the single params in a paramstring
  277. *
  278. * @param string paramstring
  279. * @return string[] paramstrings
  280. */
  281. private function extractParams($string) {
  282. $paramArray= array();
  283. $paramString= '';
  284. $pos= 0;
  285. while ($pos < strlen($string)) {
  286. switch ($string{$pos}) {
  287. case ',':
  288. $paramArray[]= trim($paramString);
  289. $paramString= '';
  290. break;
  291. case '(':
  292. $paramString.= $string{$pos};
  293. $paramString.= $this->parseBracerString($string, $pos).')';
  294. break;
  295. case '`':
  296. $paramString.= $string{$pos};
  297. $paramString.= $this->parseQuoteString($string, $pos, '`').'`';
  298. break;
  299. case '"':
  300. $paramString.= $string{$pos};
  301. $paramString.= $this->parseQuoteString($string, $pos, '"').'"';
  302. break;
  303. default:
  304. $paramString.= $string{$pos};
  305. }
  306. $pos++;
  307. }
  308. $paramArray[]= trim($paramString);
  309. return $paramArray;
  310. }
  311. /**
  312. * filter the contraint parameters in a create table paramter string array
  313. *
  314. * @param string[] array with parameter strings
  315. * @return string[] constraint strings
  316. */
  317. private function filterConstraints($params) {
  318. $constraintArray= array();
  319. foreach ($params as $param) if ('CONSTRAINT' == substr($param, 0, 10)) $constraintArray[]= $param;
  320. return $constraintArray;
  321. }
  322. }
  323. ?>