ahwelp 7 år sedan
incheckning
2de051a4e2

+ 17 - 0
composer.json

@@ -0,0 +1,17 @@
+{
+    "name": "ahwelp/ddlwrapper",
+    "description": "From Phinx, just the DDL",
+    "type": "library",
+    "authors": [
+        {
+            "name": "ahwelp",
+            "email": "ahwelp@ahwelp.com"
+        }
+    ],
+    "require": {},
+    "autoload": {
+        "psr-4": {
+            "DDLWrapper\\": "src/DDLWrapper"
+        }
+    }
+}

+ 183 - 0
src/DDLWrapper/Db/Adapter/AdapterFactory.php

@@ -0,0 +1,183 @@
+<?php
+/**
+ * Phinx
+ *
+ * (The MIT license)
+ * Copyright (c) 2015 Rob Morgan
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated * documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ *
+ * @package    Phinx
+ * @subpackage Phinx\Migration
+ */
+namespace DDLWrapper\Db\Adapter;
+
+use DDLWrapper\Db\Adapter\AdapterInterface;
+
+/**
+ * Adapter factory and registry.
+ *
+ * Used for registering adapters and creating instances of adapters.
+ *
+ * @author Woody Gilk <woody.gilk@gmail.com>
+ */
+class AdapterFactory
+{
+    /**
+     * @var AdapterFactory
+     */
+    protected static $instance;
+
+    /**
+     * Get the factory singleton instance.
+     *
+     * @return AdapterFactory
+     */
+    public static function instance()
+    {
+        if (!static::$instance) {
+            static::$instance = new static();
+        }
+        return static::$instance;
+    }
+
+    /**
+     * Class map of database adapters, indexed by PDO::ATTR_DRIVER_NAME.
+     *
+     * @var array
+     */
+    protected $adapters = array(
+        'mysql'  => 'Phinx\Db\Adapter\MysqlAdapter',
+        'pgsql'  => 'Phinx\Db\Adapter\PostgresAdapter',
+        'sqlite' => 'Phinx\Db\Adapter\SQLiteAdapter',
+        'sqlsrv' => 'Phinx\Db\Adapter\SqlServerAdapter',
+    );
+
+    /**
+     * Class map of adapters wrappers, indexed by name.
+     *
+     * @var array
+     */
+    protected $wrappers = array(
+        'prefix' => 'Phinx\Db\Adapter\TablePrefixAdapter',
+        'proxy'  => 'Phinx\Db\Adapter\ProxyAdapter',
+    );
+
+    /**
+     * Add or replace an adapter with a fully qualified class name.
+     *
+     * @throws \RuntimeException
+     * @param  string $name
+     * @param  string $class
+     * @return $this
+     */
+    public function registerAdapter($name, $class)
+    {
+        if (!is_subclass_of($class, 'Phinx\Db\Adapter\AdapterInterface')) {
+            throw new \RuntimeException(sprintf(
+                'Adapter class "%s" must implement Phinx\\Db\\Adapter\\AdapterInterface',
+                $class
+            ));
+        }
+        $this->adapters[$name] = $class;
+        return $this;
+    }
+
+    /**
+     * Get an adapter class by name.
+     *
+     * @throws \RuntimeException
+     * @param  string $name
+     * @return string
+     */
+    protected function getClass($name)
+    {
+        if (empty($this->adapters[$name])) {
+            throw new \RuntimeException(sprintf(
+                'Adapter "%s" has not been registered',
+                $name
+            ));
+        }
+        return $this->adapters[$name];
+    }
+
+    /**
+     * Get an adapter instance by name.
+     *
+     * @param  string $name
+     * @param  array  $options
+     * @return AdapterInterface
+     */
+    public function getAdapter($name, array $options)
+    {
+        $class = $this->getClass($name);
+        return new $class($options);
+    }
+
+    /**
+     * Add or replace a wrapper with a fully qualified class name.
+     *
+     * @throws \RuntimeException
+     * @param  string $name
+     * @param  string $class
+     * @return $this
+     */
+    public function registerWrapper($name, $class)
+    {
+        if (!is_subclass_of($class, 'Phinx\Db\Adapter\WrapperInterface')) {
+            throw new \RuntimeException(sprintf(
+                'Wrapper class "%s" must be implement Phinx\\Db\\Adapter\\WrapperInterface',
+                $class
+            ));
+        }
+        $this->wrappers[$name] = $class;
+        return $this;
+    }
+
+    /**
+     * Get a wrapper class by name.
+     *
+     * @throws \RuntimeException
+     * @param  string $name
+     * @return string
+     */
+    protected function getWrapperClass($name)
+    {
+        if (empty($this->wrappers[$name])) {
+            throw new \RuntimeException(sprintf(
+                'Wrapper "%s" has not been registered',
+                $name
+            ));
+        }
+        return $this->wrappers[$name];
+    }
+
+    /**
+     * Get a wrapper instance by name.
+     *
+     * @param  string $name
+     * @param  AdapterInterface $adapter
+     * @return AdapterInterface
+     */
+    public function getWrapper($name, AdapterInterface $adapter)
+    {
+        $class = $this->getWrapperClass($name);
+        return new $class($adapter);
+    }
+}

+ 391 - 0
src/DDLWrapper/Db/Adapter/AdapterInterface.php

@@ -0,0 +1,391 @@
+<?php
+/**
+ * Phinx
+ *
+ * (The MIT license)
+ * Copyright (c) 2015 Rob Morgan
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated * documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ *
+ * @package    Phinx
+ * @subpackage Phinx\Db\Adapter
+ */
+namespace DDLWrapper\Db\Adapter;
+
+use DDLWrapper\Db\Table;
+use DDLWrapper\Db\Table\Column;
+use DDLWrapper\Db\Table\Index;
+use DDLWrapper\Db\Table\ForeignKey;
+//use DDLWrapper\Migration\MigrationInterface;
+
+/**
+ * Adapter Interface.
+ *
+ * @author Rob Morgan <robbym@gmail.com>
+ */
+interface AdapterInterface
+{
+    const PHINX_TYPE_STRING         = 'string';
+    const PHINX_TYPE_CHAR           = 'char';
+    const PHINX_TYPE_TEXT           = 'text';
+    const PHINX_TYPE_INTEGER        = 'integer';
+    const PHINX_TYPE_BIG_INTEGER    = 'biginteger';
+    const PHINX_TYPE_FLOAT          = 'float';
+    const PHINX_TYPE_DECIMAL        = 'decimal';
+    const PHINX_TYPE_DATETIME       = 'datetime';
+    const PHINX_TYPE_TIMESTAMP      = 'timestamp';
+    const PHINX_TYPE_TIME           = 'time';
+    const PHINX_TYPE_DATE           = 'date';
+    const PHINX_TYPE_BINARY         = 'binary';
+    const PHINX_TYPE_VARBINARY      = 'varbinary';
+    const PHINX_TYPE_BLOB           = 'blob';
+    const PHINX_TYPE_BOOLEAN        = 'boolean';
+    const PHINX_TYPE_JSON           = 'json';
+    const PHINX_TYPE_JSONB          = 'jsonb';
+    const PHINX_TYPE_UUID           = 'uuid';
+    const PHINX_TYPE_FILESTREAM     = 'filestream';
+
+    // Geospatial database types
+    const PHINX_TYPE_GEOMETRY       = 'geometry';
+    const PHINX_TYPE_POINT          = 'point';
+    const PHINX_TYPE_LINESTRING     = 'linestring';
+    const PHINX_TYPE_POLYGON        = 'polygon';
+
+    // only for mysql so far
+    const PHINX_TYPE_ENUM           = 'enum';
+    const PHINX_TYPE_SET            = 'set';
+
+    /**
+     * Returns the adapter type.
+     *
+     * @return string
+     */
+    public function getAdapterType();
+
+    /**
+     * Initializes the database connection.
+     *
+     * @throws \RuntimeException When the requested database driver is not installed.
+     * @return void
+     */
+    public function connect();
+
+    /**
+     * Closes the database connection.
+     *
+     * @return void
+     */
+    public function disconnect();
+
+    /**
+     * Does the adapter support transactions?
+     *
+     * @return boolean
+     */
+    public function hasTransactions();
+
+    /**
+     * Begin a transaction.
+     *
+     * @return void
+     */
+    public function beginTransaction();
+
+    /**
+     * Commit a transaction.
+     *
+     * @return void
+     */
+    public function commitTransaction();
+
+    /**
+     * Rollback a transaction.
+     *
+     * @return void
+     */
+    public function rollbackTransaction();
+
+    /**
+     * Executes a SQL statement and returns the number of affected rows.
+     *
+     * @param string $sql SQL
+     * @return int
+     */
+    public function execute($sql);
+
+    /**
+     * Executes a SQL statement and returns the result as an array.
+     *
+     * @param string $sql SQL
+     * @return array
+     */
+    public function query($sql);
+
+    /**
+     * Executes a query and returns only one row as an array.
+     *
+     * @param string $sql SQL
+     * @return array
+     */
+    public function fetchRow($sql);
+
+    /**
+     * Executes a query and returns an array of rows.
+     *
+     * @param string $sql SQL
+     * @return array
+     */
+    public function fetchAll($sql);
+
+    /**
+     * Inserts data into a table.
+     *
+     * @param Table $table where to insert data
+     * @param array $row
+     * @return void
+     */
+    public function insert(Table $table, $row);
+
+    /**
+     * Quotes a table name for use in a query.
+     *
+     * @param string $tableName Table Name
+     * @return string
+     */
+    public function quoteTableName($tableName);
+
+    /**
+     * Quotes a column name for use in a query.
+     *
+     * @param string $columnName Table Name
+     * @return string
+     */
+    public function quoteColumnName($columnName);
+
+    /**
+     * Checks to see if a table exists.
+     *
+     * @param string $tableName Table Name
+     * @return boolean
+     */
+    public function hasTable($tableName);
+
+    /**
+     * Creates the specified database table.
+     *
+     * @param Table $table Table
+     * @return void
+     */
+    public function createTable(Table $table);
+
+    /**
+     * Renames the specified database table.
+     *
+     * @param string $tableName Table Name
+     * @param string $newName   New Name
+     * @return void
+     */
+    public function renameTable($tableName, $newName);
+
+    /**
+     * Drops the specified database table.
+     *
+     * @param string $tableName Table Name
+     * @return void
+     */
+    public function dropTable($tableName);
+
+    /**
+     * Returns table columns
+     *
+     * @param string $tableName Table Name
+     * @return Column[]
+     */
+    public function getColumns($tableName);
+
+    /**
+     * Checks to see if a column exists.
+     *
+     * @param string $tableName  Table Name
+     * @param string $columnName Column Name
+     * @return boolean
+     */
+    public function hasColumn($tableName, $columnName);
+
+    /**
+     * Adds the specified column to a database table.
+     *
+     * @param Table  $table  Table
+     * @param Column $column Column
+     * @return void
+     */
+    public function addColumn(Table $table, Column $column);
+
+    /**
+     * Renames the specified column.
+     *
+     * @param string $tableName Table Name
+     * @param string $columnName Column Name
+     * @param string $newColumnName New Column Name
+     * @return void
+     */
+    public function renameColumn($tableName, $columnName, $newColumnName);
+
+    /**
+     * Change a table column type.
+     *
+     * @param string $tableName  Table Name
+     * @param string $columnName Column Name
+     * @param Column $newColumn  New Column
+     * @return Table
+     */
+    public function changeColumn($tableName, $columnName, Column $newColumn);
+
+    /**
+     * Drops the specified column.
+     *
+     * @param string $tableName Table Name
+     * @param string $columnName Column Name
+     * @return void
+     */
+    public function dropColumn($tableName, $columnName);
+
+    /**
+     * Checks to see if an index exists.
+     *
+     * @param string $tableName Table Name
+     * @param mixed  $columns   Column(s)
+     * @return boolean
+     */
+    public function hasIndex($tableName, $columns);
+
+    /**
+     * Checks to see if an index specified by name exists.
+     *
+     * @param string $tableName Table Name
+     * @param string $indexName
+     * @return boolean
+     */
+    public function hasIndexByName($tableName, $indexName);
+
+    /**
+     * Adds the specified index to a database table.
+     *
+     * @param Table $table Table
+     * @param Index $index Index
+     * @return void
+     */
+    public function addIndex(Table $table, Index $index);
+
+    /**
+     * Drops the specified index from a database table.
+     *
+     * @param string $tableName
+     * @param mixed  $columns Column(s)
+     * @return void
+     */
+    public function dropIndex($tableName, $columns);
+
+    /**
+     * Drops the index specified by name from a database table.
+     *
+     * @param string $tableName
+     * @param string $indexName
+     * @return void
+     */
+    public function dropIndexByName($tableName, $indexName);
+
+    /**
+     * Checks to see if a foreign key exists.
+     *
+     * @param string   $tableName
+     * @param string[] $columns    Column(s)
+     * @param string   $constraint Constraint name
+     * @return boolean
+     */
+    public function hasForeignKey($tableName, $columns, $constraint = null);
+
+    /**
+     * Adds the specified foreign key to a database table.
+     *
+     * @param Table      $table
+     * @param ForeignKey $foreignKey
+     * @return void
+     */
+    public function addForeignKey(Table $table, ForeignKey $foreignKey);
+
+    /**
+     * Drops the specified foreign key from a database table.
+     *
+     * @param string   $tableName
+     * @param string[] $columns    Column(s)
+     * @param string   $constraint Constraint name
+     * @return void
+     */
+    public function dropForeignKey($tableName, $columns, $constraint = null);
+
+    /**
+     * Returns an array of the supported Phinx column types.
+     *
+     * @return array
+     */
+    public function getColumnTypes();
+
+    /**
+     * Checks that the given column is of a supported type.
+     *
+     * @param  Column $column
+     * @return boolean
+     */
+    public function isValidColumnType(Column $column);
+
+    /**
+     * Converts the Phinx logical type to the adapter's SQL type.
+     *
+     * @param string $type
+     * @param integer $limit
+     * @return string
+     */
+    public function getSqlType($type, $limit = null);
+
+    /**
+     * Creates a new database.
+     *
+     * @param string $name Database Name
+     * @param array $options Options
+     * @return void
+     */
+    public function createDatabase($name, $options = array());
+
+    /**
+     * Checks to see if a database exists.
+     *
+     * @param string $name Database Name
+     * @return boolean
+     */
+    public function hasDatabase($name);
+
+    /**
+     * Drops the specified database.
+     *
+     * @param string $name Database Name
+     * @return void
+     */
+    public function dropDatabase($name);
+}

+ 471 - 0
src/DDLWrapper/Db/Adapter/AdapterWrapper.php

