Tool.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584
  1. <?php /* vim: se et ts=4 sw=4 sts=4 fdm=marker tw=80: */
  2. /**
  3. * Copyright (c) 1998-2010 Manuel Lemos, Tomas V.V.Cox,
  4. * Stig. S. Bakken, Lukas Smith, Igor Feghali
  5. * All rights reserved.
  6. *
  7. * MDB2_Schema enables users to maintain RDBMS independant schema files
  8. * in XML that can be used to manipulate both data and database schemas
  9. * This LICENSE is in the BSD license style.
  10. *
  11. * Redistribution and use in source and binary forms, with or without
  12. * modification, are permitted provided that the following conditions
  13. * are met:
  14. *
  15. * Redistributions of source code must retain the above copyright
  16. * notice, this list of conditions and the following disclaimer.
  17. *
  18. * Redistributions in binary form must reproduce the above copyright
  19. * notice, this list of conditions and the following disclaimer in the
  20. * documentation and/or other materials provided with the distribution.
  21. *
  22. * Neither the name of Manuel Lemos, Tomas V.V.Cox, Stig. S. Bakken,
  23. * Lukas Smith, Igor Feghali nor the names of his contributors may be
  24. * used to endorse or promote products derived from this software
  25. * without specific prior written permission.
  26. *
  27. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  28. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  29. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
  30. * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
  31. * REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
  32. * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
  33. * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
  34. * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
  35. * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  36. * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
  37. * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  38. * POSSIBILITY OF SUCH DAMAGE.
  39. *
  40. * PHP version 5
  41. *
  42. * @category Database
  43. * @package MDB2_Schema
  44. * @author Christian Weiske <cweiske@php.net>
  45. * @license BSD http://www.opensource.org/licenses/bsd-license.php
  46. * @version SVN: $Id$
  47. * @link http://pear.php.net/packages/MDB2_Schema
  48. */
  49. require_once 'MDB2/Schema.php';
  50. require_once 'MDB2/Schema/Tool/ParameterException.php';
  51. /**
  52. * Command line tool to work with database schemas
  53. *
  54. * Functionality:
  55. * - dump a database schema to stdout
  56. * - import schema into database
  57. * - create a diff between two schemas
  58. * - apply diff to database
  59. *
  60. * @category Database
  61. * @package MDB2_Schema
  62. * @author Christian Weiske <cweiske@php.net>
  63. * @license BSD http://www.opensource.org/licenses/bsd-license.php
  64. * @link http://pear.php.net/packages/MDB2_Schema
  65. */
  66. class MDB2_Schema_Tool
  67. {
  68. /**
  69. * Run the schema tool
  70. *
  71. * @param array $args Array of command line arguments
  72. */
  73. public function __construct($args)
  74. {
  75. $strAction = $this->getAction($args);
  76. try {
  77. $this->{'do' . ucfirst($strAction)}($args);
  78. } catch (MDB2_Schema_Tool_ParameterException $e) {
  79. $this->{'doHelp' . ucfirst($strAction)}($e->getMessage());
  80. }
  81. }//public function __construct($args)
  82. /**
  83. * Runs the tool with command line arguments
  84. *
  85. * @return void
  86. */
  87. public static function run()
  88. {
  89. $args = $GLOBALS['argv'];
  90. array_shift($args);
  91. try {
  92. $tool = new MDB2_Schema_Tool($args);
  93. } catch (Exception $e) {
  94. self::toStdErr($e->getMessage() . "\n");
  95. }
  96. }//public static function run()
  97. /**
  98. * Reads the first parameter from the argument array and
  99. * returns the action.
  100. *
  101. * @param array &$args Command line parameters
  102. *
  103. * @return string Action to execute
  104. */
  105. protected function getAction(&$args)
  106. {
  107. if (count($args) == 0) {
  108. return 'help';
  109. }
  110. $arg = array_shift($args);
  111. switch ($arg) {
  112. case 'h':
  113. case 'help':
  114. case '-h':
  115. case '--help':
  116. return 'help';
  117. case 'd':
  118. case 'dump':
  119. case '-d':
  120. case '--dump':
  121. return 'dump';
  122. case 'l':
  123. case 'load':
  124. case '-l':
  125. case '--load':
  126. return 'load';
  127. case 'i':
  128. case 'diff':
  129. case '-i':
  130. case '--diff':
  131. return 'diff';
  132. case 'a':
  133. case 'apply':
  134. case '-a':
  135. case '--apply':
  136. return 'apply';
  137. case 'n':
  138. case 'init':
  139. case '-i':
  140. case '--init':
  141. return 'init';
  142. default:
  143. throw new MDB2_Schema_Tool_ParameterException(
  144. "Unknown mode \"$arg\""
  145. );
  146. }
  147. }//protected function getAction(&$args)
  148. /**
  149. * Writes the message to stderr
  150. *
  151. * @param string $msg Message to print
  152. *
  153. * @return void
  154. */
  155. protected static function toStdErr($msg)
  156. {
  157. file_put_contents('php://stderr', $msg);
  158. }//protected static function toStdErr($msg)
  159. /**
  160. * Displays generic help to stdout
  161. *
  162. * @return void
  163. */
  164. protected function doHelp()
  165. {
  166. self::toStdErr(
  167. <<<EOH
  168. Usage: mdb2_schematool mode parameters
  169. Works with database schemas
  170. mode: (- and -- are optional)
  171. h, help Show this help screen
  172. d, dump Dump a schema to stdout
  173. l, load Load a schema into database
  174. i, diff Create a diff between two schemas and dump it to stdout
  175. a, apply Apply a diff to a database
  176. n, init Initialize a database with data
  177. EOH
  178. );
  179. }//protected function doHelp()
  180. /**
  181. * Displays the help screen for "dump" command
  182. *
  183. * @return void
  184. */
  185. protected function doHelpDump()
  186. {
  187. self::toStdErr(
  188. <<<EOH
  189. Usage: mdb2_schematool dump [all|data|schema] [-p] DSN
  190. Dumps a database schema to stdout
  191. If dump type is not specified, defaults to "schema".
  192. DSN: Data source name in the form of
  193. driver://user:password@host/database
  194. User and password may be omitted.
  195. Using -p reads password from stdin which is more secure than passing it in the
  196. parameter.
  197. EOH
  198. );
  199. }//protected function doHelpDump()
  200. /**
  201. * Displays the help screen for "init" command
  202. *
  203. * @return void
  204. */
  205. protected function doHelpInit()
  206. {
  207. self::toStdErr(
  208. <<<EOH
  209. Usage: mdb2_schematool init source [-p] destination
  210. Initializes a database with data
  211. (Inserts data on a previous created database at destination)
  212. source should be a schema file containing data,
  213. destination should be a DSN
  214. DSN: Data source name in the form of
  215. driver://user:password@host/database
  216. User and password may be omitted.
  217. Using -p reads password from stdin which is more secure than passing it in the
  218. parameter.
  219. EOH
  220. );
  221. }//protected function doHelpInit()
  222. /**
  223. * Displays the help screen for "load" command
  224. *
  225. * @return void
  226. */
  227. protected function doHelpLoad()
  228. {
  229. self::toStdErr(
  230. <<<EOH
  231. Usage: mdb2_schematool load [-p] source [-p] destination
  232. Loads a database schema from source to destination
  233. (Creates the database schema at destination)
  234. source can be a DSN or a schema file,
  235. destination should be a DSN
  236. DSN: Data source name in the form of
  237. driver://user:password@host/database
  238. User and password may be omitted.
  239. Using -p reads password from stdin which is more secure than passing it in the
  240. parameter.
  241. EOH
  242. );
  243. }//protected function doHelpLoad()
  244. /**
  245. * Returns an array of options for MDB2_Schema constructor
  246. *
  247. * @return array Options for MDB2_Schema constructor
  248. */
  249. protected function getSchemaOptions()
  250. {
  251. $options = array(
  252. 'log_line_break' => '<br>',
  253. 'idxname_format' => '%s',
  254. 'debug' => true,
  255. 'quote_identifier' => true,
  256. 'force_defaults' => false,
  257. 'portability' => true,
  258. 'use_transactions' => false,
  259. );
  260. return $options;
  261. }//protected function getSchemaOptions()
  262. /**
  263. * Checks if the passed parameter is a PEAR_Error object
  264. * and throws an exception in that case.
  265. *
  266. * @param mixed $object Some variable to check
  267. * @param string $location Where the error occured
  268. *
  269. * @return void
  270. */
  271. protected function throwExceptionOnError($object, $location = '')
  272. {
  273. if (PEAR::isError($object)) {
  274. //FIXME: exception class
  275. //debug_print_backtrace();
  276. throw new Exception('Error ' . $location
  277. . "\n" . $object->getMessage()
  278. . "\n" . $object->getUserInfo()
  279. );
  280. }
  281. }//protected function throwExceptionOnError($object, $location = '')
  282. /**
  283. * Loads a file or a dsn from the arguments
  284. *
  285. * @param array &$args Array of arguments to the program
  286. *
  287. * @return array Array of ('file'|'dsn', $value)
  288. */
  289. protected function getFileOrDsn(&$args)
  290. {
  291. if (count($args) == 0) {
  292. throw new MDB2_Schema_Tool_ParameterException(
  293. 'File or DSN expected'
  294. );
  295. }
  296. $arg = array_shift($args);
  297. if ($arg == '-p') {
  298. $bAskPassword = true;
  299. $arg = array_shift($args);
  300. } else {
  301. $bAskPassword = false;
  302. }
  303. if (strpos($arg, '://') === false) {
  304. if (file_exists($arg)) {
  305. //File
  306. return array('file', $arg);
  307. } else {
  308. throw new Exception('Schema file does not exist');
  309. }
  310. }
  311. //read password if necessary
  312. if ($bAskPassword) {
  313. $password = $this->readPasswordFromStdin($arg);
  314. $arg = self::setPasswordIntoDsn($arg, $password);
  315. self::toStdErr($arg);
  316. }
  317. return array('dsn', $arg);
  318. }//protected function getFileOrDsn(&$args)
  319. /**
  320. * Takes a DSN data source name and integrates the given
  321. * password into it.
  322. *
  323. * @param string $dsn Data source name
  324. * @param string $password Password
  325. *
  326. * @return string DSN with password
  327. */
  328. protected function setPasswordIntoDsn($dsn, $password)
  329. {
  330. //simple try to integrate password
  331. if (strpos($dsn, '@') === false) {
  332. //no @ -> no user and no password
  333. return str_replace('://', '://:' . $password . '@', $dsn);
  334. } else if (preg_match('|://[^:]+@|', $dsn)) {
  335. //user only, no password
  336. return str_replace('@', ':' . $password . '@', $dsn);
  337. } else if (strpos($dsn, ':@') !== false) {
  338. //abstract version
  339. return str_replace(':@', ':' . $password . '@', $dsn);
  340. }
  341. return $dsn;
  342. }//protected function setPasswordIntoDsn($dsn, $password)
  343. /**
  344. * Reads a password from stdin
  345. *
  346. * @param string $dsn DSN name to put into the message
  347. *
  348. * @return string Password
  349. */
  350. protected function readPasswordFromStdin($dsn)
  351. {
  352. $stdin = fopen('php://stdin', 'r');
  353. self::toStdErr('Please insert password for ' . $dsn . "\n");
  354. $password = '';
  355. $breakme = false;
  356. while (false !== ($char = fgetc($stdin))) {
  357. if (ord($char) == 10 || $char == "\n" || $char == "\r") {
  358. break;
  359. }
  360. $password .= $char;
  361. }
  362. fclose($stdin);
  363. return trim($password);
  364. }//protected function readPasswordFromStdin()
  365. /**
  366. * Creates a database schema dump and sends it to stdout
  367. *
  368. * @param array $args Command line arguments
  369. *
  370. * @return void
  371. */
  372. protected function doDump($args)
  373. {
  374. $dump_what = MDB2_SCHEMA_DUMP_STRUCTURE;
  375. $arg = '';
  376. if (count($args)) {
  377. $arg = $args[0];
  378. }
  379. switch (strtolower($arg)) {
  380. case 'all':
  381. $dump_what = MDB2_SCHEMA_DUMP_ALL;
  382. array_shift($args);
  383. break;
  384. case 'data':
  385. $dump_what = MDB2_SCHEMA_DUMP_CONTENT;
  386. array_shift($args);
  387. break;
  388. case 'schema':
  389. array_shift($args);
  390. }
  391. list($type, $dsn) = $this->getFileOrDsn($args);
  392. if ($type == 'file') {
  393. throw new MDB2_Schema_Tool_ParameterException(
  394. 'Dumping a schema file as a schema file does not make much ' .
  395. 'sense'
  396. );
  397. }
  398. $schema = MDB2_Schema::factory($dsn, $this->getSchemaOptions());
  399. $this->throwExceptionOnError($schema);
  400. $definition = $schema->getDefinitionFromDatabase();
  401. $this->throwExceptionOnError($definition);
  402. $dump_options = array(
  403. 'output_mode' => 'file',
  404. 'output' => 'php://stdout',
  405. 'end_of_line' => "\r\n"
  406. );
  407. $op = $schema->dumpDatabase(
  408. $definition, $dump_options, $dump_what
  409. );
  410. $this->throwExceptionOnError($op);
  411. $schema->disconnect();
  412. }//protected function doDump($args)
  413. /**
  414. * Loads a database schema
  415. *
  416. * @param array $args Command line arguments
  417. *
  418. * @return void
  419. */
  420. protected function doLoad($args)
  421. {
  422. list($typeSource, $dsnSource) = $this->getFileOrDsn($args);
  423. list($typeDest, $dsnDest) = $this->getFileOrDsn($args);
  424. if ($typeDest == 'file') {
  425. throw new MDB2_Schema_Tool_ParameterException(
  426. 'A schema can only be loaded into a database, not a file'
  427. );
  428. }
  429. $schemaDest = MDB2_Schema::factory($dsnDest, $this->getSchemaOptions());
  430. $this->throwExceptionOnError($schemaDest);
  431. //load definition
  432. if ($typeSource == 'file') {
  433. $definition = $schemaDest->parseDatabaseDefinitionFile($dsnSource);
  434. $where = 'loading schema file';
  435. } else {
  436. $schemaSource = MDB2_Schema::factory(
  437. $dsnSource,
  438. $this->getSchemaOptions()
  439. );
  440. $this->throwExceptionOnError(
  441. $schemaSource,
  442. 'connecting to source database'
  443. );
  444. $definition = $schemaSource->getDefinitionFromDatabase();
  445. $where = 'loading definition from database';
  446. }
  447. $this->throwExceptionOnError($definition, $where);
  448. //create destination database from definition
  449. $simulate = false;
  450. $op = $schemaDest->createDatabase(
  451. $definition,
  452. array(),
  453. $simulate
  454. );
  455. $this->throwExceptionOnError($op, 'creating the database');
  456. }//protected function doLoad($args)
  457. /**
  458. * Initializes a database with data
  459. *
  460. * @param array $args Command line arguments
  461. *
  462. * @return void
  463. */
  464. protected function doInit($args)
  465. {
  466. list($typeSource, $dsnSource) = $this->getFileOrDsn($args);
  467. list($typeDest, $dsnDest) = $this->getFileOrDsn($args);
  468. if ($typeSource != 'file') {
  469. throw new MDB2_Schema_Tool_ParameterException(
  470. 'Data must come from a source file'
  471. );
  472. }
  473. if ($typeDest != 'dsn') {
  474. throw new MDB2_Schema_Tool_ParameterException(
  475. 'A schema can only be loaded into a database, not a file'
  476. );
  477. }
  478. $schemaDest = MDB2_Schema::factory($dsnDest, $this->getSchemaOptions());
  479. $this->throwExceptionOnError(
  480. $schemaDest,
  481. 'connecting to destination database'
  482. );
  483. $definition = $schemaDest->getDefinitionFromDatabase();
  484. $this->throwExceptionOnError(
  485. $definition,
  486. 'loading definition from database'
  487. );
  488. $op = $schemaDest->writeInitialization($dsnSource, $definition);
  489. $this->throwExceptionOnError($op, 'initializing database');
  490. }//protected function doInit($args)
  491. }//class MDB2_Schema_Tool