IndexShell.php 6.6 KB

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