server/inc/MDB2/Schema/Tool.php

560 lines
16 KiB
PHP

<?php
/**
* PHP versions 4 and 5
*
* Copyright (c) 1998-2008 Manuel Lemos, Tomas V.V.Cox,
* Stig. S. Bakken, Lukas Smith, Igor Feghali
* All rights reserved.
*
* MDB2_Schema enables users to maintain RDBMS independant schema files
* in XML that can be used to manipulate both data and database schemas
* This LICENSE is in the BSD license style.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* Neither the name of Manuel Lemos, Tomas V.V.Cox, Stig. S. Bakken,
* Lukas Smith, Igor Feghali nor the names of his contributors may be
* used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
* WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* Author: Christian Weiske <cweiske@php.net>
* $Id: Tool.php,v 1.6 2008/12/13 00:26:07 clockwerx Exp $
*
* @category Database
* @package MDB2_Schema
* @author Christian Weiske <cweiske@php.net>
* @license BSD http://www.opensource.org/licenses/bsd-license.php
* @version CVS: $Id: Tool.php,v 1.6 2008/12/13 00:26:07 clockwerx Exp $
* @link http://pear.php.net/packages/MDB2_Schema
*/
require_once 'MDB2/Schema.php';
require_once 'MDB2/Schema/Tool/ParameterException.php';
/**
* Command line tool to work with database schemas
*
* Functionality:
* - dump a database schema to stdout
* - import schema into database
* - create a diff between two schemas
* - apply diff to database
*
* @category Database
* @package MDB2_Schema
* @author Christian Weiske <cweiske@php.net>
* @license BSD http://www.opensource.org/licenses/bsd-license.php
* @link http://pear.php.net/packages/MDB2_Schema
*/
class MDB2_Schema_Tool
{
/**
* Run the schema tool
*
* @param array $args Array of command line arguments
*/
public function __construct($args)
{
$strAction = $this->getAction($args);
try {
$this->{'do' . ucfirst($strAction)}($args);
} catch (MDB2_Schema_Tool_ParameterException $e) {
$this->{'doHelp' . ucfirst($strAction)}($e->getMessage());
}
}//public function __construct($args)
/**
* Runs the tool with command line arguments
*
* @return void
*/
public static function run()
{
$args = $GLOBALS['argv'];
array_shift($args);
try {
$tool = new MDB2_Schema_Tool($args);
} catch (Exception $e) {
self::toStdErr($e->getMessage() . "\n");
}
}//public static function run()
/**
* Reads the first parameter from the argument array and
* returns the action.
*
* @param array &$args Command line parameters
*
* @return string Action to execute
*/
protected function getAction(&$args)
{
if (count($args) == 0) {
return 'help';
}
$arg = array_shift($args);
switch ($arg) {
case 'h':
case 'help':
case '-h':
case '--help':
return 'help';
case 'd':
case 'dump':
case '-d':
case '--dump':
return 'dump';
case 'l':
case 'load':
case '-l':
case '--load':
return 'load';
case 'i':
case 'diff':
case '-i':
case '--diff':
return 'diff';
case 'a':
case 'apply':
case '-a':
case '--apply':
return 'apply';
case 'n':
case 'init':
case '-i':
case '--init':
return 'init';
default:
throw new MDB2_Schema_Tool_ParameterException("Unknown mode \"$arg\"");
}
}//protected function getAction(&$args)
/**
* Writes the message to stderr
*
* @param string $msg Message to print
*
* @return void
*/
protected static function toStdErr($msg)
{
file_put_contents('php://stderr', $msg);
}//protected static function toStdErr($msg)
/**
* Displays generic help to stdout
*
* @return void
*/
protected function doHelp()
{
self::toStdErr(<<<EOH
Usage: mdb2_schematool mode parameters
Works with database schemas
mode: (- and -- are optional)
h, help Show this help screen
d, dump Dump a schema to stdout
l, load Load a schema into database
i, diff Create a diff between two schemas and dump it to stdout
a, apply Apply a diff to a database
n, init Initialize a database with data
EOH
);
}//protected function doHelp()
/**
* Displays the help screen for "dump" command
*
* @return void
*/
protected function doHelpDump()
{
self::toStdErr( <<<EOH
Usage: mdb2_schematool dump [all|data|schema] [-p] DSN
Dumps a database schema to stdout
If dump type is not specified, defaults to "schema".
DSN: Data source name in the form of
driver://user:password@host/database
User and password may be omitted.
Using -p reads password from stdin which is more secure than passing it in the parameter.
EOH
);
}//protected function doHelpDump()
/**
* Displays the help screen for "init" command
*
* @return void
*/
protected function doHelpInit()
{
self::toStdErr( <<<EOH
Usage: mdb2_schematool init source [-p] destination
Initializes a database with data
(Inserts data on a previous created database at destination)
source should be a schema file containing data,
destination should be a DSN
DSN: Data source name in the form of
driver://user:password@host/database
User and password may be omitted.
Using -p reads password from stdin which is more secure than passing it in the parameter.
EOH
);
}//protected function doHelpInit()
/**
* Displays the help screen for "load" command
*
* @return void
*/
protected function doHelpLoad()
{
self::toStdErr( <<<EOH
Usage: mdb2_schematool load [-p] source [-p] destination
Loads a database schema from source to destination
(Creates the database schema at destination)
source can be a DSN or a schema file,
destination should be a DSN
DSN: Data source name in the form of
driver://user:password@host/database
User and password may be omitted.
Using -p reads password from stdin which is more secure than passing it in the parameter.
EOH
);
}//protected function doHelpLoad()
/**
* Returns an array of options for MDB2_Schema constructor
*
* @return array Options for MDB2_Schema constructor
*/
protected function getSchemaOptions()
{
$options = array(
'log_line_break' => '<br>',
'idxname_format' => '%s',
'debug' => true,
'quote_identifier' => true,
'force_defaults' => false,
'portability' => true,
'use_transactions' => false,
);
return $options;
}//protected function getSchemaOptions()
/**
* Checks if the passed parameter is a PEAR_Error object
* and throws an exception in that case.
*
* @param mixed $object Some variable to check
* @param string $location Where the error occured
*
* @return void
*/
protected function throwExceptionOnError($object, $location = '')
{
if (PEAR::isError($object)) {
//FIXME: exception class
//debug_print_backtrace();
throw new Exception('Error ' . $location
. "\n" . $object->getMessage()
. "\n" . $object->getUserInfo()
);
}
}//protected function throwExceptionOnError($object, $location = '')
/**
* Loads a file or a dsn from the arguments
*
* @param array &$args Array of arguments to the program
*
* @return array Array of ('file'|'dsn', $value)
*/
protected function getFileOrDsn(&$args)
{
if (count($args) == 0) {
throw new MDB2_Schema_Tool_ParameterException('File or DSN expected');
}
$arg = array_shift($args);
if ($arg == '-p') {
$bAskPassword = true;
$arg = array_shift($args);
} else {
$bAskPassword = false;
}
if (strpos($arg, '://') === false) {
if (file_exists($arg)) {
//File
return array('file', $arg);
} else {
throw new Exception('Schema file does not exist');
}
}
//read password if necessary
if ($bAskPassword) {
$password = $this->readPasswordFromStdin($arg);
$arg = self::setPasswordIntoDsn($arg, $password);
self::toStdErr($arg);
}
return array('dsn', $arg);
}//protected function getFileOrDsn(&$args)
/**
* Takes a DSN data source name and integrates the given
* password into it.
*
* @param string $dsn Data source name
* @param string $password Password
*
* @return string DSN with password
*/
protected function setPasswordIntoDsn($dsn, $password)
{
//simple try to integrate password
if (strpos($dsn, '@') === false) {
//no @ -> no user and no password
return str_replace('://', '://:' . $password . '@', $dsn);
} else if (preg_match('|://[^:]+@|', $dsn)) {
//user only, no password
return str_replace('@', ':' . $password . '@', $dsn);
} else if (strpos($dsn, ':@') !== false) {
//abstract version
return str_replace(':@', ':' . $password . '@', $dsn);
}
return $dsn;
}//protected function setPasswordIntoDsn($dsn, $password)
/**
* Reads a password from stdin
*
* @param string $dsn DSN name to put into the message
*
* @return string Password
*/
protected function readPasswordFromStdin($dsn)
{
$stdin = fopen('php://stdin', 'r');
self::toStdErr('Please insert password for ' . $dsn . "\n");
$password = '';
$breakme = false;
while (false !== ($char = fgetc($stdin))) {
if (ord($char) == 10 || $char == "\n" || $char == "\r") {
break;
}
$password .= $char;
}
fclose($stdin);
return trim($password);
}//protected function readPasswordFromStdin()
/**
* Creates a database schema dump and sends it to stdout
*
* @param array $args Command line arguments
*
* @return void
*/
protected function doDump($args)
{
$dump_what = MDB2_SCHEMA_DUMP_STRUCTURE;
$arg = '';
if (count($args)) {
$arg = $args[0];
}
switch (strtolower($arg)) {
case 'all':
$dump_what = MDB2_SCHEMA_DUMP_ALL;
array_shift($args);
break;
case 'data':
$dump_what = MDB2_SCHEMA_DUMP_CONTENT;
array_shift($args);
break;
case 'schema':
array_shift($args);
}
list($type, $dsn) = $this->getFileOrDsn($args);
if ($type == 'file') {
throw new MDB2_Schema_Tool_ParameterException(
'Dumping a schema file as a schema file does not make much sense'
);
}
$schema = MDB2_Schema::factory($dsn, $this->getSchemaOptions());
$this->throwExceptionOnError($schema);
$definition = $schema->getDefinitionFromDatabase();
$this->throwExceptionOnError($definition);
$dump_options = array(
'output_mode' => 'file',
'output' => 'php://stdout',
'end_of_line' => "\r\n"
);
$op = $schema->dumpDatabase(
$definition, $dump_options, $dump_what
);
$this->throwExceptionOnError($op);
$schema->disconnect();
}//protected function doDump($args)
/**
* Loads a database schema
*
* @param array $args Command line arguments
*
* @return void
*/
protected function doLoad($args)
{
list($typeSource, $dsnSource) = $this->getFileOrDsn($args);
list($typeDest, $dsnDest) = $this->getFileOrDsn($args);
if ($typeDest == 'file') {
throw new MDB2_Schema_Tool_ParameterException(
'A schema can only be loaded into a database, not a file'
);
}
$schemaDest = MDB2_Schema::factory($dsnDest, $this->getSchemaOptions());
$this->throwExceptionOnError($schemaDest);
//load definition
if ($typeSource == 'file') {
$definition = $schemaDest->parseDatabaseDefinitionFile($dsnSource);
$where = 'loading schema file';
} else {
$schemaSource = MDB2_Schema::factory($dsnSource, $this->getSchemaOptions());
$this->throwExceptionOnError($schemaSource, 'connecting to source database');
$definition = $schemaSource->getDefinitionFromDatabase();
$where = 'loading definition from database';
}
$this->throwExceptionOnError($definition, $where);
//create destination database from definition
$simulate = false;
$op = $schemaDest->createDatabase($definition, array(), $simulate);
$this->throwExceptionOnError($op, 'creating the database');
}//protected function doLoad($args)
/**
* Initializes a database with data
*
* @param array $args Command line arguments
*
* @return void
*/
protected function doInit($args)
{
list($typeSource, $dsnSource) = $this->getFileOrDsn($args);
list($typeDest, $dsnDest) = $this->getFileOrDsn($args);
if ($typeSource != 'file') {
throw new MDB2_Schema_Tool_ParameterException(
'Data must come from a source file'
);
}
if ($typeDest != 'dsn') {
throw new MDB2_Schema_Tool_ParameterException(
'A schema can only be loaded into a database, not a file'
);
}
$schemaDest = MDB2_Schema::factory($dsnDest, $this->getSchemaOptions());
$this->throwExceptionOnError($schemaDest, 'connecting to destination database');
$definition = $schemaDest->getDefinitionFromDatabase();
$this->throwExceptionOnError($definition, 'loading definition from database');
$op = $schemaDest->writeInitialization($dsnSource, $definition);
$this->throwExceptionOnError($op, 'initializing database');
}//protected function doInit($args)
}//class MDB2_Schema_Tool
?>