@@ -0,0 +1,471 @@
+<?php
+/**
+ * Phinx
+ *
+ * (The MIT license)
+ * Copyright (c) 2015 Rob Morgan
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated * documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ *
+ * @package    Phinx
+ * @subpackage Phinx\Db\Adapter
+ */
+namespace DDLWrapper\Db\Adapter;
+
+use DDLWrapper\Db\Table;
+use DDLWrapper\Db\Table\Column;
+use DDLWrapper\Db\Table\Index;
+use DDLWrapper\Db\Table\ForeignKey;
+use DDLWrapper\Migration\MigrationInterface;
+
+/**
+ * Adapter Wrapper.
+ *
+ * Proxy commands through to another adapter, allowing modification of
+ * parameters during calls.
+ *
+ * @author Woody Gilk <woody.gilk@gmail.com>
+ */
+abstract class AdapterWrapper implements AdapterInterface, WrapperInterface
+{
+    /**
+     * @var AdapterInterface
+     */
+    protected $adapter;
+
+    /**
+     * {@inheritdoc}
+     */
+    public function __construct(AdapterInterface $adapter)
+    {
+        $this->setAdapter($adapter);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function setAdapter(AdapterInterface $adapter)
+    {
+        $this->adapter = $adapter;
+        return $this;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAdapter()
+    {
+        return $this->adapter;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function setOptions(array $options)
+    {
+        $this->adapter->setOptions($options);
+        return $this;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getOptions()
+    {
+        return $this->adapter->getOptions();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function hasOption($name)
+    {
+        return $this->adapter->hasOption($name);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getOption($name)
+    {
+        return $this->adapter->getOption($name);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function connect()
+    {
+        return $this->getAdapter()->connect();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function disconnect()
+    {
+        return $this->getAdapter()->disconnect();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function execute($sql)
+    {
+        return $this->getAdapter()->execute($sql);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function query($sql)
+    {
+        return $this->getAdapter()->query($sql);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function insert(Table $table, $row)
+    {
+        return $this->getAdapter()->insert($table, $row);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function fetchRow($sql)
+    {
+        return $this->getAdapter()->fetchRow($sql);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function fetchAll($sql)
+    {
+        return $this->getAdapter()->fetchAll($sql);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getVersions()
+    {
+        return $this->getAdapter()->getVersions();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getVersionLog()
+    {
+        return $this->getAdapter()->getVersionLog();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function migrated(MigrationInterface $migration, $direction, $startTime, $endTime)
+    {
+        $this->getAdapter()->migrated($migration, $direction, $startTime, $endTime);
+        return $this;
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function toggleBreakpoint(MigrationInterface $migration)
+    {
+        $this->getAdapter()->toggleBreakpoint($migration);
+        return $this;
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function resetAllBreakpoints()
+    {
+        return $this->getAdapter()->resetAllBreakpoints();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function hasSchemaTable()
+    {
+        return $this->getAdapter()->hasSchemaTable();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function createSchemaTable()
+    {
+        return $this->getAdapter()->createSchemaTable();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getColumnTypes()
+    {
+        return $this->getAdapter()->getColumnTypes();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function isValidColumnType(Column $column)
+    {
+        return $this->getAdapter()->isValidColumnType($column);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function hasTransactions()
+    {
+        return $this->getAdapter()->hasTransactions();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function beginTransaction()
+    {
+        return $this->getAdapter()->beginTransaction();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function commitTransaction()
+    {
+        return $this->getAdapter()->commitTransaction();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function rollbackTransaction()
+    {
+        return $this->getAdapter()->rollbackTransaction();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function quoteTableName($tableName)
+    {
+        return $this->getAdapter()->quoteTableName($tableName);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function quoteColumnName($columnName)
+    {
+        return $this->getAdapter()->quoteColumnName($columnName);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function hasTable($tableName)
+    {
+        return $this->getAdapter()->hasTable($tableName);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function createTable(Table $table)
+    {
+        return $this->getAdapter()->createTable($table);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function renameTable($tableName, $newTableName)
+    {
+        return $this->getAdapter()->renameTable($tableName, $newTableName);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function dropTable($tableName)
+    {
+        return $this->getAdapter()->dropTable($tableName);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getColumns($tableName)
+    {
+        return $this->getAdapter()->getColumns($tableName);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function hasColumn($tableName, $columnName)
+    {
+        return $this->getAdapter()->hasColumn($tableName, $columnName);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function addColumn(Table $table, Column $column)
+    {
+        return $this->getAdapter()->addColumn($table, $column);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function renameColumn($tableName, $columnName, $newColumnName)
+    {
+        return $this->getAdapter()->renameColumn($tableName, $columnName, $newColumnName);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function changeColumn($tableName, $columnName, Column $newColumn)
+    {
+        return $this->getAdapter()->changeColumn($tableName, $columnName, $newColumn);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function dropColumn($tableName, $columnName)
+    {
+        return $this->getAdapter()->dropColumn($tableName, $columnName);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function hasIndex($tableName, $columns)
+    {
+        return $this->getAdapter()->hasIndex($tableName, $columns);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function hasIndexByName($tableName, $indexName)
+    {
+        return $this->getAdapter()->hasIndexByName($tableName, $indexName);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function addIndex(Table $table, Index $index)
+    {
+        return $this->getAdapter()->addIndex($table, $index);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function dropIndex($tableName, $columns, $options = array())
+    {
+        return $this->getAdapter()->dropIndex($tableName, $columns, $options);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function dropIndexByName($tableName, $indexName)
+    {
+        return $this->getAdapter()->dropIndexByName($tableName, $indexName);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function hasForeignKey($tableName, $columns, $constraint = null)
+    {
+        return $this->getAdapter()->hasForeignKey($tableName, $columns, $constraint);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function addForeignKey(Table $table, ForeignKey $foreignKey)
+    {
+        return $this->getAdapter()->addForeignKey($table, $foreignKey);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function dropForeignKey($tableName, $columns, $constraint = null)
+    {
+        return $this->getAdapter()->dropForeignKey($tableName, $columns, $constraint);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getSqlType($type, $limit = null)
+    {
+        return $this->getAdapter()->getSqlType($type, $limit);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function createDatabase($name, $options = array())
+    {
+        return $this->getAdapter()->createDatabase($name, $options);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function hasDatabase($name)
+    {
+        return $this->getAdapter()->hasDatabase($name);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function dropDatabase($name)
+    {
+        return $this->getAdapter()->dropDatabase($name);
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function castToBool($value)
+    {
+        return $this->getAdapter()->castToBool($value);
+    }
+}

+ 1148 - 0
src/DDLWrapper/Db/Adapter/MysqlAdapter.php

@@ -0,0 +1,1148 @@
+<?php
+/**
+ * Phinx
+ *
+ * (The MIT license)
+ * Copyright (c) 2015 Rob Morgan
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated * documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ *
+ * @package    Phinx
+ * @subpackage Phinx\Db\Adapter
+ */
+namespace DDLWrapper\Db\Adapter;
+
+use DDLWrapper\Db\Table;
+use DDLWrapper\Db\Table\Column;
+use DDLWrapper\Db\Table\Index;
+use DDLWrapper\Db\Table\ForeignKey;
+
+/**
+ * Phinx MySQL Adapter.
+ *
+ * @author Rob Morgan <robbym@gmail.com>
+ */
+class MysqlAdapter extends PdoAdapter implements AdapterInterface
+{
+
+    protected $signedColumnTypes = array('integer' => true, 'biginteger' => true, 'float' => true, 'decimal' => true, 'boolean' => true);
+
+    const TEXT_TINY    = 255;
+    const TEXT_SMALL   = 255; /* deprecated, alias of TEXT_TINY */
+    const TEXT_REGULAR = 65535;
+    const TEXT_MEDIUM  = 16777215;
+    const TEXT_LONG    = 4294967295;
+
+    // According to https://dev.mysql.com/doc/refman/5.0/en/blob.html BLOB sizes are the same as TEXT
+    const BLOB_TINY    = 255;
+    const BLOB_SMALL   = 255; /* deprecated, alias of BLOB_TINY */
+    const BLOB_REGULAR = 65535;
+    const BLOB_MEDIUM  = 16777215;
+    const BLOB_LONG    = 4294967295;
+
+    const INT_TINY    = 255;
+    const INT_SMALL   = 65535;
+    const INT_MEDIUM  = 16777215;
+    const INT_REGULAR = 4294967295;
+    const INT_BIG     = 18446744073709551615;
+
+    const TYPE_YEAR   = 'year';
+
+    /**
+     * {@inheritdoc}
+     */
+    public function connect()
+    {
+        if (null === $this->connection) {
+            if (!class_exists('PDO') || !in_array('mysql', \PDO::getAvailableDrivers(), true)) {
+                // @codeCoverageIgnoreStart
+                throw new \RuntimeException('You need to enable the PDO_Mysql extension for Phinx to run properly.');
+                // @codeCoverageIgnoreEnd
+            }
+
+            $db = null;
+            $options = $this->getOptions();
+
+            $dsn = 'mysql:';
+
+            if (!empty($options['unix_socket'])) {
+                // use socket connection
+                $dsn .= 'unix_socket=' . $options['unix_socket'];
+            } else {
+                // use network connection
+                $dsn .= 'host=' . $options['host'];
+                if (!empty($options['port'])) {
+                    $dsn .= ';port=' . $options['port'];
+                }
+            }
+
+            $dsn .= ';dbname=' . $options['name'];
+
+            // charset support
+            if (!empty($options['charset'])) {
+                $dsn .= ';charset=' . $options['charset'];
+            }
+
+            $driverOptions = array(\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION);
+
+            // support arbitrary \PDO::MYSQL_ATTR_* driver options and pass them to PDO
+            // http://php.net/manual/en/ref.pdo-mysql.php#pdo-mysql.constants
+            foreach ($options as $key => $option) {
+                if (strpos($key, 'mysql_attr_') === 0) {
+                    $driverOptions[constant('\PDO::' . strtoupper($key))] = $option;
+                }
+            }
+
+            try {
+                $db = new \PDO($dsn, $options['user'], $options['pass'], $driverOptions);
+            } catch (\PDOException $exception) {
+                throw new \InvalidArgumentException(sprintf(
+                    'There was a problem connecting to the database: %s',
+                    $exception->getMessage()
+                ));
+            }
+
+            $this->setConnection($db);
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function disconnect()
+    {
+        $this->connection = null;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function hasTransactions()
+    {
+        return true;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function beginTransaction()
+    {
+        $this->execute('START TRANSACTION');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function commitTransaction()
+    {
+        $this->execute('COMMIT');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function rollbackTransaction()
+    {
+        $this->execute('ROLLBACK');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function quoteTableName($tableName)
+    {
+        return str_replace('.', '`.`', $this->quoteColumnName($tableName));
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function quoteColumnName($columnName)
+    {
+        return '`' . str_replace('`', '``', $columnName) . '`';
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function hasTable($tableName)
+    {
+        $options = $this->getOptions();
+
+        $exists = $this->fetchRow(sprintf(
+            "SELECT TABLE_NAME
+            FROM INFORMATION_SCHEMA.TABLES
+            WHERE TABLE_SCHEMA = '%s' AND TABLE_NAME = '%s'",
+            $options['name'], $tableName
+        ));
+
+        return !empty($exists);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function createTable(Table $table)
+    {
+        $this->startCommandTimer();
+
+        // This method is based on the MySQL docs here: http://dev.mysql.com/doc/refman/5.1/en/create-index.html
+        $defaultOptions = array(
+            'engine' => 'InnoDB',
+            'collation' => 'utf8_general_ci'
+        );
+        $options = array_merge($defaultOptions, $table->getOptions());
+
+        // Add the default primary key
+        $columns = $table->getPendingColumns();
+        if (!isset($options['id']) || (isset($options['id']) && $options['id'] === true)) {
+            $column = new Column();
+            $column->setName('id')
+                   ->setType('integer')
+                   ->setIdentity(true);
+
+            array_unshift($columns, $column);
+            $options['primary_key'] = 'id';
+
+        } elseif (isset($options['id']) && is_string($options['id'])) {
+            // Handle id => "field_name" to support AUTO_INCREMENT
+            $column = new Column();
+            $column->setName($options['id'])
+                   ->setType('integer')
+                   ->setIdentity(true);
+
+            array_unshift($columns, $column);
+            $options['primary_key'] = $options['id'];
+        }
+
+        // TODO - process table options like collation etc
+
+        // process table engine (default to InnoDB)
+        $optionsStr = 'ENGINE = InnoDB';
+        if (isset($options['engine'])) {
+            $optionsStr = sprintf('ENGINE = %s', $options['engine']);
+        }
+
+        // process table collation
+        if (isset($options['collation'])) {
+            $charset = explode('_', $options['collation']);
+            $optionsStr .= sprintf(' CHARACTER SET %s', $charset[0]);
+            $optionsStr .= sprintf(' COLLATE %s', $options['collation']);
+        }
+
+        // set the table comment
+        if (isset($options['comment'])) {
+            $optionsStr .= sprintf(" COMMENT=%s ", $this->getConnection()->quote($options['comment']));
+        }
+
+        $sql = 'CREATE TABLE ';
+        $sql .= $this->quoteTableName($table->getName()) . ' (';
+        foreach ($columns as $column) {
+            $sql .= $this->quoteColumnName($column->getName()) . ' ' . $this->getColumnSqlDefinition($column) . ', ';
+        }
+
+        // set the primary key(s)
+        if (isset($options['primary_key'])) {
+            $sql = rtrim($sql);
+            $sql .= ' PRIMARY KEY (';
+            if (is_string($options['primary_key'])) {       // handle primary_key => 'id'
+                $sql .= $this->quoteColumnName($options['primary_key']);
+            } elseif (is_array($options['primary_key'])) { // handle primary_key => array('tag_id', 'resource_id')
+                // PHP 5.4 will allow access of $this, so we can call quoteColumnName() directly in the
+                // anonymous function, but for now just hard-code the adapter quotes
+                $sql .= implode(
+                    ',',
+                    array_map(
+                        function ($v) {
+                            return '`' . $v . '`';
+                        },
+                        $options['primary_key']
+                    )
+                );
+            }
+            $sql .= ')';
+        } else {
+            $sql = substr(rtrim($sql), 0, -1);              // no primary keys
+        }
+
+        // set the indexes
+        $indexes = $table->getIndexes();
+        if (!empty($indexes)) {
+            foreach ($indexes as $index) {
+                $sql .= ', ' . $this->getIndexSqlDefinition($index);
+            }
+        }
+
+        // set the foreign keys
+        $foreignKeys = $table->getForeignKeys();
+        if (!empty($foreignKeys)) {
+            foreach ($foreignKeys as $foreignKey) {
+                $sql .= ', ' . $this->getForeignKeySqlDefinition($foreignKey);
+            }
+        }
+
+        $sql .= ') ' . $optionsStr;
+        $sql = rtrim($sql) . ';';
+
+        // execute the sql
+        //$this->writeCommand('createTable', array($table->getName()));
+        $this->execute($sql);
+        $this->endCommandTimer();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function renameTable($tableName, $newTableName)
+    {
+        $this->startCommandTimer();
+        //$this->writeCommand('renameTable', array($tableName, $newTableName));
+        $this->execute(sprintf('RENAME TABLE %s TO %s', $this->quoteTableName($tableName), $this->quoteTableName($newTableName)));
+        $this->endCommandTimer();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function dropTable($tableName)
+    {
+        $this->startCommandTimer();
+       // $this->writeCommand('dropTable', array($tableName));
+        $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($tableName)));
+        $this->endCommandTimer();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getColumns($tableName)
+    {
+        $columns = array();
+        $rows = $this->fetchAll(sprintf('SHOW COLUMNS FROM %s', $this->quoteTableName($tableName)));
+        foreach ($rows as $columnInfo) {
+
+            $phinxType = $this->getPhinxType($columnInfo['Type']);
+
+            $column = new Column();
+            $column->setName($columnInfo['Field'])
+                   ->setNull($columnInfo['Null'] !== 'NO')
+                   ->setDefault($columnInfo['Default'])
+                   ->setType($phinxType['name'])
+                   ->setLimit($phinxType['limit']);
+
+            if ($columnInfo['Extra'] === 'auto_increment') {
+                $column->setIdentity(true);
+            }
+
+            $columns[] = $column;
+        }
+
+        return $columns;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function hasColumn($tableName, $columnName)
+    {
+        $rows = $this->fetchAll(sprintf('SHOW COLUMNS FROM %s', $this->quoteTableName($tableName)));
+        foreach ($rows as $column) {
+            if (strcasecmp($column['Field'], $columnName) === 0) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Get the defintion for a `DEFAULT` statement.
+     *
+     * @param  mixed $default
+     * @return string
+     */
+    protected function getDefaultValueDefinition($default)
+    {
+        if (is_string($default) && 'CURRENT_TIMESTAMP' !== $default) {
+            $default = $this->getConnection()->quote($default);
+        } elseif (is_bool($default)) {
+            $default = $this->castToBool($default);
+        }
+        return isset($default) ? ' DEFAULT ' . $default : '';
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function addColumn(Table $table, Column $column)
+    {
+        $this->startCommandTimer();
+        $sql = sprintf(
+            'ALTER TABLE %s ADD %s %s',
+            $this->quoteTableName($table->getName()),
+            $this->quoteColumnName($column->getName()),
+            $this->getColumnSqlDefinition($column)
+        );
+
+        if ($column->getAfter()) {
+            $sql .= ' AFTER ' . $this->quoteColumnName($column->getAfter());
+        }
+
+        //$this->writeCommand('addColumn', array($table->getName(), $column->getName(), $column->getType()));
+        $this->execute($sql);
+        $this->endCommandTimer();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function renameColumn($tableName, $columnName, $newColumnName)
+    {
+        $this->startCommandTimer();
+        $rows = $this->fetchAll(sprintf('DESCRIBE %s', $this->quoteTableName($tableName)));
+        foreach ($rows as $row) {
+            if (strcasecmp($row['Field'], $columnName) === 0) {
+                $null = ($row['Null'] == 'NO') ? 'NOT NULL' : 'NULL';
+                $extra = ' ' . strtoupper($row['Extra']);
+                if (!is_null($row['Default'])) {
+                    $extra .= $this->getDefaultValueDefinition($row['Default']);
+                }
+                $definition = $row['Type'] . ' ' . $null . $extra;
+
+                //$this->writeCommand('renameColumn', array($tableName, $columnName, $newColumnName));
+                $this->execute(
+                    sprintf(
+                        'ALTER TABLE %s CHANGE COLUMN %s %s %s',
+                        $this->quoteTableName($tableName),
+                        $this->quoteColumnName($columnName),
+                        $this->quoteColumnName($newColumnName),
+                        $definition
+                    )
+                );
+                $this->endCommandTimer();
+                return;
+            }
+        }
+
+        throw new \InvalidArgumentException(sprintf(
+            'The specified column doesn\'t exist: '
+            . $columnName
+        ));
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function changeColumn($tableName, $columnName, Column $newColumn)
+    {
+        $this->startCommandTimer();
+        //$this->writeCommand('changeColumn', array($tableName, $columnName, $newColumn->getType()));
+        $after = $newColumn->getAfter() ? ' AFTER ' . $this->quoteColumnName($newColumn->getAfter()) : '';
+        $this->execute(
+            sprintf(
+                'ALTER TABLE %s CHANGE %s %s %s%s',
+                $this->quoteTableName($tableName),
+                $this->quoteColumnName($columnName),
+                $this->quoteColumnName($newColumn->getName()),
+                $this->getColumnSqlDefinition($newColumn),
+                $after
+            )
+        );
+        $this->endCommandTimer();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function dropColumn($tableName, $columnName)
+    {
+        $this->startCommandTimer();
+        //$this->writeCommand('dropColumn', array($tableName, $columnName));
+        $this->execute(
+            sprintf(
+                'ALTER TABLE %s DROP COLUMN %s',
+                $this->quoteTableName($tableName),
+                $this->quoteColumnName($columnName)
+            )
+        );
+        $this->endCommandTimer();
+    }
+
+    /**
+     * Get an array of indexes from a particular table.
+     *
+     * @param string $tableName Table Name
+     * @return array
+     */
+    protected function getIndexes($tableName)
+    {
+        $indexes = array();
+        $rows = $this->fetchAll(sprintf('SHOW INDEXES FROM %s', $this->quoteTableName($tableName)));
+        foreach ($rows as $row) {
+            if (!isset($indexes[$row['Key_name']])) {
+                $indexes[$row['Key_name']] = array('columns' => array());
+            }
+            $indexes[$row['Key_name']]['columns'][] = strtolower($row['Column_name']);
+        }
+        return $indexes;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function hasIndex($tableName, $columns)
+    {
+        if (is_string($columns)) {
+            $columns = array($columns); // str to array
+        }
+
+        $columns = array_map('strtolower', $columns);
+        $indexes = $this->getIndexes($tableName);
+
+        foreach ($indexes as $index) {
+            if ($columns == $index['columns']) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function hasIndexByName($tableName, $indexName)
+    {
+        $indexes = $this->getIndexes($tableName);
+
+        foreach ($indexes as $name => $index) {
+            if ($name === $indexName) {
+                 return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function addIndex(Table $table, Index $index)
+    {
+        $this->startCommandTimer();
+        //$this->writeCommand('addIndex', array($table->getName(), $index->getColumns()));
+        $this->execute(
+            sprintf(
+                'ALTER TABLE %s ADD %s',
+                $this->quoteTableName($table->getName()),
+                $this->getIndexSqlDefinition($index)
+            )
+        );
+        $this->endCommandTimer();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function dropIndex($tableName, $columns)
+    {
+        $this->startCommandTimer();
+        if (is_string($columns)) {
+            $columns = array($columns); // str to array
+        }
+
+        $this->writeCommand('dropIndex', array($tableName, $columns));
+        $indexes = $this->getIndexes($tableName);
+        $columns = array_map('strtolower', $columns);
+
+        foreach ($indexes as $indexName => $index) {
+            if ($columns == $index['columns']) {
+                $this->execute(
+                    sprintf(
+                        'ALTER TABLE %s DROP INDEX %s',
+                        $this->quoteTableName($tableName),
+                        $this->quoteColumnName($indexName)
+                    )
+                );
+                $this->endCommandTimer();
+                return;
+            }
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function dropIndexByName($tableName, $indexName)
+    {
+        $this->startCommandTimer();
+        $this->writeCommand('dropIndexByName', array($tableName, $indexName));
+        $indexes = $this->getIndexes($tableName);
+
+        foreach ($indexes as $name => $index) {
+            //$a = array_diff($columns, $index['columns']);
+            if ($name === $indexName) {
+                $this->execute(
+                    sprintf(
+                        'ALTER TABLE %s DROP INDEX %s',
+                        $this->quoteTableName($tableName),
+                        $this->quoteColumnName($indexName)
+                    )
+                );
+                $this->endCommandTimer();
+                return;
+            }
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function hasForeignKey($tableName, $columns, $constraint = null)
+    {
+        if (is_string($columns)) {
+            $columns = array($columns); // str to array
+        }
+        $foreignKeys = $this->getForeignKeys($tableName);
+        if ($constraint) {
+            if (isset($foreignKeys[$constraint])) {
+                return !empty($foreignKeys[$constraint]);
+            }
+            return false;
+        } else {
+            foreach ($foreignKeys as $key) {
+                $a = array_diff($columns, $key['columns']);
+                if ($columns == $key['columns']) {
+                    return true;
+                }
+            }
+            return false;
+        }
+    }
+
+    /**
+     * Get an array of foreign keys from a particular table.
+     *
+     * @param string $tableName Table Name
+     * @return array
+     */
+    protected function getForeignKeys($tableName)
+    {
+        $foreignKeys = array();
+        $rows = $this->fetchAll(sprintf(
+            "SELECT
+              CONSTRAINT_NAME,
+              TABLE_NAME,
+              COLUMN_NAME,
+              REFERENCED_TABLE_NAME,
+              REFERENCED_COLUMN_NAME
+            FROM information_schema.KEY_COLUMN_USAGE
+            WHERE REFERENCED_TABLE_SCHEMA = DATABASE()
+              AND REFERENCED_TABLE_NAME IS NOT NULL
+              AND TABLE_NAME = '%s'
+            ORDER BY POSITION_IN_UNIQUE_CONSTRAINT",
+            $tableName
+        ));
+        foreach ($rows as $row) {
+            $foreignKeys[$row['CONSTRAINT_NAME']]['table'] = $row['TABLE_NAME'];
+            $foreignKeys[$row['CONSTRAINT_NAME']]['columns'][] = $row['COLUMN_NAME'];
+            $foreignKeys[$row['CONSTRAINT_NAME']]['referenced_table'] = $row['REFERENCED_TABLE_NAME'];
+            $foreignKeys[$row['CONSTRAINT_NAME']]['referenced_columns'][] = $row['REFERENCED_COLUMN_NAME'];
+        }
+        return $foreignKeys;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function addForeignKey(Table $table, ForeignKey $foreignKey)
+    {
+        $this->startCommandTimer();
+        $this->writeCommand('addForeignKey', array($table->getName(), $foreignKey->getColumns()));
+        $this->execute(
+            sprintf(
+                'ALTER TABLE %s ADD %s',
+                $this->quoteTableName($table->getName()),
+                $this->getForeignKeySqlDefinition($foreignKey)
+            )
+        );
+        $this->endCommandTimer();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function dropForeignKey($tableName, $columns, $constraint = null)
+    {
+        $this->startCommandTimer();
+        if (is_string($columns)) {
+            $columns = array($columns); // str to array
+        }
+
+        $this->writeCommand('dropForeignKey', array($tableName, $columns));
+
+        if ($constraint) {
+            $this->execute(
+                sprintf(
+                    'ALTER TABLE %s DROP FOREIGN KEY %s',
+                    $this->quoteTableName($tableName),
+                    $constraint
+                )
+            );
+            $this->endCommandTimer();
+            return;
+        } else {
+            foreach ($columns as $column) {
+                $rows = $this->fetchAll(sprintf(
+                    "SELECT
+                        CONSTRAINT_NAME
+                      FROM information_schema.KEY_COLUMN_USAGE
+                      WHERE REFERENCED_TABLE_SCHEMA = DATABASE()
+                        AND REFERENCED_TABLE_NAME IS NOT NULL
+                        AND TABLE_NAME = '%s'
+                        AND COLUMN_NAME = '%s'
+                      ORDER BY POSITION_IN_UNIQUE_CONSTRAINT",
+                    $tableName,
+                    $column
+                ));
+                foreach ($rows as $row) {
+                    $this->dropForeignKey($tableName, $columns, $row['CONSTRAINT_NAME']);
+                }
+            }
+        }
+        $this->endCommandTimer();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getSqlType($type, $limit = null)
+    {
+        switch ($type) {
+            case static::PHINX_TYPE_STRING:
+                return array('name' => 'varchar', 'limit' => $limit ? $limit : 255);
+                break;
+            case static::PHINX_TYPE_CHAR:
+                return array('name' => 'char', 'limit' => $limit ? $limit : 255);
+                break;
+            case static::PHINX_TYPE_TEXT:
+                if ($limit) {
+                    $sizes = array(
+                        // Order matters! Size must always be tested from longest to shortest!
+                        'longtext'   => static::TEXT_LONG,
+                        'mediumtext' => static::TEXT_MEDIUM,
+                        'text'       => static::TEXT_REGULAR,
+                        'tinytext'   => static::TEXT_SMALL,
+                    );
+                    foreach ($sizes as $name => $length) {
+                        if ($limit >= $length) {
+                            return array('name' => $name);
+                        }
+                    }
+                }
+                return array('name' => 'text');
+                break;
+            case static::PHINX_TYPE_BINARY:
+                return array('name' => 'binary', 'limit' => $limit ? $limit : 255);
+                break;
+            case static::PHINX_TYPE_VARBINARY:
+                return array('name' => 'varbinary', 'limit' => $limit ? $limit : 255);
+                break;
+            case static::PHINX_TYPE_BLOB:
+                if ($limit) {
+                    $sizes = array(
+                        // Order matters! Size must always be tested from longest to shortest!
+                        'longblob'   => static::BLOB_LONG,
+                        'mediumblob' => static::BLOB_MEDIUM,
+                        'blob'       => static::BLOB_REGULAR,
+                        'tinyblob'   => static::BLOB_SMALL,
+                    );
+                    foreach ($sizes as $name => $length) {
+                        if ($limit >= $length) {
+                            return array('name' => $name);
+                        }
+                    }
+                }
+                return array('name' => 'blob');
+                break;
+            case static::PHINX_TYPE_INTEGER:
+                if ($limit && $limit >= static::INT_TINY) {
+                    $sizes = array(
+                        // Order matters! Size must always be tested from longest to shortest!
+                        'bigint'    => static::INT_BIG,
+                        'int'       => static::INT_REGULAR,
+                        'mediumint' => static::INT_MEDIUM,
+                        'smallint'  => static::INT_SMALL,
+                        'tinyint'   => static::INT_TINY,
+                    );
+                    $limits = array(
+                        'int'    => 11,
+                        'bigint' => 20,
+                    );
+                    foreach ($sizes as $name => $length) {
+                        if ($limit >= $length) {
+                            $def = array('name' => $name);
+                            if (isset($limits[$name])) {
+                                $def['limit'] = $limits[$name];
+                            }
+                            return $def;
+                        }
+                    }
+                } elseif (!$limit) {
+                    $limit = 11;
+                }
+                return array('name' => 'int', 'limit' => $limit);
+                break;
+            case static::PHINX_TYPE_BIG_INTEGER:
+                return array('name' => 'bigint', 'limit' => 20);
+                break;
+            case static::PHINX_TYPE_FLOAT:
+                return array('name' => 'float');
+                break;
+            case static::PHINX_TYPE_DECIMAL:
+                return array('name' => 'decimal');
+                break;
+            case static::PHINX_TYPE_DATETIME:
+                return array('name' => 'datetime');
+                break;
+            case static::PHINX_TYPE_TIMESTAMP:
+                return array('name' => 'timestamp');
+                break;
+            case static::PHINX_TYPE_TIME:
+                return array('name' => 'time');
+                break;
+            case static::PHINX_TYPE_DATE:
+                return array('name' => 'date');
+                break;
+            case static::PHINX_TYPE_BOOLEAN:
+                return array('name' => 'tinyint', 'limit' => 1);
+                break;
+            case static::PHINX_TYPE_UUID:
+                return array('name' => 'char', 'limit' => 36);
+            // Geospatial database types
+            case static::PHINX_TYPE_GEOMETRY:
+            case static::PHINX_TYPE_POINT:
+            case static::PHINX_TYPE_LINESTRING:
+            case static::PHINX_TYPE_POLYGON:
+                return array('name' => $type);
+            case static::PHINX_TYPE_ENUM:
+                return array('name' => 'enum');
+                break;
+            case static::PHINX_TYPE_SET:
+                return array('name' => 'set');
+                break;
+            case static::TYPE_YEAR:
+                if (!$limit || in_array($limit, array(2, 4)))
+                    $limit = 4;
+                return array('name' => 'year', 'limit' => $limit);
+                break;
+            case static::PHINX_TYPE_JSON:
+                return array('name' => 'json');
+                break;
+            default:
+                throw new \RuntimeException('The type: "' . $type . '" is not supported.');
+        }
+    }
+
+    /**
+     * Returns Phinx type by SQL type
+     *
+     * @param string $sqlTypeDef
+     * @throws \RuntimeException
+     * @internal param string $sqlType SQL type
+     * @returns string Phinx type
+     */
+    public function getPhinxType($sqlTypeDef)
+    {
+        if (!preg_match('/^([\w]+)(\(([\d]+)*(,([\d]+))*\))*(.+)*$/', $sqlTypeDef, $matches)) {
+            throw new \RuntimeException('Column type ' . $sqlTypeDef . ' is not supported');
+        } else {
+            $limit = null;
+            $precision = null;
+            $type = $matches[1];
+            if (count($matches) > 2) {
+                $limit = $matches[3] ? (int) $matches[3] : null;
+            }
+            if (count($matches) > 4) {
+                $precision = (int) $matches[5];
+            }
+            if ($type === 'tinyint' && $limit === 1) {
+                $type = static::PHINX_TYPE_BOOLEAN;
+                $limit = null;
+            }
+            switch ($type) {
+                case 'varchar':
+                    $type = static::PHINX_TYPE_STRING;
+                    if ($limit === 255) {
+                        $limit = null;
+                    }
+                    break;
+                case 'char':
+                    $type = static::PHINX_TYPE_CHAR;
+                    if ($limit === 255) {
+                        $limit = null;
+                    }
+                    if ($limit === 36) {
+                        $type = static::PHINX_TYPE_UUID;
+                    }
+                    break;
+                case 'tinyint':
+                    $type  = static::PHINX_TYPE_INTEGER;
+                    $limit = static::INT_TINY;
+                    break;
+                case 'smallint':
+                    $type  = static::PHINX_TYPE_INTEGER;
+                    $limit = static::INT_SMALL;
+                    break;
+                case 'mediumint':
+                    $type  = static::PHINX_TYPE_INTEGER;
+                    $limit = static::INT_MEDIUM;
+                    break;
+                case 'int':
+                    $type = static::PHINX_TYPE_INTEGER;
+                    if ($limit === 11) {
+                        $limit = null;
+                    }
+                    break;
+                case 'bigint':
+                    if ($limit === 20) {
+                        $limit = null;
+                    }
+                    $type = static::PHINX_TYPE_BIG_INTEGER;
+                    break;
+                case 'blob':
+                    $type = static::PHINX_TYPE_BINARY;
+                    break;
+                case 'tinyblob':
+                    $type  = static::PHINX_TYPE_BINARY;
+                    $limit = static::BLOB_TINY;
+                    break;
+                case 'mediumblob':
+                    $type  = static::PHINX_TYPE_BINARY;
+                    $limit = static::BLOB_MEDIUM;
+                    break;
+                case 'longblob':
+                    $type  = static::PHINX_TYPE_BINARY;
+                    $limit = static::BLOB_LONG;
+                    break;
+                case 'tinytext':
+                    $type  = static::PHINX_TYPE_TEXT;
+                    $limit = static::TEXT_TINY;
+                    break;
+                case 'mediumtext':
+                    $type  = static::PHINX_TYPE_TEXT;
+                    $limit = static::TEXT_MEDIUM;
+                    break;
+                case 'longtext':
+                    $type  = static::PHINX_TYPE_TEXT;
+                    $limit = static::TEXT_LONG;
+                    break;
+            }
+
+            $this->getSqlType($type, $limit);
+
+            return array(
+                'name' => $type,
+                'limit' => $limit,
+                'precision' => $precision
+            );
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function createDatabase($name, $options = array())
+    {
+        $this->startCommandTimer();
+        $this->writeCommand('createDatabase', array($name));
+        $charset = isset($options['charset']) ? $options['charset'] : 'utf8';
+
+        if (isset($options['collation'])) {
+            $this->execute(sprintf('CREATE DATABASE `%s` DEFAULT CHARACTER SET `%s` COLLATE `%s`', $name, $charset, $options['collation']));
+        } else {
+            $this->execute(sprintf('CREATE DATABASE `%s` DEFAULT CHARACTER SET `%s`', $name, $charset));
+        }
+        $this->endCommandTimer();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function hasDatabase($name)
+    {
+        $rows = $this->fetchAll(
+            sprintf(
+                'SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = \'%s\'',
+                $name
+            )
+        );
+
+        foreach ($rows as $row) {
+            if (!empty($row)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function dropDatabase($name)
+    {
+        $this->startCommandTimer();
+        $this->writeCommand('dropDatabase', array($name));
+        $this->execute(sprintf('DROP DATABASE IF EXISTS `%s`', $name));
+        $this->endCommandTimer();
+    }
+
+    /**
+     * Gets the MySQL Column Definition for a Column object.
+     *
+     * @param Column $column Column
+     * @return string
+     */
+    protected function getColumnSqlDefinition(Column $column)
+    {
+        $sqlType = $this->getSqlType($column->getType(), $column->getLimit());
+
+        $def = '';
+        $def .= strtoupper($sqlType['name']);
+        if ($column->getPrecision() && $column->getScale()) {
+            $def .= '(' . $column->getPrecision() . ',' . $column->getScale() . ')';
+        } elseif (isset($sqlType['limit'])) {
+            $def .= '(' . $sqlType['limit'] . ')';
+        }
+        if (($values = $column->getValues()) && is_array($values)) {
+            $def .= "('" . implode("', '", $values) . "')";
+        }
+        $def .= (!$column->isSigned() && isset($this->signedColumnTypes[$column->getType()])) ? ' unsigned' : '' ;
+        $def .= ($column->isNull() == false) ? ' NOT NULL' : ' NULL';
+        $def .= ($column->isIdentity()) ? ' AUTO_INCREMENT' : '';
+        $def .= $this->getDefaultValueDefinition($column->getDefault());
+
+        if ($column->getComment()) {
+            $def .= ' COMMENT ' . $this->getConnection()->quote($column->getComment());
+        }
+
+        if ($column->getUpdate()) {
+            $def .= ' ON UPDATE ' . $column->getUpdate();
+        }
+
+        return $def;
+    }
+
+    /**
+     * Gets the MySQL Index Definition for an Index object.
+     *
+     * @param Index $index Index
+     * @return string
+     */
+    protected function getIndexSqlDefinition(Index $index)
+    {
+        $def = '';
+        $limit = '';
+        if ($index->getLimit()) {
+            $limit = '(' . $index->getLimit() . ')';
+        }
+
+        if ($index->getType() == Index::UNIQUE) {
+            $def .= ' UNIQUE';
+        }
+
+        if ($index->getType() == Index::FULLTEXT) {
+            $def .= ' FULLTEXT';
+        }
+
+        $def .= ' KEY';
+
+        if (is_string($index->getName())) {
+            $def .= ' `' . $index->getName() . '`';
+        }
+
+        $def .= ' (`' . implode('`,`', $index->getColumns()) . '`' . $limit . ')';
+
+        return $def;
+    }
+
+    /**
+     * Gets the MySQL Foreign Key Definition for an ForeignKey object.
+     *
+     * @param ForeignKey $foreignKey
+     * @return string
+     */
+    protected function getForeignKeySqlDefinition(ForeignKey $foreignKey)
+    {
+        $def = '';
+        if ($foreignKey->getConstraint()) {
+            $def .= ' CONSTRAINT ' . $this->quoteColumnName($foreignKey->getConstraint());
+        }
+        $columnNames = array();
+        foreach ($foreignKey->getColumns() as $column) {
+            $columnNames[] = $this->quoteColumnName($column);
+        }
+        $def .= ' FOREIGN KEY (' . implode(',', $columnNames) . ')';
+        $refColumnNames = array();
+        foreach ($foreignKey->getReferencedColumns() as $column) {
+            $refColumnNames[] = $this->quoteColumnName($column);
+        }
+        $def .= ' REFERENCES ' . $this->quoteTableName($foreignKey->getReferencedTable()->getName()) . ' (' . implode(',', $refColumnNames) . ')';
+        if ($foreignKey->getOnDelete()) {
+            $def .= ' ON DELETE ' . $foreignKey->getOnDelete();
+        }
+        if ($foreignKey->getOnUpdate()) {
+            $def .= ' ON UPDATE ' . $foreignKey->getOnUpdate();
+        }
+        return $def;
+    }
+
+    /**
+     * Describes a database table. This is a MySQL adapter specific method.
+     *
+     * @param string $tableName Table name
+     * @return array
+     */
+    public function describeTable($tableName)
+    {
+        $options = $this->getOptions();
+
+        // mysql specific
+        $sql = sprintf(
+            "SELECT *
+             FROM information_schema.tables
+             WHERE table_schema = '%s'
+             AND table_name = '%s'",
+            $options['name'],
+            $tableName
+        );
+
+        return $this->fetchRow($sql);
+    }
+
+    /**
+     * Returns MySQL column types (inherited and MySQL specified).
+     * @return array
+     */
+    public function getColumnTypes()
+    {
+        return array_merge(parent::getColumnTypes(), array ('enum', 'set', 'year', 'json'));
+    }
+}

+ 302 - 0
src/DDLWrapper/Db/Adapter/PdoAdapter.php

@@ -0,0 +1,302 @@
+<?php
+/**
+ * Phinx
+ *
+ * (The MIT license)
+ * Copyright (c) 2015 Rob Morgan
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated * documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ *
+ * @package    Phinx
+ * @subpackage Phinx\Db\Adapter
+ */
+namespace DDLWrapper\Db\Adapter;
+
+use DDLWrapper\Db\Table;
+use DDLWrapper\Db\Table\Column;
+
+/**
+ * Phinx PDO Adapter.
+ *
+ * @author Rob Morgan <robbym@gmail.com>
+ */
+abstract class PdoAdapter implements AdapterInterface
+{
+
+    /**
+     * @var \PDO
+     */
+    protected $connection;
+
+    /**
+     * @var float
+     */
+    protected $commandStartTime;
+
+    /**
+     * Class Constructor.
+     *
+     * @param array $options Options
+     * @param InputInterface $input Input Interface
+     * @param OutputInterface $output Output Interface
+     */
+    public function __construct()
+    {
+
+    }
+
+
+
+    /**
+     * Sets the database connection.
+     *
+     * @param \PDO $connection Connection
+     * @return AdapterInterface
+     */
+    public function setConnection(\PDO $connection)
+    {
+        $this->connection = $connection;
+
+        return $this;
+    }
+
+    /**
+     * Gets the database connection
+     *
+     * @return \PDO
+     */
+    public function getConnection()
+    {
+        if (null === $this->connection) {
+            $this->connect();
+        }
+        return $this->connection;
+    }
+
+    /**
+     * Sets the command start time
+     *
+     * @param int $time
+     * @return AdapterInterface
+     */
+    public function setCommandStartTime($time)
+    {
+        $this->commandStartTime = $time;
+        return $this;
+    }
+
+    /**
+     * Gets the command start time
+     *
+     * @return int
+     */
+    public function getCommandStartTime()
+    {
+        return $this->commandStartTime;
+    }
+
+    /**
+     * Start timing a command.
+     *
+     * @return void
+     */
+    public function startCommandTimer()
+    {
+        $this->setCommandStartTime(microtime(true));
+    }
+
+    /**
+     * Stop timing the current command and write the elapsed time to the
+     * output.
+     *
+     * @return void
+     */
+    public function endCommandTimer()
+    {
+        return;
+        $end = microtime(true);
+        if (OutputInterface::VERBOSITY_VERBOSE <= $this->getOutput()->getVerbosity()) {
+            $this->getOutput()->writeln('    -> ' . sprintf('%.4fs', $end - $this->getCommandStartTime()));
+        }
+    }
+
+    /**
+     * Write a Phinx command to the output.
+     *
+     * @param string $command Command Name
+     * @param array  $args    Command Args
+     * @return void
+     */
+    public function writeCommand($command, $args = array())
+    {
+        return '';
+        if (OutputInterface::VERBOSITY_VERBOSE <= $this->getOutput()->getVerbosity()) {
+            if (count($args)) {
+                $outArr = array();
+                foreach ($args as $arg) {
+                    if (is_array($arg)) {
+                        $arg = array_map(function ($value) {
+                            return '\'' . $value . '\'';
+                        }, $arg);
+                        $outArr[] = '[' . implode(', ', $arg)  . ']';
+                        continue;
+                    }
+
+                    $outArr[] = '\'' . $arg . '\'';
+                }
+                $this->getOutput()->writeln(' -- ' . $command . '(' . implode(', ', $outArr) . ')');
+                return;
+            }
+            $this->getOutput()->writeln(' -- ' . $command);
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function connect()
+    {
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function disconnect()
+    {
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function execute($sql)
+    {
+        echo $sql;
+        return $this->getConnection()->exec($sql);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function query($sql)
+    {
+        return $this->getConnection()->query($sql);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function fetchRow($sql)
+    {
+        $result = $this->query($sql);
+        return $result->fetch();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function fetchAll($sql)
+    {
+        $rows = array();
+        $result = $this->query($sql);
+        while ($row = $result->fetch()) {
+            $rows[] = $row;
+        }
+        return $rows;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function insert(Table $table, $row)
+    {
+        $this->startCommandTimer();
+        //$this->writeCommand('insert', array($table->getName()));
+
+        $sql = sprintf(
+            "INSERT INTO %s ",
+            $this->quoteTableName($table->getName())
+        );
+
+        $columns = array_keys($row);
+        $sql .= "(". implode(', ', array_map(array($this, 'quoteColumnName'), $columns)) . ")";
+        $sql .= " VALUES (" . implode(', ', array_fill(0, count($columns), '?')) . ")";
+
+        $stmt = $this->getConnection()->prepare($sql);
+        $stmt->execute(array_values($row));
+        $this->endCommandTimer();
+    }
+
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAdapterType()
+    {
+        return $this->getOption('adapter');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getColumnTypes()
+    {
+        return array(
+            'string',
+            'char',
+            'text',
+            'integer',
+            'biginteger',
+            'float',
+            'decimal',
+            'datetime',
+            'timestamp',
+            'time',
+            'date',
+            'blob',
+            'binary',
+            'varbinary',
+            'boolean',
+            'uuid',
+            // Geospatial data types
+            'geometry',
+            'point',
+            'linestring',
+            'polygon',
+        );
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function isValidColumnType(Column $column) {
+        return in_array($column->getType(), $this->getColumnTypes());
+    }
+
+    /**
+     * Cast a value to a boolean appropriate for the adapter.
+     *
+     * @param mixed $value The value to be cast
+     *
+     * @return mixed
+     */
+    public function castToBool($value)
+    {
+        return (bool) $value ? 1 : 0;
+    }
+}

+ 1184 - 0
src/DDLWrapper/Db/Adapter/PostgresAdapter.php

@@ -0,0 +1,1184 @@
+<?php
+/**
+ * Phinx
+ *
+ * (The MIT license)
+ * Copyright (c) 2015 Rob Morgan
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated * documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ *
+ * @package    Phinx
+ * @subpackage Phinx\Db\Adapter
+ */
+namespace DDLWrapper\Db\Adapter;
+
+use DDLWrapper\Db\Table;
+use DDLWrapper\Db\Table\Column;
+use DDLWrapper\Db\Table\Index;
+use DDLWrapper\Db\Table\ForeignKey;
+
+class PostgresAdapter extends PdoAdapter implements AdapterInterface
+{
+    const INT_SMALL = 65535;
+
+    /**
+     * Columns with comments
+     *
+     * @var array
+     */
+    protected $columnsWithComments = array();
+
+    /**
+     * {@inheritdoc}
+     */
+    public function connect()
+    {
+        if (null === $this->connection) {
+            if (!class_exists('PDO') || !in_array('pgsql', \PDO::getAvailableDrivers(), true)) {
+                // @codeCoverageIgnoreStart
+                throw new \RuntimeException('You need to enable the PDO_Pgsql extension for Phinx to run properly.');
+                // @codeCoverageIgnoreEnd
+            }
+
+            $db = null;
+            $options = $this->getOptions();
+
+            // if port is specified use it, otherwise use the PostgreSQL default
+            if (isset($options['port'])) {
+                $dsn = 'pgsql:host=' . $options['host'] . ';port=' . $options['port'] . ';dbname=' . $options['name'];
+            } else {
+                $dsn = 'pgsql:host=' . $options['host'] . ';dbname=' . $options['name'];
+            }
+
+            try {
+                $db = new \PDO($dsn, $options['user'], $options['pass'], array(\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION));
+            } catch (\PDOException $exception) {
+                throw new \InvalidArgumentException(sprintf(
+                    'There was a problem connecting to the database: %s',
+                    $exception->getMessage()
+                ));
+            }
+
+            $this->setConnection($db);
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function disconnect()
+    {
+        $this->connection = null;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function hasTransactions()
+    {
+        return true;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function beginTransaction()
+    {
+        $this->execute('BEGIN');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function commitTransaction()
+    {
+        $this->execute('COMMIT');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function rollbackTransaction()
+    {
+        $this->execute('ROLLBACK');
+    }
+
+    /**
+     * Quotes a schema name for use in a query.
+     *
+     * @param string $schemaName Schema Name
+     * @return string
+     */
+    public function quoteSchemaName($schemaName)
+    {
+        return $this->quoteColumnName($schemaName);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function quoteTableName($tableName)
+    {
+        return $this->quoteSchemaName($this->getSchemaName()) . '.' . $this->quoteColumnName($tableName);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function quoteColumnName($columnName)
+    {
+        return '"'. $columnName . '"';
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function hasTable($tableName)
+    {
+        $result = $this->getConnection()->query(
+            sprintf(
+                'SELECT *
+                FROM information_schema.tables
+                WHERE table_schema = %s
+                AND lower(table_name) = lower(%s)',
+                $this->getConnection()->quote($this->getSchemaName()),
+                $this->getConnection()->quote($tableName)
+            )
+        );
+
+        return $result->rowCount() === 1;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function createTable(Table $table)
+    {
+        $this->startCommandTimer();
+        $options = $table->getOptions();
+
+         // Add the default primary key
+        $columns = $table->getPendingColumns();
+        if (!isset($options['id']) || (isset($options['id']) && $options['id'] === true)) {
+            $column = new Column();
+            $column->setName('id')
+                   ->setType('integer')
+                   ->setIdentity(true);
+
+            array_unshift($columns, $column);
+            $options['primary_key'] = 'id';
+
+        } elseif (isset($options['id']) && is_string($options['id'])) {
+            // Handle id => "field_name" to support AUTO_INCREMENT
+            $column = new Column();
+            $column->setName($options['id'])
+                   ->setType('integer')
+                   ->setIdentity(true);
+
+            array_unshift($columns, $column);
+            $options['primary_key'] = $options['id'];
+        }
+
+        // TODO - process table options like collation etc
+        $sql = 'CREATE TABLE ';
+        $sql .= $this->quoteTableName($table->getName()) . ' (';
+
+        $this->columnsWithComments = array();
+        foreach ($columns as $column) {
+            $sql .= $this->quoteColumnName($column->getName()) . ' ' . $this->getColumnSqlDefinition($column) . ', ';
+
+            // set column comments, if needed
+            if ($column->getComment()) {
+                $this->columnsWithComments[] = $column;
+            }
+        }
+
+         // set the primary key(s)
+        if (isset($options['primary_key'])) {
+            $sql = rtrim($sql);
+            $sql .= sprintf(' CONSTRAINT %s_pkey PRIMARY KEY (', $table->getName());
+            if (is_string($options['primary_key'])) {       // handle primary_key => 'id'
+                $sql .= $this->quoteColumnName($options['primary_key']);
+            } elseif (is_array($options['primary_key'])) { // handle primary_key => array('tag_id', 'resource_id')
+                // PHP 5.4 will allow access of $this, so we can call quoteColumnName() directly in the anonymous function,
+                // but for now just hard-code the adapter quotes
+                $sql .= implode(
+                    ',',
+                    array_map(
+                        function ($v) {
+                            return '"' . $v . '"';
+                        },
+                        $options['primary_key']
+                    )
+                );
+            }
+            $sql .= ')';
+        } else {
+            $sql = substr(rtrim($sql), 0, -1);              // no primary keys
+        }
+
+        // set the foreign keys
+        $foreignKeys = $table->getForeignKeys();
+        if (!empty($foreignKeys)) {
+            foreach ($foreignKeys as $foreignKey) {
+                $sql .= ', ' . $this->getForeignKeySqlDefinition($foreignKey, $table->getName());
+            }
+        }
+
+        $sql .= ');';
+
+        // process column comments
+        if (!empty($this->columnsWithComments)) {
+            foreach ($this->columnsWithComments as $column) {
+                $sql .= $this->getColumnCommentSqlDefinition($column, $table->getName());
+            }
+        }
+
+
+        // set the indexes
+        $indexes = $table->getIndexes();
+        if (!empty($indexes)) {
+            foreach ($indexes as $index) {
+                $sql .= $this->getIndexSqlDefinition($index, $table->getName());
+            }
+        }
+
+        // execute the sql
+        $this->writeCommand('createTable', array($table->getName()));
+        $this->execute($sql);
+
+        // process table comments
+        if (isset($options['comment'])) {
+            $sql = sprintf(
+                'COMMENT ON TABLE %s IS %s',
+                $this->quoteTableName($table->getName()),
+                $this->getConnection()->quote($options['comment'])
+            );
+            $this->execute($sql);
+        }
+
+        $this->endCommandTimer();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function renameTable($tableName, $newTableName)
+    {
+        $this->startCommandTimer();
+        $this->writeCommand('renameTable', array($tableName, $newTableName));
+        $sql = sprintf(
+            'ALTER TABLE %s RENAME TO %s',
+            $this->quoteTableName($tableName),
+            $this->quoteColumnName($newTableName)
+        );
+        $this->execute($sql);
+        $this->endCommandTimer();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function dropTable($tableName)
+    {
+        $this->startCommandTimer();
+        $this->writeCommand('dropTable', array($tableName));
+        $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($tableName)));
+        $this->endCommandTimer();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getColumns($tableName)
+    {
+        $columns = array();
+        $sql = sprintf(
+            "SELECT column_name, data_type, is_identity, is_nullable,
+             column_default, character_maximum_length, numeric_precision, numeric_scale
+             FROM information_schema.columns
+             WHERE table_name ='%s'",
+            $tableName
+        );
+        $columnsInfo = $this->fetchAll($sql);
+
+        foreach ($columnsInfo as $columnInfo) {
+            $column = new Column();
+            $column->setName($columnInfo['column_name'])
+                   ->setType($this->getPhinxType($columnInfo['data_type']))
+                   ->setNull($columnInfo['is_nullable'] === 'YES')
+                   ->setDefault($columnInfo['column_default'])
+                   ->setIdentity($columnInfo['is_identity'] === 'YES')
+                   ->setPrecision($columnInfo['numeric_precision'])
+                   ->setScale($columnInfo['numeric_scale']);
+
+            if (preg_match('/\bwith time zone$/', $columnInfo['data_type'])) {
+                $column->setTimezone(true);
+            }
+
+            if (isset($columnInfo['character_maximum_length'])) {
+                $column->setLimit($columnInfo['character_maximum_length']);
+            }
+            $columns[] = $column;
+        }
+        return $columns;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function hasColumn($tableName, $columnName, $options = array())
+    {
+        $sql = sprintf("SELECT count(*)
+            FROM information_schema.columns
+            WHERE table_schema = '%s' AND table_name = '%s' AND column_name = '%s'",
+            $this->getSchemaName(),
+            $tableName,
+            $columnName
+        );
+
+        $result = $this->fetchRow($sql);
+        return  $result['count'] > 0;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function addColumn(Table $table, Column $column)
+    {
+        $this->startCommandTimer();
+        $this->writeCommand('addColumn', array($table->getName(), $column->getName(), $column->getType()));
+        $sql = sprintf(
+            'ALTER TABLE %s ADD %s %s',
+            $this->quoteTableName($table->getName()),
+            $this->quoteColumnName($column->getName()),
+            $this->getColumnSqlDefinition($column)
+        );
+
+        $this->execute($sql);
+        $this->endCommandTimer();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function renameColumn($tableName, $columnName, $newColumnName)
+    {
+        $this->startCommandTimer();
+        $sql = sprintf(
+            "SELECT CASE WHEN COUNT(*) > 0 THEN 1 ELSE 0 END AS column_exists
+             FROM information_schema.columns
+             WHERE table_name ='%s' AND column_name = '%s'",
+            $tableName,
+            $columnName
+        );
+        $result = $this->fetchRow($sql);
+        if (!(bool) $result['column_exists']) {
+            throw new \InvalidArgumentException("The specified column does not exist: $columnName");
+        }
+        $this->writeCommand('renameColumn', array($tableName, $columnName, $newColumnName));
+        $this->execute(
+            sprintf(
+                'ALTER TABLE %s RENAME COLUMN %s TO %s',
+                $this->quoteTableName($tableName),
+                $this->quoteColumnName($columnName),
+                $newColumnName
+            )
+        );
+        $this->endCommandTimer();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function changeColumn($tableName, $columnName, Column $newColumn)
+    {
+        // TODO - is it possible to merge these 3 queries into less?
+        $this->startCommandTimer();
+        $this->writeCommand('changeColumn', array($tableName, $columnName, $newColumn->getType()));
+        // change data type
+        $sql = sprintf(
+            'ALTER TABLE %s ALTER COLUMN %s TYPE %s',
+            $this->quoteTableName($tableName),
+            $this->quoteColumnName($columnName),
+            $this->getColumnSqlDefinition($newColumn)
+        );
+        //NULL and DEFAULT cannot be set while changing column type
+        $sql = preg_replace('/ NOT NULL/', '', $sql);
+        $sql = preg_replace('/ NULL/', '', $sql);
+        //If it is set, DEFAULT is the last definition
+        $sql = preg_replace('/DEFAULT .*/', '', $sql);
+        $this->execute($sql);
+        // process null
+        $sql = sprintf(
+            'ALTER TABLE %s ALTER COLUMN %s',
+            $this->quoteTableName($tableName),
+            $this->quoteColumnName($columnName)
+        );
+        if ($newColumn->isNull()) {
+            $sql .= ' DROP NOT NULL';
+        } else {
+            $sql .= ' SET NOT NULL';
+        }
+        $this->execute($sql);
+        if (!is_null($newColumn->getDefault())) {
+            //change default
+            $this->execute(
+                sprintf(
+                    'ALTER TABLE %s ALTER COLUMN %s SET %s',
+                    $this->quoteTableName($tableName),
+                    $this->quoteColumnName($columnName),
+                    $this->getDefaultValueDefinition($newColumn->getDefault())
+                )
+            );
+        }
+        else {
+            //drop default
+            $this->execute(
+                sprintf(
+                    'ALTER TABLE %s ALTER COLUMN %s DROP DEFAULT',
+                    $this->quoteTableName($tableName),
+                    $this->quoteColumnName($columnName)
+                )
+            );
+        }
+        // rename column
+        if ($columnName !== $newColumn->getName()) {
+            $this->execute(
+                sprintf(
+                    'ALTER TABLE %s RENAME COLUMN %s TO %s',
+                    $this->quoteTableName($tableName),
+                    $this->quoteColumnName($columnName),
+                    $this->quoteColumnName($newColumn->getName())
+                )
+            );
+        }
+
+        // change column comment if needed
+        if ($newColumn->getComment()) {
+            $sql = $this->getColumnCommentSqlDefinition($newColumn, $tableName);
+            $this->execute($sql);
+        }
+
+        $this->endCommandTimer();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function dropColumn($tableName, $columnName)
+    {
+        $this->startCommandTimer();
+        $this->writeCommand('dropColumn', array($tableName, $columnName));
+        $this->execute(
+            sprintf(
+                'ALTER TABLE %s DROP COLUMN %s',
+                $this->quoteTableName($tableName),
+                $this->quoteColumnName($columnName)
+            )
+        );
+        $this->endCommandTimer();
+    }
+
+    /**
+     * Get an array of indexes from a particular table.
+     *
+     * @param string $tableName Table Name
+     * @return array
+     */
+    protected function getIndexes($tableName)
+    {
+        $indexes = array();
+        $sql = "SELECT
+            i.relname AS index_name,
+            a.attname AS column_name
+        FROM
+            pg_class t,
+            pg_class i,
+            pg_index ix,
+            pg_attribute a
+        WHERE
+            t.oid = ix.indrelid
+            AND i.oid = ix.indexrelid
+            AND a.attrelid = t.oid
+            AND a.attnum = ANY(ix.indkey)
+            AND t.relkind = 'r'
+            AND t.relname = '$tableName'
+        ORDER BY
+            t.relname,
+            i.relname;";
+        $rows = $this->fetchAll($sql);
+        foreach ($rows as $row) {
+            if (!isset($indexes[$row['index_name']])) {
+                $indexes[$row['index_name']] = array('columns' => array());
+            }
+            $indexes[$row['index_name']]['columns'][] = strtolower($row['column_name']);
+        }
+        return $indexes;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function hasIndex($tableName, $columns)
+    {
+        if (is_string($columns)) {
+            $columns = array($columns);
+        }
+        $columns = array_map('strtolower', $columns);
+        $indexes = $this->getIndexes($tableName);
+        foreach ($indexes as $index) {
+            if (array_diff($index['columns'], $columns) === array_diff($columns, $index['columns'])) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+     /**
+      * {@inheritdoc}
+      */
+     public function hasIndexByName($tableName, $indexName)
+     {
+         $indexes = $this->getIndexes($tableName);
+         foreach ($indexes as $name => $index) {
+             if ($name === $indexName) {
+                 return true;
+             }
+         }
+         return false;
+     }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function addIndex(Table $table, Index $index)
+    {
+        $this->startCommandTimer();
+        $this->writeCommand('addIndex', array($table->getName(), $index->getColumns()));
+        $sql = $this->getIndexSqlDefinition($index, $table->getName());
+        $this->execute($sql);
+        $this->endCommandTimer();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function dropIndex($tableName, $columns)
+    {
+        $this->startCommandTimer();
+        if (is_string($columns)) {
+            $columns = array($columns); // str to array
+        }
+
+        $this->writeCommand('dropIndex', array($tableName, $columns));
+        $indexes = $this->getIndexes($tableName);
+        $columns = array_map('strtolower', $columns);
+
+        foreach ($indexes as $indexName => $index) {
+            $a = array_diff($columns, $index['columns']);
+            if (empty($a)) {
+                $this->execute(
+                    sprintf(
+                        'DROP INDEX IF EXISTS %s',
+                        $this->quoteColumnName($indexName)
+                    )
+                );
+                $this->endCommandTimer();
+                return;
+            }
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function dropIndexByName($tableName, $indexName)
+    {
+        $this->startCommandTimer();
+        $this->writeCommand('dropIndexByName', array($tableName, $indexName));
+        $sql = sprintf(
+            'DROP INDEX IF EXISTS %s',
+            $indexName
+        );
+        $this->execute($sql);
+        $this->endCommandTimer();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function hasForeignKey($tableName, $columns, $constraint = null)
+    {
+        if (is_string($columns)) {
+            $columns = array($columns); // str to array
+        }
+        $foreignKeys = $this->getForeignKeys($tableName);
+        if ($constraint) {
+            if (isset($foreignKeys[$constraint])) {
+                return !empty($foreignKeys[$constraint]);
+            }
+            return false;
+        } else {
+            foreach ($foreignKeys as $key) {
+                $a = array_diff($columns, $key['columns']);
+                if (empty($a)) {
+                    return true;
+                }
+            }
+            return false;
+        }
+    }
+
+    /**
+     * Get an array of foreign keys from a particular table.
+     *
+     * @param string $tableName Table Name
+     * @return array
+     */
+    protected function getForeignKeys($tableName)
+    {
+        $foreignKeys = array();
+        $rows = $this->fetchAll(sprintf(
+            "SELECT
+                    tc.constraint_name,
+                    tc.table_name, kcu.column_name,
+                    ccu.table_name AS referenced_table_name,
+                    ccu.column_name AS referenced_column_name
+                FROM
+                    information_schema.table_constraints AS tc
+                    JOIN information_schema.key_column_usage AS kcu ON tc.constraint_name = kcu.constraint_name
+                    JOIN information_schema.constraint_column_usage AS ccu ON ccu.constraint_name = tc.constraint_name
+                WHERE constraint_type = 'FOREIGN KEY' AND tc.table_name = '%s'
+                ORDER BY kcu.position_in_unique_constraint",
+            $tableName
+        ));
+        foreach ($rows as $row) {
+            $foreignKeys[$row['constraint_name']]['table'] = $row['table_name'];
+            $foreignKeys[$row['constraint_name']]['columns'][] = $row['column_name'];
+            $foreignKeys[$row['constraint_name']]['referenced_table'] = $row['referenced_table_name'];
+            $foreignKeys[$row['constraint_name']]['referenced_columns'][] = $row['referenced_column_name'];
+        }
+        return $foreignKeys;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function addForeignKey(Table $table, ForeignKey $foreignKey)
+    {
+        $this->startCommandTimer();
+        $this->writeCommand('addForeignKey', array($table->getName(), $foreignKey->getColumns()));
+        $sql = sprintf(
+            'ALTER TABLE %s ADD %s',
+            $this->quoteTableName($table->getName()),
+            $this->getForeignKeySqlDefinition($foreignKey, $table->getName())
+        );
+        $this->execute($sql);
+        $this->endCommandTimer();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function dropForeignKey($tableName, $columns, $constraint = null)
+    {
+        $this->startCommandTimer();
+        if (is_string($columns)) {
+            $columns = array($columns); // str to array
+        }
+        $this->writeCommand('dropForeignKey', array($tableName, $columns));
+
+        if ($constraint) {
+            $this->execute(
+                sprintf(
+                    'ALTER TABLE %s DROP CONSTRAINT %s',
+                    $this->quoteTableName($tableName),
+                    $constraint
+                )
+            );
+        } else {
+            foreach ($columns as $column) {
+                $rows = $this->fetchAll(sprintf(
+                    "SELECT CONSTRAINT_NAME
+                      FROM information_schema.KEY_COLUMN_USAGE
+                      WHERE TABLE_SCHEMA = CURRENT_SCHEMA()
+                        AND TABLE_NAME IS NOT NULL
+                        AND TABLE_NAME = '%s'
+                        AND COLUMN_NAME = '%s'
+                      ORDER BY POSITION_IN_UNIQUE_CONSTRAINT",
+                    $tableName,
+                    $column
+                ));
+
+                foreach ($rows as $row) {
+                    $this->dropForeignKey($tableName, $columns, $row['constraint_name']);
+                }
+            }
+        }
+        $this->endCommandTimer();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getSqlType($type, $limit = null)
+    {
+        switch ($type) {
+            case static::PHINX_TYPE_INTEGER:
+                if ($limit && $limit == static::INT_SMALL) {
+                    return array(
+                        'name' => 'smallint',
+                        'limit' => static::INT_SMALL
+                    );
+                }
+                return array('name' => $type);
+            case static::PHINX_TYPE_TEXT:
+            case static::PHINX_TYPE_TIME:
+            case static::PHINX_TYPE_DATE:
+            case static::PHINX_TYPE_BOOLEAN:
+            case static::PHINX_TYPE_JSON:
+            case static::PHINX_TYPE_JSONB:
+            case static::PHINX_TYPE_UUID:
+                return array('name' => $type);
+            case static::PHINX_TYPE_DECIMAL:
+                return array('name' => $type, 'precision' => 18, 'scale' => 0);
+            case static::PHINX_TYPE_STRING:
+                return array('name' => 'character varying', 'limit' => 255);
+            case static::PHINX_TYPE_CHAR:
+                return array('name' => 'character', 'limit' => 255);
+            case static::PHINX_TYPE_BIG_INTEGER:
+                return array('name' => 'bigint');
+            case static::PHINX_TYPE_FLOAT:
+                return array('name' => 'real');
+            case static::PHINX_TYPE_DATETIME:
+            case static::PHINX_TYPE_TIMESTAMP:
+                return array('name' => 'timestamp');
+            case static::PHINX_TYPE_BLOB:
+            case static::PHINX_TYPE_BINARY:
+                return array('name' => 'bytea');
+            // Geospatial database types
+            // Spatial storage in Postgres is done via the PostGIS extension,
+            // which enables the use of the "geography" type in combination
+            // with SRID 4326.
+            case static::PHINX_TYPE_GEOMETRY:
+                return array('name' => 'geography', 'geometry', 4326);
+                break;
+            case static::PHINX_TYPE_POINT:
+                return array('name' => 'geography', 'point', 4326);
+                break;
+            case static::PHINX_TYPE_LINESTRING:
+                return array('name' => 'geography', 'linestring', 4326);
+                break;
+            case static::PHINX_TYPE_POLYGON:
+                return array('name' => 'geography', 'polygon', 4326);
+                break;
+            default:
+                if ($this->isArrayType($type)) {
+                    return array('name' => $type);
+                }
+                // Return array type
+                throw new \RuntimeException('The type: "' . $type . '" is not supported');
+        }
+    }
+
+    /**
+     * Returns Phinx type by SQL type
+     *
+     * @param string $sqlType SQL type
+     * @returns string Phinx type
+     */
+    public function getPhinxType($sqlType)
+    {
+        switch ($sqlType) {
+            case 'character varying':
+            case 'varchar':
+                return static::PHINX_TYPE_STRING;
+            case 'character':
+            case 'char':
+                return static::PHINX_TYPE_CHAR;
+            case 'text':
+                return static::PHINX_TYPE_TEXT;
+            case 'json':
+                return static::PHINX_TYPE_JSON;
+            case 'jsonb':
+                return static::PHINX_TYPE_JSONB;
+            case 'smallint':
+                return array(
+                    'name' => 'smallint',
+                    'limit' => static::INT_SMALL
+                );
+            case 'int':
+            case 'int4':
+            case 'integer':
+                return static::PHINX_TYPE_INTEGER;
+            case 'decimal':
+            case 'numeric':
+                return static::PHINX_TYPE_DECIMAL;
+            case 'bigint':
+            case 'int8':
+                return static::PHINX_TYPE_BIG_INTEGER;
+            case 'real':
+            case 'float4':
+                return static::PHINX_TYPE_FLOAT;
+            case 'bytea':
+                return static::PHINX_TYPE_BINARY;
+                break;
+            case 'time':
+            case 'timetz':
+            case 'time with time zone':
+            case 'time without time zone':
+                return static::PHINX_TYPE_TIME;
+            case 'date':
+                return static::PHINX_TYPE_DATE;
+            case 'timestamp':
+            case 'timestamptz':
+            case 'timestamp with time zone':
+            case 'timestamp without time zone':
+                return static::PHINX_TYPE_DATETIME;
+            case 'bool':
+            case 'boolean':
+                return static::PHINX_TYPE_BOOLEAN;
+            case 'uuid':
+                return static::PHINX_TYPE_UUID;
+            default:
+                throw new \RuntimeException('The PostgreSQL type: "' . $sqlType . '" is not supported');
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function createDatabase($name, $options = array())
+    {
+        $this->startCommandTimer();
+        $this->writeCommand('createDatabase', array($name));
+        $charset = isset($options['charset']) ? $options['charset'] : 'utf8';
+        $this->execute(sprintf("CREATE DATABASE %s WITH ENCODING = '%s'", $name, $charset));
+        $this->endCommandTimer();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function hasDatabase($databaseName)
+    {
+        $sql = sprintf("SELECT count(*) FROM pg_database WHERE datname = '%s'", $databaseName);
+        $result = $this->fetchRow($sql);
+        return  $result['count'] > 0;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function dropDatabase($name)
+    {
+        $this->startCommandTimer();
+        $this->writeCommand('dropDatabase', array($name));
+        $this->disconnect();
+        $this->execute(sprintf('DROP DATABASE IF EXISTS %s', $name));
+        $this->connect();
+        $this->endCommandTimer();
+    }
+
+    /**
+     * Get the defintion for a `DEFAULT` statement.
+     *
+     * @param  mixed $default
+     * @return string
+     */
+    protected function getDefaultValueDefinition($default)
+    {
+        if (is_string($default) && 'CURRENT_TIMESTAMP' !== $default) {
+            $default = $this->getConnection()->quote($default);
+        } elseif (is_bool($default)) {
+            $default = $this->castToBool($default);
+        }
+        return isset($default) ? 'DEFAULT ' . $default : '';
+    }
+
+    /**
+     * Gets the PostgreSQL Column Definition for a Column object.
+     *
+     * @param Column $column Column
+     * @return string
+     */
+    protected function getColumnSqlDefinition(Column $column)
+    {
+        $buffer = array();
+        if ($column->isIdentity()) {
+            $buffer[] = $column->getType() == 'biginteger' ? 'BIGSERIAL' : 'SERIAL';
+        } else {
+            $sqlType = $this->getSqlType($column->getType(), $column->getLimit());
+            $buffer[] = strtoupper($sqlType['name']);
+            // integers cant have limits in postgres
+            if (static::PHINX_TYPE_DECIMAL === $sqlType['name'] && ($column->getPrecision() || $column->getScale())) {
+                $buffer[] = sprintf(
+                    '(%s, %s)',
+                    $column->getPrecision() ? $column->getPrecision() : $sqlType['precision'],
+                    $column->getScale() ? $column->getScale() : $sqlType['scale']
+                );
+            } elseif (!in_array($sqlType['name'], array('integer', 'smallint'))) {
+                if ($column->getLimit() || isset($sqlType['limit'])) {
+                    $buffer[] = sprintf('(%s)', $column->getLimit() ? $column->getLimit() : $sqlType['limit']);
+                }
+            }
+
+            $timeTypes = array(
+                'time',
+                'timestamp',
+            );
+            if (in_array($sqlType['name'], $timeTypes) && $column->isTimezone()) {
+                $buffer[] = strtoupper('with time zone');
+            }
+        }
+
+        $buffer[] = $column->isNull() ? 'NULL' : 'NOT NULL';
+
+        if (!is_null($column->getDefault())) {
+            $buffer[] = $this->getDefaultValueDefinition($column->getDefault());
+        }
+
+        return implode(' ', $buffer);
+    }
+
+    /**
+     * Gets the PostgreSQL Column Comment Defininition for a column object.
+     *
+     * @param Column $column Column
+     * @param string $tableName Table name
+     * @return string
+     */
+    protected function getColumnCommentSqlDefinition(Column $column, $tableName)
+    {
+        // passing 'null' is to remove column comment
+        $comment = (strcasecmp($column->getComment(), 'NULL') !== 0)
+                 ? $this->getConnection()->quote($column->getComment())
+                 : 'NULL';
+
+        return sprintf(
+            'COMMENT ON COLUMN %s.%s IS %s;',
+            $tableName,
+            $column->getName(),
+            $comment
+        );
+    }
+
+    /**
+     * Gets the PostgreSQL Index Definition for an Index object.
+     *
+     * @param Index  $index Index
+     * @param string $tableName Table name
+     * @return string
+     */
+    protected function getIndexSqlDefinition(Index $index, $tableName)
+    {
+        if (is_string($index->getName())) {
+            $indexName = $index->getName();
+        } else {
+            $columnNames = $index->getColumns();
+            if (is_string($columnNames)) {
+                $columnNames = array($columnNames);
+            }
+            $indexName = sprintf('%s_%s', $tableName, implode('_', $columnNames));
+        }
+        $def = sprintf(
+            "CREATE %s INDEX %s ON %s (%s);",
+            ($index->getType() === Index::UNIQUE ? 'UNIQUE' : ''),
+            $indexName,
+            $this->quoteTableName($tableName),
+            implode(',', $index->getColumns())
+        );
+        return $def;
+    }
+
+    /**
+     * Gets the MySQL Foreign Key Definition for an ForeignKey object.
+     *
+     * @param ForeignKey $foreignKey
+     * @param string     $tableName  Table name
+     * @return string
+     */
+    protected function getForeignKeySqlDefinition(ForeignKey $foreignKey, $tableName)
+    {
+        $constraintName = $foreignKey->getConstraint() ?: $tableName . '_' . implode('_', $foreignKey->getColumns());
+        $def = ' CONSTRAINT "' . $constraintName . '" FOREIGN KEY ("' . implode('", "', $foreignKey->getColumns()) . '")';
+        $def .= " REFERENCES {$this->quoteTableName($foreignKey->getReferencedTable()->getName())} (\"" . implode('", "', $foreignKey->getReferencedColumns()) . '")';
+        if ($foreignKey->getOnDelete()) {
+            $def .= " ON DELETE {$foreignKey->getOnDelete()}";
+        }
+        if ($foreignKey->getOnUpdate()) {
+            $def .= " ON UPDATE {$foreignKey->getOnUpdate()}";
+        }
+        return $def;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function createSchemaTable()
+    {
+        // Create the public/custom schema if it doesn't already exist
+        if (false === $this->hasSchema($this->getSchemaName())) {
+            $this->createSchema($this->getSchemaName());
+        }
+
+        $this->fetchAll(sprintf('SET search_path TO %s', $this->getSchemaName()));
+
+        return parent::createSchemaTable();
+    }
+
+    /**
+     * Creates the specified schema.
+     *
+     * @param  string $schemaName Schema Name
+     * @return void
+     */
+    public function createSchema($schemaName = 'public')
+    {
+        $this->startCommandTimer();
+        $this->writeCommand('addSchema', array($schemaName));
+        $sql = sprintf('CREATE SCHEMA %s;', $this->quoteSchemaName($schemaName)); // from postgres 9.3 we can use "CREATE SCHEMA IF NOT EXISTS schema_name"
+        $this->execute($sql);
+        $this->endCommandTimer();
+    }
+
+    /**
+     * Checks to see if a schema exists.
+     *
+     * @param string $schemaName Schema Name
+     * @return boolean
+     */
+    public function hasSchema($schemaName)
+    {
+        $sql = sprintf(
+            "SELECT count(*)
+             FROM pg_namespace
+             WHERE nspname = '%s'",
+            $schemaName
+        );
+        $result = $this->fetchRow($sql);
+        return $result['count'] > 0;
+    }
+
+    /**
+     * Drops the specified schema table.
+     *
+     * @param string $schemaName Schema name
+     * @return void
+     */
+    public function dropSchema($schemaName)
+    {
+        $this->startCommandTimer();
+        $this->writeCommand('dropSchema', array($schemaName));
+        $sql = sprintf("DROP SCHEMA IF EXISTS %s CASCADE;", $this->quoteSchemaName($schemaName));
+        $this->execute($sql);
+        $this->endCommandTimer();
+    }
+
+    /**
+     * Drops all schemas.
+     *
+     * @return void
+     */
+    public function dropAllSchemas()
+    {
+        $this->startCommandTimer();
+        $this->writeCommand('dropAllSchemas');
+        foreach ($this->getAllSchemas() as $schema) {
+            $this->dropSchema($schema);
+        }
+        $this->endCommandTimer();
+    }
+
+    /**
+     * Returns schemas.
+     *
+     * @return array
+     */
+    public function getAllSchemas()
+    {
+        $sql = "SELECT schema_name
+                FROM information_schema.schemata
+                WHERE schema_name <> 'information_schema' AND schema_name !~ '^pg_'";
+        $items = $this->fetchAll($sql);
+        $schemaNames = array();
+        foreach ($items as $item) {
+            $schemaNames[] = $item['schema_name'];
+        }
+        return $schemaNames;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getColumnTypes()
+    {
+        return array_merge(parent::getColumnTypes(), array('json', 'jsonb'));
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function isValidColumnType(Column $column)
+    {
+        // If not a standard column type, maybe it is array type?
+        return (parent::isValidColumnType($column) || $this->isArrayType($column->getType()));
+    }
+
+    /**
+     * Check if the given column is an array of a valid type.
+     *
+     * @param  string $columnType
+     * @return bool
+     */
+    protected function isArrayType($columnType)
+    {
+        if (!preg_match('/^([a-z]+)(?:\[\]){1,}$/', $columnType, $matches)) {
+            return false;
+        }
+
+        $baseType = $matches[1];
+        return in_array($baseType, $this->getColumnTypes());
+    }
+
+    /**
+     * Gets the schema name.
+     *
+     * @return string
+     */
+    private function getSchemaName()
+    {
+        $options = $this->getOptions();
+        return empty($options['schema']) ? 'public' : $options['schema'];
+    }
+
+    /**
+     * Cast a value to a boolean appropriate for the adapter.
+     *
+     * @param mixed $value The value to be cast
+     *
+     * @return mixed
+     */
+    public function castToBool($value)
+    {
+        return (bool) $value ? 'TRUE' : 'FALSE';
+    }
+
+}

+ 325 - 0
src/DDLWrapper/Db/Adapter/ProxyAdapter.php

@@ -0,0 +1,325 @@
+<?php
+/**
+ * Phinx
+ *
+ * (The MIT license)
+ * Copyright (c) 2015 Rob Morgan
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated * documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ *
+ * @package    Phinx
+ * @subpackage Phinx\Db\Adapter
+ */
+namespace DDLWrapper\Db\Adapter;
+
+use DDLWrapper\Db\Table;
+use DDLWrapper\Db\Table\Column;
+use DDLWrapper\Db\Table\Index;
+use DDLWrapper\Db\Table\ForeignKey;
+//use DDLWrapper\Migration\IrreversibleMigrationException;
+
+/**
+ * Phinx Proxy Adapter.
+ *
+ * Used for recording migration commands to automatically reverse them.
+ *
+ * @author Rob Morgan <robbym@gmail.com>
+ */
+class ProxyAdapter extends AdapterWrapper
+{
+    /**
+     * @var array
+     */
+    protected $commands;
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAdapterType()
+    {
+        return 'ProxyAdapter';
+    }
+    /**
+     * {@inheritdoc}
+     */
+    public function createTable(Table $table)
+    {
+        $this->recordCommand('createTable', array($table->getName()));
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function renameTable($tableName, $newTableName)
+    {
+        $this->recordCommand('renameTable', array($tableName, $newTableName));
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function dropTable($tableName)
+    {
+        $this->recordCommand('dropTable', array($tableName));
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function addColumn(Table $table, Column $column)
+    {
+        $this->recordCommand('addColumn', array($table, $column));
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function renameColumn($tableName, $columnName, $newColumnName)
+    {
+        $this->recordCommand('renameColumn', array($tableName, $columnName, $newColumnName));
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function changeColumn($tableName, $columnName, Column $newColumn)
+    {
+        $this->recordCommand('changeColumn', array($tableName, $columnName, $newColumn));
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function dropColumn($tableName, $columnName)
+    {
+        $this->recordCommand('dropColumn', array($tableName, $columnName));
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function addIndex(Table $table, Index $index)
+    {
+        $this->recordCommand('addIndex', array($table, $index));
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function dropIndex($tableName, $columns, $options = array())
+    {
+        $this->recordCommand('dropIndex', array($tableName, $columns, $options));
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function dropIndexByName($tableName, $indexName)
+    {
+        $this->recordCommand('dropIndexByName', array($tableName, $indexName));
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function addForeignKey(Table $table, ForeignKey $foreignKey)
+    {
+        $this->recordCommand('addForeignKey', array($table, $foreignKey));
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function dropForeignKey($tableName, $columns, $constraint = null)
+    {
+        $this->recordCommand('dropForeignKey', array($columns, $constraint));
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function createDatabase($name, $options = array())
+    {
+        $this->recordCommand('createDatabase', array($name, $options));
+    }
+
+    /**
+     * Record a command for execution later.
+     *
+     * @param string $name Command Name
+     * @param array $arguments Command Arguments
+     * @return void
+     */
+    public function recordCommand($name, $arguments)
+    {
+        $this->commands[] = array(
+            'name'      => $name,
+            'arguments' => $arguments
+        );
+    }
+
+    /**
+     * Sets an array of recorded commands.
+     *
+     * @param array $commands Commands
+     * @return ProxyAdapter
+     */
+    public function setCommands($commands)
+    {
+        $this->commands = $commands;
+        return $this;
+    }
+
+    /**
+     * Gets an array of the recorded commands.
+     *
+     * @return array
+     */
+    public function getCommands()
+    {
+        return $this->commands;
+    }
+
+    /**
+     * Gets an array of the recorded commands in reverse.
+     *
+     * @throws IrreversibleMigrationException if a command cannot be reversed.
+     * @return array
+     */
+    /*public function getInvertedCommands()
+    {
+        if (null === $this->getCommands()) {
+            return array();
+        }
+
+        $invCommands = array();
+        $supportedCommands = array(
+            'createTable', 'renameTable', 'addColumn',
+            'renameColumn', 'addIndex', 'addForeignKey'
+        );
+        foreach (array_reverse($this->getCommands()) as $command) {
+            if (!in_array($command['name'], $supportedCommands)) {
+                throw new IrreversibleMigrationException(sprintf(
+                    'Cannot reverse a "%s" command',
+                    $command['name']
+                ));
+            }
+            $invertMethod = 'invert' . ucfirst($command['name']);
+            $invertedCommand = $this->$invertMethod($command['arguments']);
+            $invCommands[] = array(
+                'name'      => $invertedCommand['name'],
+                'arguments' => $invertedCommand['arguments']
+            );
+        }
+
+        return $invCommands;
+    }*/
+
+    /**
+     * Execute the recorded commands.
+     *
+     * @return void
+     */
+    public function executeCommands()
+    {
+        $commands = $this->getCommands();
+        foreach ($commands as $command) {
+            call_user_func_array(array($this->getAdapter(), $command['name']), $command['arguments']);
+        }
+    }
+
+    /**
+     * Execute the recorded commands in reverse.
+     *
+     * @return void
+     */
+    /*public function executeInvertedCommands()
+    {
+        $commands = $this->getInvertedCommands();
+        foreach ($commands as $command) {
+            call_user_func_array(array($this->getAdapter(), $command['name']), $command['arguments']);
+        }
+    }*/
+
+    /**
+     * Returns the reverse of a createTable command.
+     *
+     * @param array $args Method Arguments
+     * @return array
+     */
+    public function invertCreateTable($args)
+    {
+        return array('name' => 'dropTable', 'arguments' => array($args[0]));
+    }
+
+    /**
+     * Returns the reverse of a renameTable command.
+     *
+     * @param array $args Method Arguments
+     * @return array
+     */
+    public function invertRenameTable($args)
+    {
+        return array('name' => 'renameTable', 'arguments' => array($args[1], $args[0]));
+    }
+
+    /**
+     * Returns the reverse of a addColumn command.
+     *
+     * @param array $args Method Arguments
+     * @return array
+     */
+    public function invertAddColumn($args)
+    {
+        return array('name' => 'dropColumn', 'arguments' => array($args[0]->getName(), $args[1]->getName()));
+    }
+
+    /**
+     * Returns the reverse of a renameColumn command.
+     *
+     * @param array $args Method Arguments
+     * @return array
+     */
+    public function invertRenameColumn($args)
+    {
+        return array('name' => 'renameColumn', 'arguments' => array($args[0], $args[2], $args[1]));
+    }
+
+    /**
+     * Returns the reverse of a addIndex command.
+     *
+     * @param array $args Method Arguments
+     * @return array
+     */
+    public function invertAddIndex($args)
+    {
+        return array('name' => 'dropIndex', 'arguments' => array($args[0]->getName(), $args[1]->getColumns()));
+    }
+
+    /**
+     * Returns the reverse of a addForeignKey command.
+     *
+     * @param array $args Method Arguments
+     * @return array
+     */
+    public function invertAddForeignKey($args)
+    {
+        return array('name' => 'dropForeignKey', 'arguments' => array($args[0]->getName(), $args[1]->getColumns()));
+    }
+}

+ 1143 - 0
src/DDLWrapper/Db/Adapter/SQLiteAdapter.php

@@ -0,0 +1,1143 @@
+<?php
+/**
+ * Phinx
+ *
+ * (The MIT license)
+ * Copyright (c) 2015 Rob Morgan
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated * documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ *
+ * @package    Phinx
+ * @subpackage Phinx\Db\Adapter
+ */
+namespace DDLWrapper\Db\Adapter;
+
+use DDLWrapper\Db\Table;
+use DDLWrapper\Db\Table\Column;
+use DDLWrapper\Db\Table\Index;
+use DDLWrapper\Db\Table\ForeignKey;
+
+/**
+ * Phinx SQLite Adapter.
+ *
+ * @author Rob Morgan <robbym@gmail.com>
+ * @author Richard McIntyre <richard.mackstars@gmail.com>
+ */
+class SQLiteAdapter extends PdoAdapter implements AdapterInterface
+{
+    protected $definitionsWithLimits = array(
+        'CHARACTER',
+        'VARCHAR',
+        'VARYING CHARACTER',
+        'NCHAR',
+        'NATIVE CHARACTER',
+        'NVARCHAR'
+    );
+
+    /**
+     * {@inheritdoc}
+     */
+    public function connect()
+    {
+        if (null === $this->connection) {
+            if (!class_exists('PDO') || !in_array('sqlite', \PDO::getAvailableDrivers(), true)) {
+                // @codeCoverageIgnoreStart
+                throw new \RuntimeException('You need to enable the PDO_SQLITE extension for Phinx to run properly.');
+                // @codeCoverageIgnoreEnd
+            }
+
+            $db = null;
+            $options = $this->getOptions();
+
+            // if port is specified use it, otherwise use the MySQL default
+            if (isset($options['memory'])) {
+                $dsn = 'sqlite::memory:';
+            } else {
+                $dsn = 'sqlite:' . $options['name'];
+                if (file_exists($options['name'] . '.sqlite3')) {
+                    $dsn = 'sqlite:' . $options['name'] . '.sqlite3';
+                }
+            }
+
+            try {
+                $db = new \PDO($dsn);
+            } catch (\PDOException $exception) {
+                throw new \InvalidArgumentException(sprintf(
+                    'There was a problem connecting to the database: %s',
+                    $exception->getMessage()
+                ));
+            }
+
+            $this->setConnection($db);
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function disconnect()
+    {
+        $this->connection = null;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function hasTransactions()
+    {
+        return true;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function beginTransaction()
+    {
+        $this->execute('BEGIN TRANSACTION');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function commitTransaction()
+    {
+        $this->execute('COMMIT');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function rollbackTransaction()
+    {
+        $this->execute('ROLLBACK');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function quoteTableName($tableName)
+    {
+        return str_replace('.', '`.`', $this->quoteColumnName($tableName));
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function quoteColumnName($columnName)
+    {
+        return '`' . str_replace('`', '``', $columnName) . '`';
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function hasTable($tableName)
+    {
+        $tables = array();
+        $rows = $this->fetchAll(sprintf('SELECT name FROM sqlite_master WHERE type=\'table\' AND name=\'%s\'', $tableName));
+        foreach ($rows as $row) {
+            $tables[] = strtolower($row[0]);
+        }
+
+        return in_array(strtolower($tableName), $tables);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function createTable(Table $table)
+    {
+        $this->startCommandTimer();
+
+        // Add the default primary key
+        $columns = $table->getPendingColumns();
+        $options = $table->getOptions();
+        if (!isset($options['id']) || (isset($options['id']) && $options['id'] === true)) {
+            $column = new Column();
+            $column->setName('id')
+                   ->setType('integer')
+                   ->setIdentity(true);
+
+            array_unshift($columns, $column);
+
+        } elseif (isset($options['id']) && is_string($options['id'])) {
+            // Handle id => "field_name" to support AUTO_INCREMENT
+            $column = new Column();
+            $column->setName($options['id'])
+                   ->setType('integer')
+                   ->setIdentity(true);
+
+            array_unshift($columns, $column);
+        }
+
+
+        $sql = 'CREATE TABLE ';
+        $sql .= $this->quoteTableName($table->getName()) . ' (';
+        foreach ($columns as $column) {
+            $sql .= $this->quoteColumnName($column->getName()) . ' ' . $this->getColumnSqlDefinition($column) . ', ';
+        }
+
+        // set the primary key(s)
+        if (isset($options['primary_key'])) {
+            $sql = rtrim($sql);
+            $sql .= ' PRIMARY KEY (';
+            if (is_string($options['primary_key'])) {       // handle primary_key => 'id'
+                $sql .= $this->quoteColumnName($options['primary_key']);
+            } elseif (is_array($options['primary_key'])) { // handle primary_key => array('tag_id', 'resource_id')
+                // PHP 5.4 will allow access of $this, so we can call quoteColumnName() directly in the anonymous function,
+                // but for now just hard-code the adapter quotes
+                $sql .= implode(
+                    ',',
+                    array_map(
+                        function ($v) {
+                            return '`' . $v . '`';
+                        },
+                        $options['primary_key']
+                    )
+                );
+            }
+            $sql .= ')';
+        } else {
+            $sql = substr(rtrim($sql), 0, -1);              // no primary keys
+        }
+
+        // set the foreign keys
+        $foreignKeys = $table->getForeignKeys();
+        if (!empty($foreignKeys)) {
+            foreach ($foreignKeys as $foreignKey) {
+                $sql .= ', ' . $this->getForeignKeySqlDefinition($foreignKey);
+            }
+        }
+
+        $sql = rtrim($sql) . ');';
+        // execute the sql
+        $this->writeCommand('createTable', array($table->getName()));
+        $this->execute($sql);
+        $this->endCommandTimer();
+
+        foreach ($table->getIndexes() as $index) {
+            $this->addIndex($table, $index);
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function renameTable($tableName, $newTableName)
+    {
+        $this->startCommandTimer();
+        $this->writeCommand('renameTable', array($tableName, $newTableName));
+        $this->execute(sprintf('ALTER TABLE %s RENAME TO %s', $this->quoteTableName($tableName), $this->quoteTableName($newTableName)));
+        $this->endCommandTimer();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function dropTable($tableName)
+    {
+        $this->startCommandTimer();
+        $this->writeCommand('dropTable', array($tableName));
+        $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($tableName)));
+        $this->endCommandTimer();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getColumns($tableName)
+    {
+        $columns = array();
+        $rows = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($tableName)));
+
+        foreach ($rows as $columnInfo) {
+            $column = new Column();
+            $type = strtolower($columnInfo['type']);
+            $column->setName($columnInfo['name'])
+                   ->setNull($columnInfo['notnull'] !== '1')
+                   ->setDefault($columnInfo['dflt_value']);
+
+            $phinxType = $this->getPhinxType($type);
+            $column->setType($phinxType['name'])
+                   ->setLimit($phinxType['limit']);
+
+            if ($columnInfo['pk'] == 1) {
+                $column->setIdentity(true);
+            }
+
+            $columns[] = $column;
+        }
+
+        return $columns;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function hasColumn($tableName, $columnName)
+    {
+        $rows = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($tableName)));
+        foreach ($rows as $column) {
+            if (strcasecmp($column['name'], $columnName) === 0) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function addColumn(Table $table, Column $column)
+    {
+        $this->startCommandTimer();
+
+        $sql = sprintf(
+            'ALTER TABLE %s ADD COLUMN %s %s',
+            $this->quoteTableName($table->getName()),
+            $this->quoteColumnName($column->getName()),
+            $this->getColumnSqlDefinition($column)
+        );
+
+        $this->writeCommand('addColumn', array($table->getName(), $column->getName(), $column->getType()));
+        $this->execute($sql);
+        $this->endCommandTimer();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function renameColumn($tableName, $columnName, $newColumnName)
+    {
+        $tmpTableName = 'tmp_' . $tableName;
+
+        $rows = $this->fetchAll('select * from sqlite_master where `type` = \'table\'');
+
+        $sql = '';
+        foreach ($rows as $table) {
+            if ($table['tbl_name'] === $tableName) {
+                $sql = $table['sql'];
+            }
+        }
+
+        $columns = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($tableName)));
+        $selectColumns = array();
+        $writeColumns = array();
+        foreach ($columns as $column) {
+            $selectName = $column['name'];
+            $writeName = ($selectName == $columnName)? $newColumnName : $selectName;
+            $selectColumns[] = $this->quoteColumnName($selectName);
+            $writeColumns[] = $this->quoteColumnName($writeName);
+        }
+
+        if (!in_array($this->quoteColumnName($columnName), $selectColumns)) {
+            throw new \InvalidArgumentException(sprintf(
+                'The specified column doesn\'t exist: ' . $columnName
+            ));
+        }
+
+        $this->execute(sprintf('ALTER TABLE %s RENAME TO %s', $tableName, $tmpTableName));
+
+        $sql = str_replace(
+            $this->quoteColumnName($columnName),
+            $this->quoteColumnName($newColumnName),
+            $sql
+        );
+        $this->execute($sql);
+
+
+        $sql = sprintf(
+            'INSERT INTO %s(%s) SELECT %s FROM %s',
+            $tableName,
+            implode(', ', $writeColumns),
+            implode(', ', $selectColumns),
+            $tmpTableName
+        );
+
+        $this->execute($sql);
+
+        $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($tmpTableName)));
+        $this->endCommandTimer();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function changeColumn($tableName, $columnName, Column $newColumn)
+    {
+
+        // TODO: DRY this up....
+
+        $this->startCommandTimer();
+        $this->writeCommand('changeColumn', array($tableName, $columnName, $newColumn->getType()));
+
+        $tmpTableName = 'tmp_' . $tableName;
+
+        $rows = $this->fetchAll('select * from sqlite_master where `type` = \'table\'');
+
+        $sql = '';
+        foreach ($rows as $table) {
+            if ($table['tbl_name'] === $tableName) {
+                $sql = $table['sql'];
+            }
+        }
+
+        $columns = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($tableName)));
+        $selectColumns = array();
+        $writeColumns = array();
+        foreach ($columns as $column) {
+            $selectName = $column['name'];
+            $writeName = ($selectName === $columnName)? $newColumn->getName() : $selectName;
+            $selectColumns[] = $this->quoteColumnName($selectName);
+            $writeColumns[] = $this->quoteColumnName($writeName);
+        }
+
+        if (!in_array($this->quoteColumnName($columnName), $selectColumns)) {
+            throw new \InvalidArgumentException(sprintf(
+                'The specified column doesn\'t exist: ' . $columnName
+            ));
+        }
+
+        $this->execute(sprintf('ALTER TABLE %s RENAME TO %s', $tableName, $tmpTableName));
+
+        $sql = preg_replace(
+            sprintf("/%s(?:\/\*.*?\*\/|\([^)]+\)|'[^']+'|[^,])+([,)])/", $this->quoteColumnName($columnName)),
+            sprintf('%s %s$1', $this->quoteColumnName($newColumn->getName()), $this->getColumnSqlDefinition($newColumn)),
+            $sql,
+            1
+        );
+
+        $this->execute($sql);
+
+        $sql = sprintf(
+            'INSERT INTO %s(%s) SELECT %s FROM %s',
+            $tableName,
+            implode(', ', $writeColumns),
+            implode(', ', $selectColumns),
+            $tmpTableName
+        );
+
+        $this->execute($sql);
+        $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($tmpTableName)));
+        $this->endCommandTimer();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function dropColumn($tableName, $columnName)
+    {
+        // TODO: DRY this up....
+
+        $this->startCommandTimer();
+        $this->writeCommand('dropColumn', array($tableName, $columnName));
+
+        $tmpTableName = 'tmp_' . $tableName;
+
+        $rows = $this->fetchAll('select * from sqlite_master where `type` = \'table\'');
+
+        $sql = '';
+        foreach ($rows as $table) {
+            if ($table['tbl_name'] === $tableName) {
+                $sql = $table['sql'];
+            }
+        }
+
+        $rows = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($tableName)));
+        $columns = array();
+        $columnType = null;
+        foreach ($rows as $row) {
+            if ($row['name'] !== $columnName) {
+                $columns[] = $row['name'];
+            } else {
+                $found = true;
+                $columnType = $row['type'];
+            }
+        }
+
+        if (!isset($found)) {
+            throw new \InvalidArgumentException(sprintf(
+                'The specified column doesn\'t exist: ' . $columnName
+            ));
+        }
+
+        $this->execute(sprintf('ALTER TABLE %s RENAME TO %s', $tableName, $tmpTableName));
+
+        $sql = preg_replace(
+            sprintf("/%s\s%s[^,)]*(,\s|\))/", preg_quote($this->quoteColumnName($columnName)), preg_quote($columnType)),
+            "",
+            $sql
+        );
+
+        if (substr($sql, -2) === ', ') {
+            $sql = substr($sql, 0, -2) . ')';
+        }
+
+        $this->execute($sql);
+
+        $sql = sprintf(
+            'INSERT INTO %s(%s) SELECT %s FROM %s',
+            $tableName,
+            implode(', ', $columns),
+            implode(', ', $columns),
+            $tmpTableName
+        );
+
+        $this->execute($sql);
+        $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($tmpTableName)));
+        $this->endCommandTimer();
+    }
+
+    /**
+     * Get an array of indexes from a particular table.
+     *
+     * @param string $tableName Table Name
+     * @return array
+     */
+    protected function getIndexes($tableName)
+    {
+        $indexes = array();
+        $rows = $this->fetchAll(sprintf('pragma index_list(%s)', $tableName));
+
+        foreach ($rows as $row) {
+            $indexData = $this->fetchAll(sprintf('pragma index_info(%s)', $row['name']));
+            if (!isset($indexes[$tableName])) {
+                $indexes[$tableName] = array('index' => $row['name'], 'columns' => array());
+            }
+            foreach ($indexData as $indexItem) {
+                $indexes[$tableName]['columns'][] = strtolower($indexItem['name']);
+            }
+        }
+        return $indexes;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function hasIndex($tableName, $columns)
+    {
+        if (is_string($columns)) {
+            $columns = array($columns); // str to array
+        }
+
+        $columns = array_map('strtolower', $columns);
+        $indexes = $this->getIndexes($tableName);
+
+        foreach ($indexes as $index) {
+            $a = array_diff($columns, $index['columns']);
+            if (empty($a)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function hasIndexByName($tableName, $indexName)
+    {
+        $indexes = $this->getIndexes($tableName);
+
+        foreach ($indexes as $index) {
+            if ($indexName === $index['index']) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function addIndex(Table $table, Index $index)
+    {
+        $this->startCommandTimer();
+        $this->writeCommand('addIndex', array($table->getName(), $index->getColumns()));
+        $indexColumnArray = array();
+        foreach ($index->getColumns() as $column) {
+            $indexColumnArray []= sprintf('`%s` ASC', $column);
+        }
+        $indexColumns = implode(',', $indexColumnArray);
+        $this->execute(
+            sprintf(
+                'CREATE %s ON %s (%s)',
+                $this->getIndexSqlDefinition($table, $index),
+                $this->quoteTableName($table->getName()),
+                $indexColumns
+            )
+        );
+        $this->endCommandTimer();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function dropIndex($tableName, $columns)
+    {
+        $this->startCommandTimer();
+        if (is_string($columns)) {
+            $columns = array($columns); // str to array
+        }
+
+        $this->writeCommand('dropIndex', array($tableName, $columns));
+        $indexes = $this->getIndexes($tableName);
+        $columns = array_map('strtolower', $columns);
+
+        foreach ($indexes as $index) {
+            $a = array_diff($columns, $index['columns']);
+            if (empty($a)) {
+                $this->execute(
+                    sprintf(
+                        'DROP INDEX %s',
+                        $this->quoteColumnName($index['index'])
+                    )
+                );
+                $this->endCommandTimer();
+                return;
+            }
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function dropIndexByName($tableName, $indexName)
+    {
+        $this->startCommandTimer();
+
+        $this->writeCommand('dropIndexByName', array($tableName, $indexName));
+        $indexes = $this->getIndexes($tableName);
+
+        foreach ($indexes as $index) {
+            if ($indexName === $index['index']) {
+                $this->execute(
+                    sprintf(
+                        'DROP INDEX %s',
+                        $this->quoteColumnName($indexName)
+                    )
+                );
+                $this->endCommandTimer();
+                return;
+            }
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function hasForeignKey($tableName, $columns, $constraint = null)
+    {
+        if (is_string($columns)) {
+            $columns = array($columns); // str to array
+        }
+        $foreignKeys = $this->getForeignKeys($tableName);
+
+        $a = array_diff($columns, $foreignKeys);
+        if (empty($a)) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Get an array of foreign keys from a particular table.
+     *
+     * @param string $tableName Table Name
+     * @return array
+     */
+    protected function getForeignKeys($tableName)
+    {
+        $foreignKeys = array();
+        $rows = $this->fetchAll(
+            "SELECT sql, tbl_name
+              FROM (
+                    SELECT sql sql, type type, tbl_name tbl_name, name name
+                      FROM sqlite_master
+                     UNION ALL
+                    SELECT sql, type, tbl_name, name
+                      FROM sqlite_temp_master
+                   )
+             WHERE type != 'meta'
+               AND sql NOTNULL
+               AND name NOT LIKE 'sqlite_%'
+             ORDER BY substr(type, 2, 1), name"
+        );
+
+        foreach ($rows as $row) {
+            if ($row['tbl_name'] === $tableName) {
+
+                if (strpos($row['sql'], 'REFERENCES') !== false) {
+                    preg_match_all("/\(`([^`]*)`\) REFERENCES/", $row['sql'], $matches);
+                    foreach ($matches[1] as $match) {
+                        $foreignKeys[] = $match;
+                    }
+                }
+            }
+        }
+        return $foreignKeys;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function addForeignKey(Table $table, ForeignKey $foreignKey)
+    {
+        // TODO: DRY this up....
+        $this->startCommandTimer();
+        $this->writeCommand('addForeignKey', array($table->getName(), $foreignKey->getColumns()));
+        $this->execute('pragma foreign_keys = ON');
+
+        $tmpTableName = 'tmp_' . $table->getName();
+        $rows = $this->fetchAll('select * from sqlite_master where `type` = \'table\'');
+
+        $sql = '';
+        foreach ($rows as $row) {
+            if ($row['tbl_name'] === $table->getName()) {
+                $sql = $row['sql'];
+            }
+        }
+
+        $rows = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($table->getName())));
+        $columns = array();
+        foreach ($rows as $column) {
+            $columns[] = $this->quoteColumnName($column['name']);
+        }
+
+        $this->execute(sprintf('ALTER TABLE %s RENAME TO %s', $this->quoteTableName($table->getName()), $tmpTableName));
+
+        $sql = substr($sql, 0, -1) . ',' . $this->getForeignKeySqlDefinition($foreignKey) . ')';
+        $this->execute($sql);
+
+        $sql = sprintf(
+            'INSERT INTO %s(%s) SELECT %s FROM %s',
+            $this->quoteTableName($table->getName()),
+            implode(', ', $columns),
+            implode(', ', $columns),
+            $this->quoteTableName($tmpTableName)
+        );
+
+        $this->execute($sql);
+        $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($tmpTableName)));
+        $this->endCommandTimer();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function dropForeignKey($tableName, $columns, $constraint = null)
+    {
+        // TODO: DRY this up....
+
+        $this->startCommandTimer();
+        if (is_string($columns)) {
+            $columns = array($columns); // str to array
+        }
+
+        $this->writeCommand('dropForeignKey', array($tableName, $columns));
+
+        $tmpTableName = 'tmp_' . $tableName;
+
+        $rows = $this->fetchAll('select * from sqlite_master where `type` = \'table\'');
+
+        $sql = '';
+        foreach ($rows as $table) {
+            if ($table['tbl_name'] === $tableName) {
+                $sql = $table['sql'];
+            }
+        }
+
+        $rows = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($tableName)));
+        $replaceColumns = array();
+        foreach ($rows as $row) {
+            if (!in_array($row['name'], $columns)) {
+                $replaceColumns[] = $row['name'];
+            } else {
+                $found = true;
+            }
+        }
+
+        if (!isset($found)) {
+            throw new \InvalidArgumentException(sprintf(
+                'The specified column doesn\'t exist: '
+            ));
+        }
+
+        $this->execute(sprintf('ALTER TABLE %s RENAME TO %s', $this->quoteTableName($tableName), $tmpTableName));
+
+        foreach ($columns as $columnName) {
+            $search = sprintf(
+                "/,[^,]*\(%s(?:,`?(.*)`?)?\) REFERENCES[^,]*\([^\)]*\)[^,)]*/",
+                $this->quoteColumnName($columnName)
+            );
+            $sql = preg_replace($search, '', $sql, 1);
+        }
+
+        $this->execute($sql);
+
+        $sql = sprintf(
+            'INSERT INTO %s(%s) SELECT %s FROM %s',
+            $tableName,
+            implode(', ', $columns),
+            implode(', ', $columns),
+            $tmpTableName
+        );
+
+        $this->execute($sql);
+        $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($tmpTableName)));
+        $this->endCommandTimer();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function insert(Table $table, $row)
+    {
+        $this->startCommandTimer();
+        $this->writeCommand('insert', array($table->getName()));
+
+        $sql = sprintf(
+            "INSERT INTO %s ",
+            $this->quoteTableName($table->getName())
+        );
+
+        $columns = array_keys($row);
+        $sql .= "(". implode(', ', array_map(array($this, 'quoteColumnName'), $columns)) . ")";
+        $sql .= " VALUES ";
+
+        $sql .= "(" . implode(', ', array_map(function ($value) {
+                if (is_numeric($value)) {
+                    return $value;
+                }
+
+                if ($value === null) {
+                    return 'null';
+                }
+
+                return $this->getConnection()->quote($value);
+            }, $row)) . ")";
+
+        $this->execute($sql);
+        $this->endCommandTimer();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getSqlType($type, $limit = null)
+    {
+        switch ($type) {
+            case static::PHINX_TYPE_STRING:
+                return array('name' => 'varchar', 'limit' => 255);
+                break;
+            case static::PHINX_TYPE_CHAR:
+                return array('name' => 'char', 'limit' => 255);
+                break;
+            case static::PHINX_TYPE_TEXT:
+                return array('name' => 'text');
+                break;
+            case static::PHINX_TYPE_INTEGER:
+                return array('name' => 'integer');
+                break;
+            case static::PHINX_TYPE_BIG_INTEGER:
+                return array('name' => 'bigint');
+                break;
+            case static::PHINX_TYPE_FLOAT:
+                return array('name' => 'float');
+                break;
+            case static::PHINX_TYPE_DECIMAL:
+                return array('name' => 'decimal');
+                break;
+            case static::PHINX_TYPE_DATETIME:
+                return array('name' => 'datetime');
+                break;
+            case static::PHINX_TYPE_TIMESTAMP:
+                return array('name' => 'datetime');
+                break;
+            case static::PHINX_TYPE_TIME:
+                return array('name' => 'time');
+                break;
+            case static::PHINX_TYPE_DATE:
+                return array('name' => 'date');
+                break;
+            case static::PHINX_TYPE_BLOB:
+            case static::PHINX_TYPE_BINARY:
+                return array('name' => 'blob');
+                break;
+            case static::PHINX_TYPE_BOOLEAN:
+                return array('name' => 'boolean');
+                break;
+            case static::PHINX_TYPE_UUID:
+                return array('name' => 'char', 'limit' => 36);
+            case static::PHINX_TYPE_ENUM:
+                return array('name' => 'enum');
+            // Geospatial database types
+            // No specific data types exist in SQLite, instead all geospatial
+            // functionality is handled in the client. See also: SpatiaLite.
+            case static::PHINX_TYPE_GEOMETRY:
+            case static::PHINX_TYPE_POLYGON:
+                return array('name' => 'text');
+                return;
+            case static::PHINX_TYPE_LINESTRING:
+                return array('name' => 'varchar', 'limit' => 255);
+                break;
+            case static::PHINX_TYPE_POINT:
+                return array('name' => 'float');
+            default:
+                throw new \RuntimeException('The type: "' . $type . '" is not supported.');
+        }
+    }
+
+    /**
+     * Returns Phinx type by SQL type
+     *
+     * @param string $sqlTypeDef SQL type
+     * @returns string Phinx type
+     */
+    public function getPhinxType($sqlTypeDef)
+    {
+        if (!preg_match('/^([\w]+)(\(([\d]+)*(,([\d]+))*\))*$/', $sqlTypeDef, $matches)) {
+            throw new \RuntimeException('Column type ' . $sqlTypeDef . ' is not supported');
+        } else {
+            $limit = null;
+            $precision = null;
+            $type = $matches[1];
+            if (count($matches) > 2) {
+                $limit = $matches[3] ? $matches[3] : null;
+            }
+            if (count($matches) > 4) {
+                $precision = $matches[5];
+            }
+            switch ($matches[1]) {
+                case 'varchar':
+                    $type = static::PHINX_TYPE_STRING;
+                    if ($limit === 255) {
+                        $limit = null;
+                    }
+                    break;
+                case 'char':
+                    $type = static::PHINX_TYPE_CHAR;
+                    if ($limit === 255) {
+                        $limit = null;
+                    }
+                    if ($limit === 36) {
+                        $type = static::PHINX_TYPE_UUID;
+                    }
+                    break;
+                case 'int':
+                    $type = static::PHINX_TYPE_INTEGER;
+                    if ($limit === 11) {
+                        $limit = null;
+                    }
+                    break;
+                case 'bigint':
+                    if ($limit === 11) {
+                        $limit = null;
+                    }
+                    $type = static::PHINX_TYPE_BIG_INTEGER;
+                    break;
+                case 'blob':
+                    $type = static::PHINX_TYPE_BINARY;
+                    break;
+            }
+            if ($type === 'tinyint') {
+                if ($matches[3] === 1) {
+                    $type = static::PHINX_TYPE_BOOLEAN;
+                    $limit = null;
+                }
+            }
+
+            $this->getSqlType($type);
+
+            return array(
+                'name' => $type,
+                'limit' => $limit,
+                'precision' => $precision
+            );
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function createDatabase($name, $options = array())
+    {
+        $this->startCommandTimer();
+        $this->writeCommand('createDatabase', array($name));
+        touch($name . '.sqlite3');
+        $this->endCommandTimer();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function hasDatabase($name)
+    {
+        return is_file($name . '.sqlite3');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function dropDatabase($name)
+    {
+        $this->startCommandTimer();
+        $this->writeCommand('dropDatabase', array($name));
+        if (file_exists($name . '.sqlite3')) {
+            unlink($name . '.sqlite3');
+        }
+        $this->endCommandTimer();
+    }
+
+    /**
+     * Get the definition for a `DEFAULT` statement.
+     *
+     * @param  mixed $default
+     * @return string
+     */
+    protected function getDefaultValueDefinition($default)
+    {
+        if (is_string($default) && 'CURRENT_TIMESTAMP' !== $default) {
+            $default = $this->getConnection()->quote($default);
+        } elseif (is_bool($default)) {
+            $default = $this->castToBool($default);
+        }
+        return isset($default) ? ' DEFAULT ' . $default : '';
+    }
+
+    /**
+     * Gets the SQLite Column Definition for a Column object.
+     *
+     * @param Column $column Column
+     * @return string
+     */
+    protected function getColumnSqlDefinition(Column $column)
+    {
+        $sqlType = $this->getSqlType($column->getType());
+        $def = '';
+        $def .= strtoupper($sqlType['name']);
+        if ($column->getPrecision() && $column->getScale()) {
+            $def .= '(' . $column->getPrecision() . ',' . $column->getScale() . ')';
+        }
+        $limitable = in_array(strtoupper($sqlType['name']), $this->definitionsWithLimits);
+        if (($column->getLimit() || isset($sqlType['limit'])) && $limitable) {
+            $def .= '(' . ($column->getLimit() ? $column->getLimit() : $sqlType['limit']) . ')';
+        }
+        if (($values = $column->getValues()) && is_array($values)) {
+            $def .= " CHECK({$column->getName()} IN ('" . implode("', '", $values) . "'))";
+        }
+
+        $default = $column->getDefault();
+
+        $def .= ($column->isNull() || is_null($default)) ? ' NULL' : ' NOT NULL';
+        $def .= $this->getDefaultValueDefinition($default);
+        $def .= ($column->isIdentity()) ? ' PRIMARY KEY AUTOINCREMENT' : '';
+
+        if ($column->getUpdate()) {
+            $def .= ' ON UPDATE ' . $column->getUpdate();
+        }
+
+        $def .= $this->getCommentDefinition($column);
+
+        return $def;
+    }
+
+    /**
+     * Gets the comment Definition for a Column object.
+     *
+     * @param Column $column Column
+     * @return string
+     */
+    protected function getCommentDefinition(Column $column)
+    {
+        if ($column->getComment()) {
+            return ' /* ' . $column->getComment() . ' */ ';
+        }
+        return '';
+    }
+
+    /**
+     * Gets the SQLite Index Definition for an Index object.
+     *
+     * @param Index $index Index
+     * @return string
+     */
+    protected function getIndexSqlDefinition(Table $table, Index $index)
+    {
+        if ($index->getType() === Index::UNIQUE) {
+            $def = 'UNIQUE INDEX';
+        } else {
+            $def = 'INDEX';
+        }
+        if (is_string($index->getName())) {
+            $indexName = $index->getName();
+        } else {
+            $indexName = $table->getName() . '_';
+            foreach ($index->getColumns() as $column) {
+                $indexName .= $column . '_';
+            }
+            $indexName .= 'index';
+        }
+        $def .= ' `' . $indexName . '`';
+        return $def;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getColumnTypes()
+    {
+        return array_merge(parent::getColumnTypes(), array('enum'));
+    }
+
+    /**
+     * Gets the SQLite Foreign Key Definition for an ForeignKey object.
+     *
+     * @param ForeignKey $foreignKey
+     * @return string
+     */
+    protected function getForeignKeySqlDefinition(ForeignKey $foreignKey)
+    {
+        $def = '';
+        if ($foreignKey->getConstraint()) {
+            $def .= ' CONSTRAINT ' . $this->quoteColumnName($foreignKey->getConstraint());
+        } else {
+            $columnNames = array();
+            foreach ($foreignKey->getColumns() as $column) {
+                $columnNames[] = $this->quoteColumnName($column);
+            }
+            $def .= ' FOREIGN KEY (' . implode(',', $columnNames) . ')';
+            $refColumnNames = array();
+            foreach ($foreignKey->getReferencedColumns() as $column) {
+                $refColumnNames[] = $this->quoteColumnName($column);
+            }
+            $def .= ' REFERENCES ' . $this->quoteTableName($foreignKey->getReferencedTable()->getName()) . ' (' . implode(',', $refColumnNames) . ')';
+            if ($foreignKey->getOnDelete()) {
+                $def .= ' ON DELETE ' . $foreignKey->getOnDelete();
+            }
+            if ($foreignKey->getOnUpdate()) {
+                $def .= ' ON UPDATE ' . $foreignKey->getOnUpdate();
+            }
+        }
+        return $def;
+    }
+}

+ 1166 - 0
src/DDLWrapper/Db/Adapter/SqlServerAdapter.php

@@ -0,0 +1,1166 @@
+<?php
+/**
+ * Phinx
+ *
+ * (The MIT license)
+ * Copyright (c) 2015 Rob Morgan
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated * documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ *
+ * @package    Phinx
+ * @subpackage Phinx\Db\Adapter
+ */
+namespace DDLWrapper\Db\Adapter;
+
+use DDLWrapper\Db\Table;
+use DDLWrapper\Db\Table\Column;
+use DDLWrapper\Db\Table\Index;
+use DDLWrapper\Db\Table\ForeignKey;
+
+/**
+ * Phinx SqlServer Adapter.
+ *
+ * @author Rob Morgan <robbym@gmail.com>
+ */
+class SqlServerAdapter extends PdoAdapter implements AdapterInterface
+{
+    protected $schema = 'dbo';
+
+    protected $signedColumnTypes = array('integer' => true, 'biginteger' => true, 'float' => true, 'decimal' => true);
+
+    /**
+     * {@inheritdoc}
+     */
+    public function connect()
+    {
+        if (null === $this->connection) {
+            if (!class_exists('PDO') || !in_array('sqlsrv', \PDO::getAvailableDrivers(), true)) {
+                // try our connection via freetds (Mac/Linux)
+                return $this->connectDblib();
+            }
+
+            $db = null;
+            $options = $this->getOptions();
+
+            // if port is specified use it, otherwise use the SqlServer default
+            if (empty($options['port'])) {
+                $dsn = 'sqlsrv:server=' . $options['host'] . ';database=' . $options['name'];
+            } else {
+                $dsn = 'sqlsrv:server=' . $options['host'] . ',' . $options['port'] . ';database=' . $options['name'];
+            }
+            $dsn .= ';MultipleActiveResultSets=false';
+
+            $driverOptions = array(\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION);
+
+            // charset support
+            if (isset($options['charset'])) {
+                $driverOptions[\PDO::SQLSRV_ATTR_ENCODING] = $options['charset'];
+            }
+
+            // support arbitrary \PDO::SQLSRV_ATTR_* driver options and pass them to PDO
+            // http://php.net/manual/en/ref.pdo-sqlsrv.php#pdo-sqlsrv.constants
+            foreach ($options as $key => $option) {
+                if (strpos($key, 'sqlsrv_attr_') === 0) {
+                    $driverOptions[constant('\PDO::' . strtoupper($key))] = $option;
+                }
+            }
+
+            try {
+                $db = new \PDO($dsn, $options['user'], $options['pass'], $driverOptions);
+            } catch (\PDOException $exception) {
+                throw new \InvalidArgumentException(sprintf(
+                    'There was a problem connecting to the database: %s',
+                    $exception->getMessage()
+                ));
+            }
+
+            $this->setConnection($db);
+        }
+    }
+
+    /**
+     * Connect to MSSQL using dblib/freetds.
+     *
+     * The "sqlsrv" driver is not available on Unix machines.
+     *
+     * @throws \InvalidArgumentException
+     */
+    protected function connectDblib()
+    {
+        if (!class_exists('PDO') || !in_array('dblib', \PDO::getAvailableDrivers(), true)) {
+            // @codeCoverageIgnoreStart
+            throw new \RuntimeException('You need to enable the PDO_Dblib extension for Phinx to run properly.');
+            // @codeCoverageIgnoreEnd
+        }
+
+        $options = $this->getOptions();
+
+        // if port is specified use it, otherwise use the SqlServer default
+        if (empty($options['port'])) {
+            $dsn = 'dblib:host=' . $options['host'] . ';dbname=' . $options['name'];
+        } else {
+            $dsn = 'dblib:host=' . $options['host'] . ':' . $options['port'] . ';dbname=' . $options['name'];
+        }
+
+        $driverOptions = array(\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION);
+
+
+        try {
+            $db = new \PDO($dsn, $options['user'], $options['pass'], $driverOptions);
+        } catch (\PDOException $exception) {
+            throw new \InvalidArgumentException(sprintf(
+                'There was a problem connecting to the database: %s',
+                $exception->getMessage()
+            ));
+        }
+
+        $this->setConnection($db);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function disconnect()
+    {
+        $this->connection = null;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function hasTransactions()
+    {
+        return true;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function beginTransaction()
+    {
+        $this->execute('BEGIN TRANSACTION');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function commitTransaction()
+    {
+        $this->execute('COMMIT TRANSACTION');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function rollbackTransaction()
+    {
+        $this->execute('ROLLBACK TRANSACTION');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function quoteTableName($tableName)
+    {
+        return str_replace('.', '].[', $this->quoteColumnName($tableName));
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function quoteColumnName($columnName)
+    {
+        return '[' . str_replace(']', '\]', $columnName) . ']';
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function hasTable($tableName)
+    {
+        $result = $this->fetchRow(sprintf('SELECT count(*) as [count] FROM information_schema.tables WHERE table_name = \'%s\';', $tableName));
+        return $result['count'] > 0;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function createTable(Table $table)
+    {
+        $this->startCommandTimer();
+
+        $options = $table->getOptions();
+
+        // Add the default primary key
+        $columns = $table->getPendingColumns();
+        if (!isset($options['id']) || (isset($options['id']) && $options['id'] === true)) {
+            $column = new Column();
+            $column->setName('id')
+                   ->setType('integer')
+                   ->setIdentity(true);
+
+            array_unshift($columns, $column);
+            $options['primary_key'] = 'id';
+
+        } elseif (isset($options['id']) && is_string($options['id'])) {
+            // Handle id => "field_name" to support AUTO_INCREMENT
+            $column = new Column();
+            $column->setName($options['id'])
+                   ->setType('integer')
+                   ->setIdentity(true);
+
+            array_unshift($columns, $column);
+            $options['primary_key'] = $options['id'];
+        }
+
+        $sql = 'CREATE TABLE ';
+        $sql .= $this->quoteTableName($table->getName()) . ' (';
+        $sqlBuffer = array();
+        $columnsWithComments = array();
+        foreach ($columns as $column) {
+            $sqlBuffer[] = $this->quoteColumnName($column->getName()) . ' ' . $this->getColumnSqlDefinition($column);
+
+            // set column comments, if needed
+            if ($column->getComment()) {
+                $columnsWithComments[] = $column;
+            }
+        }
+
+        // set the primary key(s)
+        if (isset($options['primary_key'])) {
+            $pkSql = sprintf('CONSTRAINT PK_%s PRIMARY KEY (', $table->getName());
+            if (is_string($options['primary_key'])) { // handle primary_key => 'id'
+                $pkSql .= $this->quoteColumnName($options['primary_key']);
+            } elseif (is_array($options['primary_key'])) { // handle primary_key => array('tag_id', 'resource_id')
+                // PHP 5.4 will allow access of $this, so we can call quoteColumnName() directly in the anonymous function,
+                // but for now just hard-code the adapter quotes
+                $pkSql .= implode(
+                    ',',
+                    array_map(
+                        function ($v) {
+                            return '[' . $v . ']';
+                        },
+                        $options['primary_key']
+                    )
+                );
+            }
+            $pkSql .= ')';
+            $sqlBuffer[] = $pkSql;
+        }
+
+        // set the foreign keys
+        $foreignKeys = $table->getForeignKeys();
+        if (!empty($foreignKeys)) {
+            foreach ($foreignKeys as $foreignKey) {
+                $sqlBuffer[] = $this->getForeignKeySqlDefinition($foreignKey, $table->getName());
+            }
+        }
+
+        $sql .= implode(', ', $sqlBuffer);
+        $sql .= ');';
+
+        // process column comments
+        if (!empty($columnsWithComments)) {
+            foreach ($columnsWithComments as $column) {
+                $sql .= $this->getColumnCommentSqlDefinition($column, $table->getName());
+            }
+        }
+
+        // set the indexes
+        $indexes = $table->getIndexes();
+        if (!empty($indexes)) {
+            foreach ($indexes as $index) {
+                $sql .= $this->getIndexSqlDefinition($index, $table->getName());
+            }
+        }
+
+        // execute the sql
+        $this->writeCommand('createTable', array($table->getName()));
+        $this->execute($sql);
+        $this->endCommandTimer();
+    }
+
+    /**
+     * Gets the SqlServer Column Comment Defininition for a column object.
+     *
+     * @param Column $column    Column
+     * @param string $tableName Table name
+     *
+     * @return string
+     */
+    protected function getColumnCommentSqlDefinition(Column $column, $tableName)
+    {
+        // passing 'null' is to remove column comment
+        $currentComment = $this->getColumnComment($tableName, $column->getName());
+
+        $comment = (strcasecmp($column->getComment(), 'NULL') !== 0) ? $this->getConnection()->quote($column->getComment()) : '\'\'';
+        $command = $currentComment === false ? 'sp_addextendedproperty' : 'sp_updateextendedproperty';
+        return sprintf(
+            "EXECUTE %s N'MS_Description', N%s, N'SCHEMA', N'%s', N'TABLE', N'%s', N'COLUMN', N'%s';",
+            $command,
+            $comment,
+            $this->schema,
+            $tableName,
+            $column->getName()
+        );
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function renameTable($tableName, $newTableName)
+    {
+        $this->startCommandTimer();
+        $this->writeCommand('renameTable', array($tableName, $newTableName));
+        $this->execute(sprintf('EXEC sp_rename \'%s\', \'%s\'', $tableName, $newTableName));
+        $this->endCommandTimer();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function dropTable($tableName)
+    {
+        $this->startCommandTimer();
+        $this->writeCommand('dropTable', array($tableName));
+        $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($tableName)));
+        $this->endCommandTimer();
+    }
+
+    public function getColumnComment($tableName, $columnName)
+    {
+        $sql = sprintf("SELECT cast(extended_properties.[value] as nvarchar(4000)) comment
+  FROM sys.schemas
+ INNER JOIN sys.tables
+    ON schemas.schema_id = tables.schema_id
+ INNER JOIN sys.columns
+    ON tables.object_id = columns.object_id
+ INNER JOIN sys.extended_properties
+    ON tables.object_id = extended_properties.major_id
+   AND columns.column_id = extended_properties.minor_id
+   AND extended_properties.name = 'MS_Description'
+   WHERE schemas.[name] = '%s' AND tables.[name] = '%s' AND columns.[name] = '%s'", $this->schema, $tableName, $columnName);
+        $row = $this->fetchRow($sql);
+
+        if ($row) {
+            return $row['comment'];
+        }
+
+        return false;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getColumns($tableName)
+    {
+        $columns = array();
+        $sql = sprintf(
+            "SELECT DISTINCT TABLE_SCHEMA AS [schema], TABLE_NAME as [table_name], COLUMN_NAME AS [name], DATA_TYPE AS [type],
+            IS_NULLABLE AS [null], COLUMN_DEFAULT AS [default],
+            CHARACTER_MAXIMUM_LENGTH AS [char_length],
+            NUMERIC_PRECISION AS [precision],
+            NUMERIC_SCALE AS [scale], ORDINAL_POSITION AS [ordinal_position],
+            COLUMNPROPERTY(object_id(TABLE_NAME), COLUMN_NAME, 'IsIdentity') as [identity]
+        FROM INFORMATION_SCHEMA.COLUMNS
+        WHERE TABLE_NAME = '%s'
+        ORDER BY ordinal_position",
+            $tableName
+        );
+        $rows = $this->fetchAll($sql);
+        foreach ($rows as $columnInfo) {
+            $column = new Column();
+            $column->setName($columnInfo['name'])
+                   ->setType($this->getPhinxType($columnInfo['type']))
+                   ->setNull($columnInfo['null'] !== 'NO')
+                   ->setDefault($this->parseDefault($columnInfo['default']))
+                   ->setIdentity($columnInfo['identity'] === '1')
+                   ->setComment($this->getColumnComment($columnInfo['table_name'], $columnInfo['name']));
+
+            if (!empty($columnInfo['char_length'])) {
+                $column->setLimit($columnInfo['char_length']);
+            }
+
+            $columns[$columnInfo['name']] = $column;
+        }
+
+        return $columns;
+    }
+
+    protected function parseDefault($default)
+    {
+        $default = preg_replace(array("/\('(.*)'\)/", "/\(\((.*)\)\)/", "/\((.*)\)/"), '$1', $default);
+
+        if (strtoupper($default) === 'NULL') {
+            $default = null;
+        } elseif (is_numeric($default)) {
+            $default = (int) $default;
+        }
+
+        return $default;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function hasColumn($tableName, $columnName, $options = array())
+    {
+        $sql = sprintf(
+            "SELECT count(*) as [count]
+             FROM information_schema.columns
+             WHERE table_name = '%s' AND column_name = '%s'",
+            $tableName,
+            $columnName
+        );
+        $result = $this->fetchRow($sql);
+
+        return $result['count'] > 0;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function addColumn(Table $table, Column $column)
+    {
+        $this->startCommandTimer();
+        $sql = sprintf(
+            'ALTER TABLE %s ADD %s %s',
+            $this->quoteTableName($table->getName()),
+            $this->quoteColumnName($column->getName()),
+            $this->getColumnSqlDefinition($column)
+        );
+
+        $this->writeCommand('addColumn', array($table->getName(), $column->getName(), $column->getType()));
+        $this->execute($sql);
+        $this->endCommandTimer();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function renameColumn($tableName, $columnName, $newColumnName)
+    {
+        $this->startCommandTimer();
+
+        if (!$this->hasColumn($tableName, $columnName)) {
+            throw new \InvalidArgumentException("The specified column does not exist: $columnName");
+        }
+        $this->writeCommand('renameColumn', array($tableName, $columnName, $newColumnName));
+        $this->renameDefault($tableName, $columnName, $newColumnName);
+        $this->execute(
+             sprintf(
+                 "EXECUTE sp_rename N'%s.%s', N'%s', 'COLUMN' ",
+                 $tableName,
+                 $columnName,
+                 $newColumnName
+             )
+        );
+        $this->endCommandTimer();
+    }
+
+    protected function renameDefault($tableName, $columnName, $newColumnName)
+    {
+        $oldConstraintName = "DF_{$tableName}_{$columnName}";
+        $newConstraintName = "DF_{$tableName}_{$newColumnName}";
+        $sql = <<<SQL
+IF (OBJECT_ID('$oldConstraintName', 'D') IS NOT NULL)
+BEGIN
+     EXECUTE sp_rename N'%s', N'%s', N'OBJECT'
+END
+SQL;
+        $this->execute(sprintf(
+            $sql,
+            $oldConstraintName,
+            $newConstraintName
+        ));
+    }
+
+    public function changeDefault($tableName, Column $newColumn)
+    {
+        $constraintName = "DF_{$tableName}_{$newColumn->getName()}";
+        $default = $newColumn->getDefault();
+
+        if ($default === null) {
+            $default = 'DEFAULT NULL';
+        } else {
+            $default = $this->getDefaultValueDefinition($default);
+        }
+
+        if (empty($default)) {
+            return;
+        }
+
+        $this->execute(sprintf(
+            'ALTER TABLE %s ADD CONSTRAINT %s %s FOR %s',
+            $this->quoteTableName($tableName),
+            $constraintName,
+            $default,
+            $this->quoteColumnName($newColumn->getName())
+        ));
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function changeColumn($tableName, $columnName, Column $newColumn)
+    {
+        $this->startCommandTimer();
+        $this->writeCommand('changeColumn', array($tableName, $columnName, $newColumn->getType()));
+        $columns = $this->getColumns($tableName);
+        $changeDefault = $newColumn->getDefault() !== $columns[$columnName]->getDefault() || $newColumn->getType() !== $columns[$columnName]->getType();
+        if ($columnName !== $newColumn->getName()) {
+            $this->renameColumn($tableName, $columnName, $newColumn->getName());
+        }
+
+        if ($changeDefault) {
+            $this->dropDefaultConstraint($tableName, $newColumn->getName());
+        }
+
+        $this->execute(
+            sprintf(
+                'ALTER TABLE %s ALTER COLUMN %s %s',
+                $this->quoteTableName($tableName),
+                $this->quoteColumnName($newColumn->getName()),
+                $this->getColumnSqlDefinition($newColumn, false)
+            )
+        );
+        // change column comment if needed
+        if ($newColumn->getComment()) {
+            $sql = $this->getColumnCommentSqlDefinition($newColumn, $tableName);
+            $this->execute($sql);
+        }
+
+        if ($changeDefault) {
+            $this->changeDefault($tableName, $newColumn);
+        }
+        $this->endCommandTimer();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function dropColumn($tableName, $columnName)
+    {
+        $this->startCommandTimer();
+        $this->writeCommand('dropColumn', array($tableName, $columnName));
+        $this->dropDefaultConstraint($tableName, $columnName);
+
+        $this->execute(
+            sprintf(
+                'ALTER TABLE %s DROP COLUMN %s',
+                $this->quoteTableName($tableName),
+                $this->quoteColumnName($columnName)
+            )
+        );
+        $this->endCommandTimer();
+    }
+
+    protected function dropDefaultConstraint($tableName, $columnName)
+    {
+        $defaultConstraint = $this->getDefaultConstraint($tableName, $columnName);
+
+        if (!$defaultConstraint) {
+            return;
+        }
+
+        $this->dropForeignKey($tableName, $columnName, $defaultConstraint);
+    }
+
+    protected function getDefaultConstraint($tableName, $columnName)
+    {
+        $sql = "SELECT
+    default_constraints.name
+FROM
+    sys.all_columns
+
+        INNER JOIN
+    sys.tables
+        ON all_columns.object_id = tables.object_id
+
+        INNER JOIN
+    sys.schemas
+        ON tables.schema_id = schemas.schema_id
+
+        INNER JOIN
+    sys.default_constraints
+        ON all_columns.default_object_id = default_constraints.object_id
+
+WHERE
+        schemas.name = 'dbo'
+    AND tables.name = '{$tableName}'
+    AND all_columns.name = '{$columnName}'";
+
+        $rows = $this->fetchAll($sql);
+        return empty($rows) ? false : $rows[0]['name'];
+    }
+
+    protected function getIndexColums($tableId, $indexId)
+    {
+        $sql = "SELECT AC.[name] AS [column_name]
+FROM sys.[index_columns] IC
+  INNER JOIN sys.[all_columns] AC ON IC.[column_id] = AC.[column_id]
+WHERE AC.[object_id] = {$tableId} AND IC.[index_id] = {$indexId}  AND IC.[object_id] = {$tableId}
+ORDER BY IC.[key_ordinal];";
+
+        $rows = $this->fetchAll($sql);
+        $columns = array();
+        foreach($rows as $row) {
+            $columns[] = strtolower($row['column_name']);
+        }
+        return $columns;
+    }
+
+    /**
+     * Get an array of indexes from a particular table.
+     *
+     * @param string $tableName Table Name
+     * @return array
+     */
+    public function getIndexes($tableName)
+    {
+        $indexes = array();
+        $sql = "SELECT I.[name] AS [index_name], I.[index_id] as [index_id], T.[object_id] as [table_id]
+FROM sys.[tables] AS T
+  INNER JOIN sys.[indexes] I ON T.[object_id] = I.[object_id]
+WHERE T.[is_ms_shipped] = 0 AND I.[type_desc] <> 'HEAP'  AND T.[name] = '{$tableName}'
+ORDER BY T.[name], I.[index_id];";
+
+        $rows = $this->fetchAll($sql);
+        foreach ($rows as $row) {
+            $columns = $this->getIndexColums($row['table_id'], $row['index_id']);
+            $indexes[$row['index_name']] = array('columns' => $columns);
+        }
+
+        return $indexes;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function hasIndex($tableName, $columns)
+    {
+        if (is_string($columns)) {
+            $columns = array($columns); // str to array
+        }
+
+        $columns = array_map('strtolower', $columns);
+        $indexes = $this->getIndexes($tableName);
+
+        foreach ($indexes as $index) {
+            $a = array_diff($columns, $index['columns']);
+
+            if (empty($a)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function hasIndexByName($tableName, $indexName)
+    {
+        $indexes = $this->getIndexes($tableName);
+
+        foreach ($indexes as $name => $index) {
+            if ($name === $indexName) {
+                 return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function addIndex(Table $table, Index $index)
+    {
+        $this->startCommandTimer();
+        $this->writeCommand('addIndex', array($table->getName(), $index->getColumns()));
+        $sql = $this->getIndexSqlDefinition($index, $table->getName());
+        $this->execute($sql);
+        $this->endCommandTimer();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function dropIndex($tableName, $columns)
+    {
+        $this->startCommandTimer();
+        if (is_string($columns)) {
+            $columns = array($columns); // str to array
+        }
+
+        $this->writeCommand('dropIndex', array($tableName, $columns));
+        $indexes = $this->getIndexes($tableName);
+        $columns = array_map('strtolower', $columns);
+
+        foreach ($indexes as $indexName => $index) {
+            $a = array_diff($columns, $index['columns']);
+            if (empty($a)) {
+                $this->execute(
+                    sprintf(
+                        'DROP INDEX %s ON %s',
+                        $this->quoteColumnName($indexName),
+                        $this->quoteTableName($tableName)
+                    )
+                );
+                $this->endCommandTimer();
+                return;
+            }
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function dropIndexByName($tableName, $indexName)
+    {
+        $this->startCommandTimer();
+
+        $this->writeCommand('dropIndexByName', array($tableName, $indexName));
+        $indexes = $this->getIndexes($tableName);
+
+        foreach ($indexes as $name => $index) {
+            if ($name === $indexName) {
+                $this->execute(
+                    sprintf(
+                        'DROP INDEX %s ON %s',
+                        $this->quoteColumnName($indexName),
+                        $this->quoteTableName($tableName)
+                    )
+                );
+                $this->endCommandTimer();
+                return;
+            }
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function hasForeignKey($tableName, $columns, $constraint = null)
+    {
+        if (is_string($columns)) {
+            $columns = array($columns); // str to array
+        }
+        $foreignKeys = $this->getForeignKeys($tableName);
+        if ($constraint) {
+            if (isset($foreignKeys[$constraint])) {
+                return !empty($foreignKeys[$constraint]);
+            }
+            return false;
+        } else {
+            foreach ($foreignKeys as $key) {
+                $a = array_diff($columns, $key['columns']);
+                if (empty($a)) {
+                    return true;
+                }
+            }
+            return false;
+        }
+    }
+
+    /**
+     * Get an array of foreign keys from a particular table.
+     *
+     * @param string $tableName Table Name
+     * @return array
+     */
+    protected function getForeignKeys($tableName)
+    {
+        $foreignKeys = array();
+        $rows = $this->fetchAll(sprintf(
+            "SELECT
+                    tc.constraint_name,
+                    tc.table_name, kcu.column_name,
+                    ccu.table_name AS referenced_table_name,
+                    ccu.column_name AS referenced_column_name
+                FROM
+                    information_schema.table_constraints AS tc
+                    JOIN information_schema.key_column_usage AS kcu ON tc.constraint_name = kcu.constraint_name
+                    JOIN information_schema.constraint_column_usage AS ccu ON ccu.constraint_name = tc.constraint_name
+                WHERE constraint_type = 'FOREIGN KEY' AND tc.table_name = '%s'
+                ORDER BY kcu.ordinal_position",
+            $tableName
+        ));
+        foreach ($rows as $row) {
+            $foreignKeys[$row['constraint_name']]['table'] = $row['table_name'];
+            $foreignKeys[$row['constraint_name']]['columns'][] = $row['column_name'];
+            $foreignKeys[$row['constraint_name']]['referenced_table'] = $row['referenced_table_name'];
+            $foreignKeys[$row['constraint_name']]['referenced_columns'][] = $row['referenced_column_name'];
+        }
+
+        return $foreignKeys;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function addForeignKey(Table $table, ForeignKey $foreignKey)
+    {
+        $this->startCommandTimer();
+        $this->writeCommand('addForeignKey', array($table->getName(), $foreignKey->getColumns()));
+        $this->execute(
+            sprintf(
+                'ALTER TABLE %s ADD %s',
+                $this->quoteTableName($table->getName()),
+                $this->getForeignKeySqlDefinition($foreignKey, $table->getName())
+            )
+        );
+        $this->endCommandTimer();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function dropForeignKey($tableName, $columns, $constraint = null)
+    {
+        $this->startCommandTimer();
+        if (is_string($columns)) {
+            $columns = array($columns); // str to array
+        }
+
+        $this->writeCommand('dropForeignKey', array($tableName, $columns));
+
+        if ($constraint) {
+            $this->execute(
+                sprintf(
+                    'ALTER TABLE %s DROP CONSTRAINT %s',
+                    $this->quoteTableName($tableName),
+                    $constraint
+                )
+            );
+            $this->endCommandTimer();
+            return;
+        } else {
+            foreach ($columns as $column) {
+                $rows = $this->fetchAll(sprintf(
+                    "SELECT
+                    tc.constraint_name,
+                    tc.table_name, kcu.column_name,
+                    ccu.table_name AS referenced_table_name,
+                    ccu.column_name AS referenced_column_name
+                FROM
+                    information_schema.table_constraints AS tc
+                    JOIN information_schema.key_column_usage AS kcu ON tc.constraint_name = kcu.constraint_name
+                    JOIN information_schema.constraint_column_usage AS ccu ON ccu.constraint_name = tc.constraint_name
+                WHERE constraint_type = 'FOREIGN KEY' AND tc.table_name = '%s' and ccu.column_name='%s'
+                ORDER BY kcu.ordinal_position",
+                    $tableName,
+                    $column
+                ));
+                foreach ($rows as $row) {
+                    $this->dropForeignKey($tableName, $columns, $row['constraint_name']);
+                }
+            }
+        }
+        $this->endCommandTimer();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getSqlType($type, $limit = null)
+    {
+        switch ($type) {
+            case static::PHINX_TYPE_STRING:
+                return array('name' => 'nvarchar', 'limit' => 255);
+                break;
+            case static::PHINX_TYPE_CHAR:
+                return array('name' => 'nchar', 'limit' => 255);
+                break;
+            case static::PHINX_TYPE_TEXT:
+                return array('name' => 'ntext');
+                break;
+            case static::PHINX_TYPE_INTEGER:
+                return array('name' => 'int');
+                break;
+            case static::PHINX_TYPE_BIG_INTEGER:
+                return array('name' => 'bigint');
+                break;
+            case static::PHINX_TYPE_FLOAT:
+                return array('name' => 'float');
+                break;
+            case static::PHINX_TYPE_DECIMAL:
+                return array('name' => 'decimal');
+                break;
+            case static::PHINX_TYPE_DATETIME:
+            case static::PHINX_TYPE_TIMESTAMP:
+                return array('name' => 'datetime');
+                break;
+            case static::PHINX_TYPE_TIME:
+                return array('name' => 'time');
+                break;
+            case static::PHINX_TYPE_DATE:
+                return array('name' => 'date');
+                break;
+            case static::PHINX_TYPE_BLOB:
+            case static::PHINX_TYPE_BINARY:
+                return array('name' => 'varbinary');
+                break;
+            case static::PHINX_TYPE_BOOLEAN:
+                return array('name' => 'bit');
+                break;
+            case static::PHINX_TYPE_UUID:
+                return array('name' => 'uniqueidentifier');
+            case static::PHINX_TYPE_FILESTREAM:
+                return array('name' => 'varbinary', 'limit' => 'max');
+            // Geospatial database types
+            case static::PHINX_TYPE_GEOMETRY:
+            case static::PHINX_TYPE_POINT:
+            case static::PHINX_TYPE_LINESTRING:
+            case static::PHINX_TYPE_POLYGON:
+                // SQL Server stores all spatial data using a single data type.
+                // Specific types (point, polygon, etc) are set at insert time.
+                return array('name' => 'geography');
+                break;
+            default:
+                throw new \RuntimeException('The type: "' . $type . '" is not supported.');
+        }
+    }
+
+    /**
+     * Returns Phinx type by SQL type
+     *
+     * @param $sqlTypeDef
+     * @throws \RuntimeException
+     * @internal param string $sqlType SQL type
+     * @returns string Phinx type
+     */
+    public function getPhinxType($sqlType)
+    {
+        switch ($sqlType) {
+            case 'nvarchar':
+            case 'varchar':
+                return static::PHINX_TYPE_STRING;
+            case 'char':
+            case 'nchar':
+                return static::PHINX_TYPE_CHAR;
+            case 'text':
+            case 'ntext':
+                return static::PHINX_TYPE_TEXT;
+            case 'int':
+            case 'integer':
+                return static::PHINX_TYPE_INTEGER;
+            case 'decimal':
+            case 'numeric':
+            case 'money':
+                return static::PHINX_TYPE_DECIMAL;
+            case 'bigint':
+                return static::PHINX_TYPE_BIG_INTEGER;
+            case 'real':
+            case 'float':
+                return static::PHINX_TYPE_FLOAT;
+            case 'binary':
+            case 'image':
+            case 'varbinary':
+                return static::PHINX_TYPE_BINARY;
+                break;
+            case 'time':
+                return static::PHINX_TYPE_TIME;
+            case 'date':
+                return static::PHINX_TYPE_DATE;
+            case 'datetime':
+            case 'timestamp':
+                return static::PHINX_TYPE_DATETIME;
+            case 'bit':
+                return static::PHINX_TYPE_BOOLEAN;
+            case 'uniqueidentifier':
+                return static::PHINX_TYPE_UUID;
+            case 'filestream':
+                return static::PHINX_TYPE_FILESTREAM;
+            default:
+                throw new \RuntimeException('The SqlServer type: "' . $sqlType . '" is not supported');
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function createDatabase($name, $options = array())
+    {
+        $this->startCommandTimer();
+        $this->writeCommand('createDatabase', array($name));
+
+        if (isset($options['collation'])) {
+            $this->execute(sprintf('CREATE DATABASE [%s] COLLATE [%s]', $name, $options['collation']));
+        } else {
+            $this->execute(sprintf('CREATE DATABASE [%s]', $name));
+        }
+        $this->execute(sprintf('USE [%s]', $name));
+        $this->endCommandTimer();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function hasDatabase($name)
+    {
+        $result = $this->fetchRow(
+            sprintf(
+                'SELECT count(*) as [count] FROM master.dbo.sysdatabases WHERE [name] = \'%s\'',
+                $name
+            )
+        );
+
+        return $result['count'] > 0;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function dropDatabase($name)
+    {
+        $this->startCommandTimer();
+        $this->writeCommand('dropDatabase', array($name));
+        $sql = <<<SQL
+USE master;
+IF EXISTS(select * from sys.databases where name=N'$name')
+ALTER DATABASE [$name] SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
+DROP DATABASE [$name];
+SQL;
+        $this->execute($sql);
+        $this->endCommandTimer();
+    }
+
+    /**
+     * Get the defintion for a `DEFAULT` statement.
+     *
+     * @param  mixed $default
+     * @return string
+     */
+    protected function getDefaultValueDefinition($default)
+    {
+        if (is_string($default) && 'CURRENT_TIMESTAMP' !== $default) {
+            $default = $this->getConnection()->quote($default);
+        } elseif (is_bool($default)) {
+            $default = $this->castToBool($default);
+        }
+        return isset($default) ? ' DEFAULT ' . $default : '';
+    }
+
+    /**
+     * Gets the SqlServer Column Definition for a Column object.
+     *
+     * @param Column $column Column
+     * @return string
+     */
+    protected function getColumnSqlDefinition(Column $column, $create = true)
+    {
+        $buffer = array();
+
+        $sqlType = $this->getSqlType($column->getType());
+        $buffer[] = strtoupper($sqlType['name']);
+        // integers cant have limits in SQlServer
+        $noLimits = array(
+            'bigint',
+            'int',
+            'tinyint'
+        );
+        if (!in_array($sqlType['name'], $noLimits) && ($column->getLimit() || isset($sqlType['limit']))) {
+            $buffer[] = sprintf('(%s)', $column->getLimit() ? $column->getLimit() : $sqlType['limit']);
+        }
+        if ($column->getPrecision() && $column->getScale()) {
+            $buffer[] = '(' . $column->getPrecision() . ',' . $column->getScale() . ')';
+        }
+
+        $properties = $column->getProperties();
+        $buffer[] = $column->getType() === 'filestream' ? 'FILESTREAM' : '';
+        $buffer[] = isset($properties['rowguidcol']) ? 'ROWGUIDCOL' : '';
+
+        $buffer[] = $column->isNull() ? 'NULL' : 'NOT NULL';
+
+        if ($create === true) {
+            if ($column->getDefault() === null && $column->isNull()) {
+                $buffer[] = ' DEFAULT NULL';
+            } else {
+                $buffer[] = $this->getDefaultValueDefinition($column->getDefault());
+            }
+        }
+
+        if ($column->isIdentity()) {
+            $buffer[] = 'IDENTITY(1, 1)';
+        }
+
+        return implode(' ', $buffer);
+    }
+
+    /**
+     * Gets the SqlServer Index Definition for an Index object.
+     *
+     * @param Index $index Index
+     * @return string
+     */
+    protected function getIndexSqlDefinition(Index $index, $tableName)
+    {
+        if (is_string($index->getName())) {
+            $indexName = $index->getName();
+        } else {
+            $columnNames = $index->getColumns();
+            if (is_string($columnNames)) {
+                $columnNames = array($columnNames);
+            }
+            $indexName = sprintf('%s_%s', $tableName, implode('_', $columnNames));
+        }
+        $def = sprintf(
+            "CREATE %s INDEX %s ON %s (%s);",
+            ($index->getType() === Index::UNIQUE ? 'UNIQUE' : ''),
+            $indexName,
+            $this->quoteTableName($tableName),
+            '[' . implode('],[', $index->getColumns()) . ']'
+        );
+
+        return $def;
+    }
+
+    /**
+     * Gets the SqlServer Foreign Key Definition for an ForeignKey object.
+     *
+     * @param ForeignKey $foreignKey
+     * @return string
+     */
+    protected function getForeignKeySqlDefinition(ForeignKey $foreignKey, $tableName)
+    {
+        $def = ' CONSTRAINT "';
+        $def .= $tableName . '_' . implode('_', $foreignKey->getColumns());
+        $def .= '" FOREIGN KEY ("' . implode('", "', $foreignKey->getColumns()) . '")';
+        $def .= " REFERENCES {$this->quoteTableName($foreignKey->getReferencedTable()->getName())} (\"" . implode('", "', $foreignKey->getReferencedColumns()) . '")';
+        if ($foreignKey->getOnDelete()) {
+            $def .= " ON DELETE {$foreignKey->getOnDelete()}";
+        }
+        if ($foreignKey->getOnUpdate()) {
+            $def .= " ON UPDATE {$foreignKey->getOnUpdate()}";
+        }
+
+        return $def;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getColumnTypes()
+    {
+        return array_merge(parent::getColumnTypes(), array('filestream'));
+    }
+}

+ 272 - 0
src/DDLWrapper/Db/Adapter/TablePrefixAdapter.php

@@ -0,0 +1,272 @@
+<?php
+/**
+ * Phinx
+ *
+ * (The MIT license)
+ * Copyright (c) 2015 Rob Morgan
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated * documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ *
+ * @package    Phinx
+ * @subpackage Phinx\Db\Adapter
+ */
+namespace DDLWrapper\Db\Adapter;
+
+use DDLWrapper\Db\Table;
+use DDLWrapper\Db\Table\Column;
+use DDLWrapper\Db\Table\Index;
+use DDLWrapper\Db\Table\ForeignKey;
+
+/**
+ * Table prefix/suffix adapter.
+ *
+ * Used for inserting a prefix or suffix into table names.
+ *
+ * @author Samuel Fisher <sam@sfisher.co>
+ */
+class TablePrefixAdapter extends AdapterWrapper
+{
+    /**
+     * {@inheritdoc}
+     */
+    public function getAdapterType()
+    {
+        return 'TablePrefixAdapter';
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function hasTable($tableName)
+    {
+        $adapterTableName = $this->getAdapterTableName($tableName);
+        return parent::hasTable($adapterTableName);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function createTable(Table $table)
+    {
+        $adapterTable = clone $table;
+        $adapterTableName = $this->getAdapterTableName($table->getName());
+        $adapterTable->setName($adapterTableName);
+
+        foreach ($adapterTable->getForeignKeys() as $fk) {
+            $adapterReferenceTable = $fk->getReferencedTable();
+            $adapterReferenceTableName = $this->getAdapterTableName($adapterReferenceTable->getName());
+            $adapterReferenceTable->setName($adapterReferenceTableName);
+        }
+
+        return parent::createTable($adapterTable);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function renameTable($tableName, $newTableName)
+    {
+        $adapterTableName = $this->getAdapterTableName($tableName);
+        $adapterNewTableName = $this->getAdapterTableName($newTableName);
+        return parent::renameTable($adapterTableName, $adapterNewTableName);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function dropTable($tableName)
+    {
+        $adapterTableName = $this->getAdapterTableName($tableName);
+        return parent::dropTable($adapterTableName);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getColumns($tableName)
+    {
+        $adapterTableName = $this->getAdapterTableName($tableName);
+        return parent::getColumns($adapterTableName);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function hasColumn($tableName, $columnName)
+    {
+        $adapterTableName = $this->getAdapterTableName($tableName);
+        return parent::hasColumn($adapterTableName, $columnName);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function addColumn(Table $table, Column $column)
+    {
+        $adapterTable = clone $table;
+        $adapterTableName = $this->getAdapterTableName($table->getName());
+        $adapterTable->setName($adapterTableName);
+        return parent::addColumn($adapterTable, $column);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function renameColumn($tableName, $columnName, $newColumnName)
+    {
+        $adapterTableName = $this->getAdapterTableName($tableName);
+        return parent::renameColumn($adapterTableName, $columnName, $newColumnName);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function changeColumn($tableName, $columnName, Column $newColumn)
+    {
+        $adapterTableName = $this->getAdapterTableName($tableName);
+        return parent::changeColumn($adapterTableName, $columnName, $newColumn);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function dropColumn($tableName, $columnName)
+    {
+        $adapterTableName = $this->getAdapterTableName($tableName);
+        return parent::dropColumn($adapterTableName, $columnName);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function hasIndex($tableName, $columns)
+    {
+        $adapterTableName = $this->getAdapterTableName($tableName);
+        return parent::hasIndex($adapterTableName, $columns);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function hasIndexByName($tableName, $indexName)
+    {
+        $adapterTableName = $this->getAdapterTableName($tableName);
+        return parent::hasIndexByName($adapterTableName, $indexName);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function addIndex(Table $table, Index $index)
+    {
+        $adapterTable = clone $table;
+        $adapterTableName = $this->getAdapterTableName($table->getName());
+        $adapterTable->setName($adapterTableName);
+        return parent::addIndex($adapterTable, $index);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function dropIndex($tableName, $columns, $options = array())
+    {
+        $adapterTableName = $this->getAdapterTableName($tableName);
+        return parent::dropIndex($adapterTableName, $columns, $options);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function dropIndexByName($tableName, $indexName)
+    {
+        $adapterTableName = $this->getAdapterTableName($tableName);
+        return parent::dropIndexByName($adapterTableName, $indexName);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function hasForeignKey($tableName, $columns, $constraint = null)
+    {
+        $adapterTableName = $this->getAdapterTableName($tableName);
+        return parent::hasForeignKey($adapterTableName, $columns, $constraint);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function addForeignKey(Table $table, ForeignKey $foreignKey)
+    {
+        $adapterTable = clone $table;
+        $adapterTableName = $this->getAdapterTableName($table->getName());
+        $adapterTable->setName($adapterTableName);
+        return parent::addForeignKey($adapterTable, $foreignKey);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function dropForeignKey($tableName, $columns, $constraint = null)
+    {
+        $adapterTableName = $this->getAdapterTableName($tableName);
+        return parent::dropForeignKey($adapterTableName, $columns, $constraint);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function insert(Table $table, $row)
+    {
+        $adapterTable = clone $table;
+        $adapterTableName = $this->getAdapterTableName($table->getName());
+        $adapterTable->setName($adapterTableName);
+        return parent::insert($adapterTable, $row);
+    }
+    
+    /**
+     * Gets the table prefix.
+     *
+     * @return string
+     */
+    public function getPrefix()
+    {
+        return (string) $this->getOption('table_prefix');
+    }
+
+    /**
+     * Gets the table suffix.
+     *
+     * @return string
+     */
+    public function getSuffix()
+    {
+        return (string) $this->getOption('table_suffix');
+    }
+
+    /**
+     * Applies the prefix and suffix to the table name.
+     *
+     * @param string $tableName
+     * @return string
+     */
+    public function getAdapterTableName($tableName)
+    {
+        return $this->getPrefix() . $tableName . $this->getSuffix();
+    }
+}

+ 60 - 0
src/DDLWrapper/Db/Adapter/WrapperInterface.php

@@ -0,0 +1,60 @@
+<?php
+/**
+ * Phinx
+ *
+ * (The MIT license)
+ * Copyright (c) 2015 Rob Morgan
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated * documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ *
+ * @package    Phinx
+ * @subpackage Phinx\Db\Adapter
+ */
+namespace DDLWrapper\Db\Adapter;
+
+/**
+ * Wrapper Interface.
+ *
+ * @author Woody Gilk <woody.gilk@gmail.com>
+ */
+interface WrapperInterface
+{
+    /**
+     * Class constructor, must always wrap another adapter.
+     *
+     * @param  AdapterInterface $adapter
+     */
+    public function __construct(AdapterInterface $adapter);
+
+    /**
+     * Sets the database adapter to proxy commands to.
+     *
+     * @param  AdapterInterface $adapter
+     * @return AdapterInterface
+     */
+    public function setAdapter(AdapterInterface $adapter);
+
+    /**
+     * Gets the database adapter.
+     *
+     * @throws \RuntimeException if the adapter has not been set
+     * @return AdapterInterface
+     */
+    public function getAdapter();
+}

+ 675 - 0
src/DDLWrapper/Db/Table.php

@@ -0,0 +1,675 @@
+<?php
+/**
+ * Phinx
+ *
+ * (The MIT license)
+ * Copyright (c) 2015 Rob Morgan
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated * documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ *
+ * @package    Phinx
+ * @subpackage Phinx\Db
+ */
+namespace DDLWrapper\Db;
+
+use DDLWrapper\Db\Table\Column;
+use DDLWrapper\Db\Table\Index;
+use DDLWrapper\Db\Table\ForeignKey;
+use DDLWrapper\Db\Adapter\AdapterInterface;
+
+/**
+ *
+ * This object is based loosely on: http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/Table.html.
+ */
+class Table
+{
+    /**
+     * @var string
+     */
+    protected $name;
+
+    /**
+     * @var array
+     */
+    protected $options = array();
+
+    /**
+     * @var AdapterInterface
+     */
+    protected $adapter;
+
+    /**
+     * @var array
+     */
+    protected $columns = array();
+
+    /**
+     * @var array
+     */
+    protected $indexes = array();
+
+    /**
+     * @var ForeignKey[]
+     */
+    protected $foreignKeys = array();
+
+    /**
+     * @var array
+     */
+    protected $data = array();
+
+    /**
+     * Class Constuctor.
+     *
+     * @param string $name Table Name
+     * @param array $options Options
+     * @param AdapterInterface $adapter Database Adapter
+     */
+    public function __construct($name, $options = array(), AdapterInterface $adapter = null)
+    {
+        $this->setName($name);
+        $this->setOptions($options);
+
+        if (null !== $adapter) {
+            echo 'Adapter definido';
+            $this->setAdapter($adapter);
+        }
+    }
+
+    /**
+     * Sets the table name.
+     *
+     * @param string $name Table Name
+     * @return Table
+     */
+    public function setName($name)
+    {
+        $this->name = $name;
+        return $this;
+    }
+
+    /**
+     * Gets the table name.
+     *
+     * @return string
+     */
+    public function getName()
+    {
+        return $this->name;
+    }
+
+    /**
+     * Sets the table options.
+     *
+     * @param array $options
+     * @return Table
+     */
+    public function setOptions($options)
+    {
+        $this->options = $options;
+        return $this;
+    }
+
+    /**
+     * Gets the table options.
+     *
+     * @return array
+     */
+    public function getOptions()
+    {
+        return $this->options;
+    }
+
+    /**
+     * Sets the database adapter.
+     *
+     * @param AdapterInterface $adapter Database Adapter
+     * @return Table
+     */
+    public function setAdapter(AdapterInterface $adapter)
+    {
+        $this->adapter = $adapter;
+        return $this;
+    }
+
+    /**
+     * Gets the database adapter.
+     *
+     * @return AdapterInterface
+     */
+    public function getAdapter()
+    {
+        return $this->adapter;
+    }
+
+    /**
+     * Does the table exist?
+     *
+     * @return boolean
+     */
+    public function exists()
+    {
+        return $this->getAdapter()->hasTable($this->getName());
+    }
+
+    /**
+     * Drops the database table.
+     *
+     * @return void
+     */
+    public function drop()
+    {
+        $this->getAdapter()->dropTable($this->getName());
+    }
+
+    /**
+     * Renames the database table.
+     *
+     * @param string $newTableName New Table Name
+     * @return Table
+     */
+    public function rename($newTableName)
+    {
+        $this->getAdapter()->renameTable($this->getName(), $newTableName);
+        $this->setName($newTableName);
+        return $this;
+    }
+
+    /**
+     * Sets an array of columns waiting to be committed.
+     * Use setPendingColumns
+     *
+     * @deprecated
+     * @param array $columns Columns
+     * @return Table
+     */
+    public function setColumns($columns)
+    {
+        $this->setPendingColumns($columns);
+    }
+
+    /**
+     * Gets an array of the table columns.
+     *
+     * @return Column[]
+     */
+    public function getColumns()
+    {
+        return $this->getAdapter()->getColumns($this->getName());
+    }
+
+    /**
+     * Sets an array of columns waiting to be committed.
+     *
+     * @param array $columns Columns
+     * @return Table
+     */
+    public function setPendingColumns($columns)
+    {
+        $this->columns = $columns;
+        return $this;
+    }
+
+    /**
+     * Gets an array of columns waiting to be committed.
+     *
+     * @return Column[]
+     */
+    public function getPendingColumns()
+    {
+        return $this->columns;
+    }
+
+    /**
+     * Sets an array of columns waiting to be indexed.
+     *
+     * @param array $indexes Indexes
+     * @return Table
+     */
+    public function setIndexes($indexes)
+    {
+        $this->indexes = $indexes;
+        return $this;
+    }
+
+    /**
+     * Gets an array of indexes waiting to be committed.
+     *
+     * @return array
+     */
+    public function getIndexes()
+    {
+        return $this->indexes;
+    }
+
+    /**
+     * Sets an array of foreign keys waiting to be commited.
+     *
+     * @param ForeignKey[] $foreignKeys foreign keys
+     * @return Table
+     */
+    public function setForeignKeys($foreignKeys)
+    {
+        $this->foreignKeys = $foreignKeys;
+        return $this;
+    }
+
+    /**
+     * Gets an array of foreign keys waiting to be commited.
+     *
+     * @return array|ForeignKey[]
+     */
+    public function getForeignKeys()
+    {
+        return $this->foreignKeys;
+    }
+
+    /**
+     * Sets an array of data to be inserted.
+     *
+     * @param array $data Data
+     * @return Table
+     */
+    public function setData($data)
+    {
+        $this->data = $data;
+        return $this;
+    }
+
+    /**
+     * Gets the data waiting to be inserted.
+     *
+     * @return array
+     */
+    public function getData()
+    {
+        return $this->data;
+    }
+
+    /**
+     * Resets all of the pending table changes.
+     *
+     * @return void
+     */
+    public function reset()
+    {
+        $this->setPendingColumns(array());
+        $this->setIndexes(array());
+        $this->setForeignKeys(array());
+        $this->setData(array());
+    }
+
+    /**
+     * Add a table column.
+     *
+     * Type can be: string, text, integer, float, decimal, datetime, timestamp,
+     * time, date, binary, boolean.
+     *
+     * Valid options can be: limit, default, null, precision or scale.
+     *
+     * @param string|Column $columnName Column Name
+     * @param string $type Column Type
+     * @param array $options Column Options
+     * @throws \RuntimeException
+     * @throws \InvalidArgumentException
+     * @return Table
+     */
+    public function addColumn($columnName, $type = null, $options = array())
+    {
+        // we need an adapter set to add a column
+        if (null === $this->getAdapter()) {
+            throw new \RuntimeException('An adapter must be specified to add a column.');
+        }
+
+        // create a new column object if only strings were supplied
+        if (!$columnName instanceof Column) {
+            $column = new Column();
+            $column->setName($columnName);
+            $column->setType($type);
+            $column->setOptions($options); // map options to column methods
+        } else {
+            $column = $columnName;
+        }
+
+        // Delegate to Adapters to check column type
+        if (!$this->getAdapter()->isValidColumnType($column)) {
+            throw new \InvalidArgumentException(sprintf(
+                'An invalid column type "%s" was specified for column "%s".',
+                $column->getType(),
+                $column->getName()
+            ));
+        }
+
+        $this->columns[] = $column;
+        return $this;
+    }
+
+    /**
+     * Remove a table column.
+     *
+     * @param string $columnName Column Name
+     * @return Table
+     */
+    public function removeColumn($columnName)
+    {
+        $this->getAdapter()->dropColumn($this->getName(), $columnName);
+        return $this;
+    }
+
+    /**
+     * Rename a table column.
+     *
+     * @param string $oldName Old Column Name
+     * @param string $newName New Column Name
+     * @return Table
+     */
+    public function renameColumn($oldName, $newName)
+    {
+        $this->getAdapter()->renameColumn($this->getName(), $oldName, $newName);
+        return $this;
+    }
+
+    /**
+     * Change a table column type.
+     *
+     * @param string        $columnName    Column Name
+     * @param string|Column $newColumnType New Column Type
+     * @param array         $options       Options
+     * @return Table
+     */
+    public function changeColumn($columnName, $newColumnType, $options = array())
+    {
+        // create a column object if one wasn't supplied
+        if (!$newColumnType instanceof Column) {
+            $newColumn = new Column();
+            $newColumn->setType($newColumnType);
+            $newColumn->setOptions($options);
+        } else {
+            $newColumn = $newColumnType;
+        }
+
+        // if the name was omitted use the existing column name
+        if (null === $newColumn->getName() || strlen($newColumn->getName()) === 0) {
+            $newColumn->setName($columnName);
+        }
+
+        $this->getAdapter()->changeColumn($this->getName(), $columnName, $newColumn);
+        return $this;
+    }
+
+    /**
+     * Checks to see if a column exists.
+     *
+     * @param string $columnName Column Name
+     * @param array $options Options
+     * @return boolean
+     */
+    public function hasColumn($columnName, $options = array())
+    {
+        return $this->getAdapter()->hasColumn($this->getName(), $columnName, $options);
+    }
+
+    /**
+     * Add an index to a database table.
+     *
+     * In $options you can specific unique = true/false or name (index name).
+     *
+     * @param string|array|Index $columns Table Column(s)
+     * @param array $options Index Options
+     * @return Table
+     */
+    public function addIndex($columns, $options = array())
+    {
+        // create a new index object if strings or an array of strings were supplied
+        if (!$columns instanceof Index) {
+            $index = new Index();
+            if (is_string($columns)) {
+                $columns = array($columns); // str to array
+            }
+            $index->setColumns($columns);
+            $index->setOptions($options);
+        } else {
+            $index = $columns;
+        }
+
+        $this->indexes[] = $index;
+        return $this;
+    }
+
+    /**
+     * Removes the given index from a table.
+     *
+     * @param array $columns Columns
+     * @param array $options Options
+     * @return Table
+     */
+    public function removeIndex($columns, $options = array())
+    {
+        $this->getAdapter()->dropIndex($this->getName(), $columns, $options);
+        return $this;
+    }
+
+    /**
+     * Removes the given index identified by its name from a table.
+     *
+     * @param string $name Index name
+     * @return Table
+     */
+    public function removeIndexByName($name)
+    {
+        $this->getAdapter()->dropIndexByName($this->getName(), $name);
+        return $this;
+    }
+
+    /**
+     * Checks to see if an index exists.
+     *
+     * @param string|array $columns Columns
+     * @param array        $options Options
+     * @return boolean
+     */
+    public function hasIndex($columns, $options = array())
+    {
+        return $this->getAdapter()->hasIndex($this->getName(), $columns, $options);
+    }
+
+    /**
+     * Add a foreign key to a database table.
+     *
+     * In $options you can specify on_delete|on_delete = cascade|no_action ..,
+     * on_update, constraint = constraint name.
+     *
+     * @param string|array $columns Columns
+     * @param string|Table $referencedTable   Referenced Table
+     * @param string|array $referencedColumns Referenced Columns
+     * @param array $options Options
+     * @return Table
+     */
+    public function addForeignKey($columns, $referencedTable, $referencedColumns = array('id'), $options = array())
+    {
+        if (is_string($referencedColumns)) {
+            $referencedColumns = array($referencedColumns); // str to array
+        }
+        $fk = new ForeignKey();
+        if ($referencedTable instanceof Table) {
+            $fk->setReferencedTable($referencedTable);
+        } else {
+            $fk->setReferencedTable(new Table($referencedTable, array(), $this->adapter));
+        }
+        $fk->setColumns($columns)
+           ->setReferencedColumns($referencedColumns)
+           ->setOptions($options);
+        $this->foreignKeys[] = $fk;
+
+        return $this;
+    }
+
+    /**
+     * Removes the given foreign key from the table.
+     *
+     * @param string|array $columns    Column(s)
+     * @param null|string  $constraint Constraint names
+     * @return Table
+     */
+    public function dropForeignKey($columns, $constraint = null)
+    {
+        if (is_string($columns)) {
+            $columns = array($columns);
+        }
+        if ($constraint) {
+            $this->getAdapter()->dropForeignKey($this->getName(), array(), $constraint);
+        } else {
+            $this->getAdapter()->dropForeignKey($this->getName(), $columns);
+        }
+
+        return $this;
+    }
+
+    /**
+     * Checks to see if a foreign key exists.
+     *
+     * @param  string|array $columns    Column(s)
+     * @param  null|string  $constraint Constraint names
+     * @return boolean
+     */
+    public function hasForeignKey($columns, $constraint = null)
+    {
+        return $this->getAdapter()->hasForeignKey($this->getName(), $columns, $constraint);
+    }
+
+    /**
+     * Add timestamp columns created_at and updated_at to the table.
+     *
+     * @param string $createdAtColumnName
+     * @param string $updatedAtColumnName
+     *
+     * @return Table
+     */
+    public function addTimestamps($createdAtColumnName = 'created_at', $updatedAtColumnName = 'updated_at')
+    {
+        $createdAtColumnName = is_null($createdAtColumnName) ? 'created_at' : $createdAtColumnName;
+        $updatedAtColumnName = is_null($updatedAtColumnName) ? 'updated_at' : $updatedAtColumnName;
+        $this->addColumn($createdAtColumnName, 'timestamp', array(
+                'default' => 'CURRENT_TIMESTAMP',
+                'update' => ''
+            ))
+             ->addColumn($updatedAtColumnName, 'timestamp', array(
+                'null'    => true,
+                'default' => null
+             ));
+
+        return $this;
+    }
+
+    /**
+     * Insert data into the table.
+     *
+     * @param $data array of data in the form:
+     *              array(
+     *                  array("col1" => "value1", "col2" => "anotherValue1"),
+     *                  array("col2" => "value2", "col2" => "anotherValue2"),
+     *              )
+     *              or array("col1" => "value1", "col2" => "anotherValue1")
+     *
+     * @return Table
+     */
+    public function insert($data)
+    {
+        // handle array of array situations
+        if (isset($data[0]) && is_array($data[0])) {
+            foreach ($data as $row) {
+                $this->data[] = $row;
+            }
+            return $this;
+        }
+        $this->data[] = $data;
+        return $this;
+    }
+
+    /**
+     * Creates a table from the object instance.
+     *
+     * @return void
+     */
+    public function create()
+    {
+        $this->getAdapter()->createTable($this);
+        $this->saveData();
+        $this->reset(); // reset pending changes
+    }
+
+    /**
+     * Updates a table from the object instance.
+     *
+     * @throws \RuntimeException
+     * @return void
+     */
+    public function update()
+    {
+        if (!$this->exists()) {
+            throw new \RuntimeException('Cannot update a table that doesn\'t exist!');
+        }
+
+        // update table
+        foreach ($this->getPendingColumns() as $column) {
+            $this->getAdapter()->addColumn($this, $column);
+        }
+
+        foreach ($this->getIndexes() as $index) {
+            $this->getAdapter()->addIndex($this, $index);
+        }
+
+        foreach ($this->getForeignKeys() as $foreignKey) {
+            $this->getAdapter()->addForeignKey($this, $foreignKey);
+        }
+
+        $this->saveData();
+        $this->reset(); // reset pending changes
+    }
+
+    /**
+     * Commit the pending data waiting for insertion.
+     *
+     * @return void
+     */
+    public function saveData()
+    {
+        foreach ($this->getData() as $row) {
+            $this->getAdapter()->insert($this, $row);
+        }
+    }
+
+    /**
+     * Commits the table changes.
+     *
+     * If the table doesn't exist it is created otherwise it is updated.
+     *
+     * @return void
+     */
+    public function save()
+    {
+        if ($this->exists()) {
+            $this->update(); // update the table
+        } else {
+            $this->create(); // create the table
+        }
+
+        $this->reset(); // reset pending changes
+    }
+}

+ 550 - 0
src/DDLWrapper/Db/Table/Column.php

@@ -0,0 +1,550 @@
+<?php
+/**
+ * Phinx
+ *
+ * (The MIT license)
+ * Copyright (c) 2015 Rob Morgan
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated * documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ *
+ * @package    Phinx
+ * @subpackage Phinx\Db
+ */
+namespace DDLWrapper\Db\Table;
+
+/**
+ *
+ * This object is based loosely on: http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/Table.html.
+ */
+class Column
+{
+    /**
+     * @var string
+     */
+    protected $name;
+
+    /**
+     * @var string
+     */
+    protected $type;
+
+    /**
+     * @var integer
+     */
+    protected $limit = null;
+
+    /**
+     * @var boolean
+     */
+    protected $null = false;
+
+    /**
+     * @var mixed
+     */
+    protected $default = null;
+
+    /**
+     * @var boolean
+     */
+    protected $identity = false;
+
+    /**
+     * @var integer
+     */
+    protected $precision;
+
+    /**
+     * @var integer
+     */
+    protected $scale;
+
+    /**
+     * @var string
+     */
+    protected $after;
+
+    /**
+     * @var string
+     */
+    protected $update;
+
+    /**
+     * @var string
+     */
+    protected $comment;
+
+    /**
+     * @var boolean
+     */
+    protected $signed = true;
+
+    /**
+     * @var boolean
+     */
+    protected $timezone = false;
+
+    /**
+     * @var array
+     */
+    protected $properties = array();
+
+    /**
+     * @var array
+     */
+    protected $values;
+
+    /**
+     * Sets the column name.
+     *
+     * @param string $name
+     * @return Column
+     */
+    public function setName($name)
+    {
+        $this->name = $name;
+        return $this;
+    }
+
+    /**
+     * Gets the column name.
+     *
+     * @return string
+     */
+    public function getName()
+    {
+        return $this->name;
+    }
+
+    /**
+     * Sets the column type.
+     *
+     * @param string $type
+     * @return Column
+     */
+    public function setType($type)
+    {
+        $this->type = $type;
+        return $this;
+    }
+
+    /**
+     * Gets the column type.
+     *
+     * @return string
+     */
+    public function getType()
+    {
+        return $this->type;
+    }
+
+    /**
+     * Sets the column limit.
+     *
+     * @param integer $limit
+     * @return Column
+     */
+    public function setLimit($limit)
+    {
+        $this->limit = $limit;
+        return $this;
+    }
+
+    /**
+     * Gets the column limit.
+     *
+     * @return integer
+     */
+    public function getLimit()
+    {
+        return $this->limit;
+    }
+
+    /**
+     * Sets whether the column allows nulls.
+     *
+     * @param boolean $null
+     * @return Column
+     */
+    public function setNull($null)
+    {
+        $this->null = (bool) $null;
+        return $this;
+    }
+
+    /**
+     * Gets whether the column allows nulls.
+     *
+     * @return boolean
+     */
+    public function getNull()
+    {
+        return $this->null;
+    }
+
+    /**
+     * Does the column allow nulls?
+     *
+     * @return boolean
+     */
+    public function isNull()
+    {
+        return $this->getNull();
+    }
+
+    /**
+     * Sets the default column value.
+     *
+     * @param mixed $default
+     * @return Column
+     */
+    public function setDefault($default)
+    {
+        $this->default = $default;
+        return $this;
+    }
+
+    /**
+     * Gets the default column value.
+     *
+     * @return mixed
+     */
+    public function getDefault()
+    {
+        return $this->default;
+    }
+
+    /**
+     * Sets whether or not the column is an identity column.
+     *
+     * @param boolean $identity
+     * @return Column
+     */
+    public function setIdentity($identity)
+    {
+        $this->identity = $identity;
+        return $this;
+    }
+
+    /**
+     * Gets whether or not the column is an identity column.
+     *
+     * @return boolean
+     */
+    public function getIdentity()
+    {
+        return $this->identity;
+    }
+
+    /**
+     * Is the column an identity column?
+     *
+     * @return boolean
+     */
+    public function isIdentity()
+    {
+        return $this->getIdentity();
+    }
+
+    /**
+     * Sets the name of the column to add this column after.
+     *
+     * @param string $after After
+     * @return Column
+     */
+    public function setAfter($after)
+    {
+        $this->after = $after;
+        return $this;
+    }
+
+    /**
+     * Returns the name of the column to add this column after.
+     *
+     * @return string
+     */
+    public function getAfter()
+    {
+        return $this->after;
+    }
+
+    /**
+     * Sets the 'ON UPDATE' mysql column function.
+     *
+     * @param  string $update On Update function
+     * @return Column
+     */
+    public function setUpdate($update)
+    {
+        $this->update = $update;
+        return $this;
+    }
+
+    /**
+     * Returns the value of the ON UPDATE column function.
+     *
+     * @return string
+     */
+    public function getUpdate()
+    {
+        return $this->update;
+    }
+
+    /**
+     * Sets the column precision for decimal.
+     *
+     * @param integer $precision
+     * @return Column
+     */
+    public function setPrecision($precision)
+    {
+        $this->precision = $precision;
+        return $this;
+    }
+
+    /**
+     * Gets the column precision for decimal.
+     *
+     * @return integer
+     */
+    public function getPrecision()
+    {
+        return $this->precision;
+    }
+
+    /**
+     * Sets the column scale for decimal.
+     *
+     * @param integer $scale
+     * @return Column
+     */
+    public function setScale($scale)
+    {
+        $this->scale = $scale;
+        return $this;
+    }
+
+    /**
+     * Gets the column scale for decimal.
+     *
+     * @return integer
+     */
+    public function getScale()
+    {
+        return $this->scale;
+    }
+
+    /**
+     * Sets the column comment.
+     *
+     * @param string $comment
+     * @return Column
+     */
+    public function setComment($comment)
+    {
+        $this->comment = $comment;
+        return $this;
+    }
+
+    /**
+     * Gets the column comment.
+     *
+     * @return string
+     */
+    public function getComment()
+    {
+        return $this->comment;
+    }
+
+    /**
+     * Sets whether field should be signed.
+     *
+     * @param bool $signed
+     * @return Column
+     */
+    public function setSigned($signed)
+    {
+        $this->signed = (bool) $signed;
+        return $this;
+    }
+
+    /**
+     * Gets whether field should be signed.
+     *
+     * @return string
+     */
+    public function getSigned()
+    {
+        return $this->signed;
+    }
+
+    /**
+     * Should the column be signed?
+     *
+     * @return boolean
+     */
+    public function isSigned()
+    {
+        return $this->getSigned();
+    }
+
+    /**
+     * Sets whether the field should have a timezone identifier.
+     * Used for date/time columns only!
+     *
+     * @param bool $timezone
+     * @return Column
+     */
+    public function setTimezone($timezone)
+    {
+        $this->timezone = (bool) $timezone;
+        return $this;
+    }
+
+    /**
+     * Gets whether field has a timezone identifier.
+     *
+     * @return boolean
+     */
+    public function getTimezone()
+    {
+        return $this->timezone;
+    }
+
+    /**
+     * Should the column have a timezone?
+     *
+     * @return boolean
+     */
+    public function isTimezone()
+    {
+        return $this->getTimezone();
+    }
+
+    /**
+     * Sets field properties.
+     *
+     * @param array $properties
+     *
+     * @return Column
+     */
+    public function setProperties($properties)
+    {
+        $this->properties = $properties;
+        return $this;
+    }
+
+    /**
+     * Gets field properties
+     *
+     * @return array
+     */
+    public function getProperties()
+    {
+        return $this->properties;
+    }
+
+    /**
+     * Sets field values.
+     *
+     * @param mixed (array|string) $values
+     *
+     * @return Column
+     */
+    public function setValues($values)
+    {
+        if (!is_array($values)) {
+            $values = preg_split('/,\s*/', $values);
+        }
+        $this->values = $values;
+        return $this;
+    }
+
+    /**
+     * Gets field values
+     *
+     * @return string
+     */
+    public function getValues()
+    {
+        return $this->values;
+    }
+
+    /**
+     * Gets all allowed options. Each option must have a corresponding `setFoo` method.
+     *
+     * @return array
+     */
+    protected function getValidOptions()
+    {
+        return array(
+            'limit',
+            'default',
+            'null',
+            'identity',
+            'precision',
+            'scale',
+            'after',
+            'update',
+            'comment',
+            'signed',
+            'timezone',
+            'properties',
+            'values',
+        );
+    }
+
+    /**
+     * Gets all aliased options. Each alias must reference a valid option.
+     *
+     * @return array
+     */
+    protected function getAliasedOptions()
+    {
+        return array(
+            'length' => 'limit',
+        );
+    }
+
+    /**
+     * Utility method that maps an array of column options to this objects methods.
+     *
+     * @param array $options Options
+     * @return Column
+     */
+    public function setOptions($options)
+    {
+        $validOptions = $this->getValidOptions();
+        $aliasOptions = $this->getAliasedOptions();
+
+        foreach ($options as $option => $value) {
+            if (isset($aliasOptions[$option])) {
+                // proxy alias -> option
+                $option = $aliasOptions[$option];
+            }
+
+            if (!in_array($option, $validOptions, true)) {
+                throw new \RuntimeException(sprintf('"%s" is not a valid column option.', $option));
+            }
+
+            $method = 'set' . ucfirst($option);
+            $this->$method($value);
+        }
+        return $this;
+    }
+}

+ 252 - 0
src/DDLWrapper/Db/Table/ForeignKey.php

@@ -0,0 +1,252 @@
+<?php
+/**
+ * Phinx
+ *
+ * (The MIT license)
+ * Copyright (c) 2015 Rob Morgan
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated * documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ *
+ * @package    Phinx
+ * @subpackage Phinx\Db
+ * @author     Leonid Kuzmin <lndkuzmin@gmail.com>
+ */
+namespace DDLWrapper\Db\Table;
+
+use DDLWrapper\Db\Table;
+
+class ForeignKey
+{
+    const CASCADE = 'CASCADE';
+    const RESTRICT = 'RESTRICT';
+    const SET_NULL = 'SET NULL';
+    const NO_ACTION = 'NO ACTION';
+
+    /**
+     * @var array
+     */
+    protected $columns = array();
+
+    /**
+     * @var Table
+     */
+    protected $referencedTable;
+
+    /**
+     * @var array
+     */
+    protected $referencedColumns = array();
+
+    /**
+     * @var string
+     */
+    protected $onDelete;
+
+    /**
+     * @var string
+     */
+    protected $onUpdate;
+
+    /**
+     * @var string|boolean
+     */
+    protected $constraint;
+
+    /**
+     * Sets the foreign key columns.
+     *
+     * @param array|string $columns
+     * @return ForeignKey
+     */
+    public function setColumns($columns)
+    {
+        if (is_string($columns)) {
+            $columns = array($columns);
+        }
+        $this->columns = $columns;
+        return $this;
+    }
+
+    /**
+     * Gets the foreign key columns.
+     *
+     * @return array
+     */
+    public function getColumns()
+    {
+        return $this->columns;
+    }
+
+    /**
+     * Sets the foreign key referenced table.
+     *
+     * @param Table $table
+     * @return ForeignKey
+     */
+    public function setReferencedTable(Table $table)
+    {
+        $this->referencedTable = $table;
+        return $this;
+    }
+
+    /**
+     * Gets the foreign key referenced table.
+     *
+     * @return Table
+     */
+    public function getReferencedTable()
+    {
+        return $this->referencedTable;
+    }
+
+    /**
+     * Sets the foreign key referenced columns.
+     *
+     * @param array $referencedColumns
+     * @return ForeignKey
+     */
+    public function setReferencedColumns(array $referencedColumns)
+    {
+        $this->referencedColumns = $referencedColumns;
+        return $this;
+    }
+
+    /**
+     * Gets the foreign key referenced columns.
+     *
+     * @return array
+     */
+    public function getReferencedColumns()
+    {
+        return $this->referencedColumns;
+    }
+
+    /**
+     * Sets ON DELETE action for the foreign key.
+     *
+     * @param string $onDelete
+     * @return ForeignKey
+     */
+    public function setOnDelete($onDelete)
+    {
+        $this->onDelete = $this->normalizeAction($onDelete);
+        return $this;
+    }
+
+    /**
+     * Gets ON DELETE action for the foreign key.
+     *
+     * @return string
+     */
+    public function getOnDelete()
+    {
+        return $this->onDelete;
+    }
+
+    /**
+     * Gets ON UPDATE action for the foreign key.
+     *
+     * @return string
+     */
+    public function getOnUpdate()
+    {
+        return $this->onUpdate;
+    }
+
+    /**
+     * Sets ON UPDATE action for the foreign key.
+     *
+     * @param string $onUpdate
+     * @return ForeignKey
+     */
+    public function setOnUpdate($onUpdate)
+    {
+        $this->onUpdate = $this->normalizeAction($onUpdate);
+        return $this;
+    }
+
+    /**
+     * Sets constraint for the foreign key.
+     *
+     * @param string $constraint
+     * @return ForeignKey
+     */
+    public function setConstraint($constraint)
+    {
+        $this->constraint = $constraint;
+        return $this;
+    }
+
+    /**
+     * Gets constraint name for the foreign key.
+     *
+     * @return string
+     */
+    public function getConstraint()
+    {
+        return $this->constraint;
+    }
+
+    /**
+     * Utility method that maps an array of index options to this objects methods.
+     *
+     * @param array $options Options
+     * @throws \RuntimeException
+     * @throws \InvalidArgumentException
+     * @return ForeignKey
+     */
+    public function setOptions($options)
+    {
+        // Valid Options
+        $validOptions = array('delete', 'update', 'constraint');
+        foreach ($options as $option => $value) {
+            if (!in_array($option, $validOptions, true)) {
+                throw new \RuntimeException(sprintf('"%s" is not a valid foreign key option.', $option));
+            }
+
+            // handle $options['delete'] as $options['update']
+            if ('delete' === $option) {
+                $this->setOnDelete($value);
+            } elseif ('update' === $option) {
+                $this->setOnUpdate($value);
+            } else {
+                $method = 'set' . ucfirst($option);
+                $this->$method($value);
+            }
+        }
+
+        return $this;
+    }
+
+    /**
+     * From passed value checks if it's correct and fixes if needed
+     *
+     * @param string $action
+     * @throws \InvalidArgumentException
+     * @return string
+     */
+    protected function normalizeAction($action)
+    {
+        $constantName = 'static::' . str_replace(' ', '_', strtoupper(trim($action)));
+        if (!defined($constantName)) {
+            throw new \InvalidArgumentException('Unknown action passed: ' . $action);
+        }
+        return constant($constantName);
+    }
+}

+ 185 - 0
src/DDLWrapper/Db/Table/Index.php

@@ -0,0 +1,185 @@
+<?php
+/**
+ * Phinx
+ *
+ * (The MIT license)
+ * Copyright (c) 2015 Rob Morgan
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated * documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ *
+ * @package    Phinx
+ * @subpackage Phinx\Db
+ */
+namespace DDLWrapper\Db\Table;
+
+class Index
+{
+    /**
+     * @var string
+     */
+    const UNIQUE = 'unique';
+
+    /**
+     * @var string
+     */
+    const INDEX = 'index';
+
+    /**
+     * @var string
+     */
+    const FULLTEXT = 'fulltext';
+
+    /**
+     * @var array
+     */
+    protected $columns;
+
+    /**
+     * @var string
+     */
+    protected $type = self::INDEX;
+
+    /**
+     * @var string
+     */
+    protected $name = null;
+
+    /**
+     * @var integer
+     */
+    protected $limit = null;
+
+    /**
+     * Sets the index columns.
+     *
+     * @param array $columns
+     * @return Index
+     */
+    public function setColumns($columns)
+    {
+        $this->columns = $columns;
+        return $this;
+    }
+
+    /**
+     * Gets the index columns.
+     *
+     * @return array
+     */
+    public function getColumns()
+    {
+        return $this->columns;
+    }
+
+    /**
+     * Sets the index type.
+     *
+     * @param string $type
+     * @return Index
+     */
+    public function setType($type)
+    {
+        $this->type = $type;
+        return $this;
+    }
+
+    /**
+     * Gets the index type.
+     *
+     * @return string
+     */
+    public function getType()
+    {
+        return $this->type;
+    }
+
+    /**
+     * Sets the index name.
+     *
+     * @param string $name
+     * @return Index
+     */
+    public function setName($name)
+    {
+        $this->name = $name;
+        return $this;
+    }
+
+    /**
+     * Gets the index name.
+     *
+     * @return string
+     */
+    public function getName()
+    {
+        return $this->name;
+    }
+
+    /**
+     * Sets the index limit.
+     *
+     * @param integer $limit
+     * @return Index
+     */
+    public function setLimit($limit)
+    {
+        $this->limit = $limit;
+        return $this;
+    }
+
+    /**
+     * Gets the index limit.
+     *
+     * @return integer
+     */
+    public function getLimit()
+    {
+        return $this->limit;
+    }
+
+    /**
+     * Utility method that maps an array of index options to this objects methods.
+     *
+     * @param array $options Options
+     * @throws \RuntimeException
+     * @return Index
+     */
+    public function setOptions($options)
+    {
+        // Valid Options
+        $validOptions = array('type', 'unique', 'name', 'limit');
+        foreach ($options as $option => $value) {
+            if (!in_array($option, $validOptions, true)) {
+                throw new \RuntimeException(sprintf('"%s" is not a valid index option.', $option));
+            }
+
+            // handle $options['unique']
+            if (strcasecmp($option, self::UNIQUE) === 0) {
+                if ((bool) $value) {
+                    $this->setType(self::UNIQUE);
+                }
+                continue;
+            }
+
+            $method = 'set' . ucfirst($option);
+            $this->$method($value);
+        }
+        return $this;
+    }
+}

+ 66 - 0
src/DDLWrapper/Wrapper.php

@@ -0,0 +1,66 @@
+<?php
+
+namespace DDLWrapper;
+
+use DDLWrapper\Db\Table as Table;
+use DDLWrapper\Db\Adapter\PostgresAdapter;
+use DDLWrapper\Db\Adapter\MysqlAdapter;
+use DDLWrapper\Db\Adapter\SQLiteAdapter;
+use DDLWrapper\Db\Adapter\SqlServerAdapter;
+
+class Wrapper{
+
+    private static $_instance;
+
+    public $_adapter;
+
+    function __construct(){ }
+
+    //SINGLETON==============================================
+    private static function newObj(){
+        if (!isset(self::$_instance)) {
+            self::$_instance = new Wrapper();
+        }
+        return self::$_instance;
+    }
+
+    public static function getInstance(){
+        if (!isset(self::$_instance)) {
+            return self::newObj();
+        }
+        return self::$_instance;
+    }
+    //=======================================================
+
+    public static function set_driver(\PDO $driver){
+        $instance = self::getInstance();
+
+        switch ($driver->driver){
+            case 'sqlite':
+                $instance->_adapter = new SQLiteAdapter();
+                $instance-> _adapter->setConnection($driver);
+                break;
+            case 'pgsql':
+                break;
+            case 'mysql':
+                break;
+
+            default:
+                break;
+        }
+    }
+
+    public static function get_table($name){
+        $instance = self::getInstance();
+        return new Table($name, Array(), $instance->_adapter);
+    }
+
+    public static function begin(){
+        $instance = self::getInstance();
+        $instance->_adapter->beginTransaction();
+    }
+    public static function commit(){
+        $instance = self::getInstance();
+        $instance->_adapter->commitTransaction();
+    }
+}