IndexShell.php 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. <?php
  2. if (!defined('CONFIGS')) {
  3. define('CONFIGS', APP . 'Config' . DS);
  4. }
  5. App::uses('ConnectionManager', 'Model');
  6. App::uses('AppShell', 'Console/Command');
  7. /**
  8. * Add missing indexes to your schema in a snip
  9. * based on ad7six' UuidifyShell
  10. *
  11. * Currently supports automatically:
  12. * - BTREE Indexes for UUIDs
  13. *
  14. * TODO:
  15. * - BTREE Indexes for AIIDs if desired
  16. * - PRIMARY_KEY Indexes for primary keys ("id")
  17. *
  18. * @author Mark Scherer
  19. * @link
  20. * @license http://www.opensource.org/licenses/mit-license.php The MIT License
  21. * @cakephp 2.x
  22. */
  23. class IndexShell extends AppShell {
  24. /**
  25. * Runtime settings
  26. *
  27. * @var array
  28. */
  29. public $settings = array(
  30. 'ds' => 'default',
  31. );
  32. /**
  33. * The Stack of sql queries to run as an array
  34. *
  35. * @var array
  36. */
  37. protected $_script = array();
  38. /**
  39. * Startup method
  40. *
  41. * @return void
  42. */
  43. public function startup() {
  44. parent::startup();
  45. $this->_welcome();
  46. }
  47. /**
  48. * Initialize method
  49. *
  50. * If the flags -h, -help or --help are present bail here and show help
  51. *
  52. * @return void
  53. */
  54. public function initialize() {
  55. parent::initialize();
  56. $this->_loadModels();
  57. }
  58. /**
  59. * Main method
  60. *
  61. * Generate the required sql, and then run it
  62. * To run for more than one datasource - comma seperate them:
  63. * cake Tools.Index default,permissions,other
  64. *
  65. * @return void
  66. */
  67. public function run() {
  68. $this->_buildScript(explode(',', $this->settings['ds']));
  69. $this->_runScript();
  70. }
  71. /**
  72. * Process each named datasource in turn
  73. *
  74. * E.g. ->_buildScript(array('default', 'users'));
  75. *
  76. * @param array $sources array()
  77. * @return void
  78. */
  79. protected function _buildScript($sources = array()) {
  80. foreach ($sources as $ds) {
  81. $this->_buildScriptForDataSource($ds);
  82. }
  83. }
  84. /**
  85. * Generate the conversion sql for the requested datasource.
  86. *
  87. * For each table in the db - find all primary or foreign keys (that follow conventions)
  88. * currently skips primary keys (should already be PRIMARY)
  89. *
  90. * @param mixed $ds
  91. * @return void
  92. */
  93. protected function _buildScriptForDataSource($ds = 'default') {
  94. $tables = $this->_tables($ds);
  95. $db = ConnectionManager::getDataSource($ds);
  96. $usePrefix = empty($db->config['prefix']) ? '' : $db->config['prefix'];
  97. $doneSomething = false;
  98. foreach ($tables as $table) {
  99. if (in_array($table, array('i18n'))) {
  100. continue;
  101. }
  102. $model = Inflector::classify($table);
  103. $Inst = ClassRegistry::init(array(
  104. 'class' => $model,
  105. 'table' => $table,
  106. 'ds' => $ds
  107. ));
  108. if (!is_callable(array($Inst, 'schema'))) {
  109. continue;
  110. }
  111. $fields = $Inst->schema();
  112. $indexInfo = $Inst->query('SHOW INDEX FROM `' . $usePrefix . $table . '`');
  113. foreach ($fields as $field => $details) {
  114. if (!preg_match('@(^|_)(id|key)$@', $field)) {
  115. continue;
  116. }
  117. if ($details['type'] !== 'integer' && ($details['type'] !== 'string' || $details['length'] !== 36)) {
  118. continue;
  119. }
  120. // right now ONLY for uuids
  121. if ($details['type'] !== 'string') {
  122. continue;
  123. }
  124. foreach ($indexInfo as $info) {
  125. $column = $info['STATISTICS']['Column_name'];
  126. $key = $info['STATISTICS']['Key_name'];
  127. // dont override primary keys
  128. if ($column == $field && $key === 'PRIMARY') {
  129. continue 2;
  130. }
  131. // already exists
  132. if ($column == $field && $key == $field) {
  133. continue 2;
  134. }
  135. }
  136. $this->out('Create index for ' . $table . '.' . $field);
  137. $this->_script[$ds]['index'][] = "ALTER TABLE `$usePrefix$table` ADD INDEX (`$field`)";
  138. }
  139. }
  140. }
  141. protected function _tables($useDbConfig = 'default') {
  142. if (!$useDbConfig) {
  143. return array();
  144. }
  145. require_once CONFIGS . 'database.php';
  146. $connections = get_class_vars('DATABASE_CONFIG');
  147. if (!isset($connections[$useDbConfig])) {
  148. return array();
  149. }
  150. $db = ConnectionManager::getDataSource($useDbConfig);
  151. if (!$db) {
  152. return array();
  153. }
  154. $usePrefix = empty($db->config['prefix']) ? '' : $db->config['prefix'];
  155. $tables = array();
  156. if ($usePrefix) {
  157. foreach ($db->listSources() as $table) {
  158. if (!strncmp($table, $usePrefix, strlen($usePrefix))) {
  159. $tables[$useDbConfig . '::' . $table] = substr($table, strlen($usePrefix));
  160. }
  161. }
  162. } else {
  163. $_tables = $db->listSources();
  164. foreach ($_tables as $table) {
  165. $tables[$useDbConfig . '::' . $table] = $table;
  166. }
  167. }
  168. return $tables;
  169. }
  170. /**
  171. * Query method
  172. *
  173. * If the force (or the shortcut f) parameter is set, don't ask for confirmation
  174. * If the user chooses to quit - stop processing at that moment
  175. * If the parameter `dry-run` is specified - don't do anything except dump the script to stdout
  176. *
  177. * @param mixed $statement
  178. * @return void
  179. */
  180. protected function _query($statement) {
  181. if (!$statement) {
  182. $this->out();
  183. return;
  184. }
  185. $statement .= ';';
  186. $this->out($statement);
  187. if (!empty($this->params['dry-run'])) {
  188. return;
  189. }
  190. if (empty($this->params['interactive'])) {
  191. $continue = 'Y';
  192. } else {
  193. $continue = strtoupper($this->in(__('Run this statement?'), array('Y', 'N', 'A', 'Q')));
  194. switch ($continue) {
  195. case 'Q':
  196. return $this->_stop();
  197. return;
  198. case 'N':
  199. return;
  200. case 'A':
  201. $continue = 'Y';
  202. $this->params['interactive'] = false;
  203. case 'Y':
  204. break;
  205. }
  206. }
  207. if ($continue === 'Y') {
  208. $this->Db->query($statement);
  209. }
  210. }
  211. /**
  212. * Loop over the script running each statement in turn
  213. *
  214. * @return void
  215. */
  216. protected function _runScript() {
  217. foreach ($this->_script as $ds => $steps) {
  218. ksort($steps);
  219. $this->Db = ConnectionManager::getDataSource($ds);
  220. foreach ($steps as $step => $statements) {
  221. foreach ($statements as $statement) {
  222. $this->_query($statement);
  223. }
  224. }
  225. }
  226. }
  227. public function getOptionParser() {
  228. $subcommandParser = array(
  229. 'options' => array(
  230. 'dry-run' => array(
  231. 'short' => 'd',
  232. 'help' => __d('cake_console', 'Dry run the update, no files will actually be modified.'),
  233. 'boolean' => true
  234. ),
  235. 'log' => array(
  236. 'short' => 'l',
  237. 'help' => __d('cake_console', 'Log all ouput to file log.txt in TMP dir'),
  238. 'boolean' => true
  239. ),
  240. 'interactive' => array(
  241. 'short' => 'i',
  242. 'help' => __d('cake_console', 'Interactive'),
  243. 'boolean' => true
  244. ),
  245. 'ds' => array(
  246. 'short' => 'c',
  247. 'help' => __d('cake_console', 'custom ds'),
  248. 'boolean' => true
  249. )
  250. )
  251. );
  252. return parent::getOptionParser()
  253. ->description(__d('cake_console', "..."))
  254. ->addSubcommand('run', array(
  255. 'help' => __d('cake_console', 'Run'),
  256. 'parser' => $subcommandParser
  257. ));
  258. }
  259. }