IndexShell.php 6.8 KB

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