Skip to content

Commit

Permalink
Merge pull request #91 from PhpGt/migrations
Browse files Browse the repository at this point in the history
Migrations
  • Loading branch information
g105b authored May 23, 2018
2 parents 4eb0693 + fa6d039 commit 698d283
Show file tree
Hide file tree
Showing 10 changed files with 137 additions and 71 deletions.
44 changes: 17 additions & 27 deletions bin/db-migrate
Original file line number Diff line number Diff line change
Expand Up @@ -7,43 +7,32 @@ use Gt\Database\Migration\Migrator;
* Database migration iterates over a set of incremental schema changes and
* stores the currently-migrated schema version within the database itself.
*/
$autoloadPath = "";
$currentDir = __DIR__;

// The bin directory may be in multiple places, depending on how this library
// was installed. Iterate up the tree until either the autoloader or the root
// directory is found:
while((empty($autoloadPath)) && $currentDir !== "/") {
$currentDir = realpath($currentDir . "/..");
if(is_file("$currentDir/autoload.php")) {
$autoloadPath = "$currentDir/autoload.php";
}
}
if(empty($autoloadPath)) {
$autoloadPath = realpath(__DIR__ . "/../vendor/autoload.php");
}
// The script must be run from the context of a project's root directory.
$repoBasePath = getcwd();
$autoloadPath = implode(DIRECTORY_SEPARATOR, [
$repoBasePath,
"vendor",
"autoload.php",
]);
require($autoloadPath);

// Repository will always be the parent directory above autoload.php.
$repoBasePath = dirname(dirname($autoloadPath));

$forced = false;
if(!empty($argv[1])
&& ($argv[1] === "--force" || $argv[1] === "-f")) {
$forced = true;
}

$config = new Config("$repoBasePath/config.ini", [
"database" => [
"query_path" => "src/query",
"migration_path" => "_migration",
"migration_table" => "_migration",
]
// Load the default config supplied by WebEngine, if available:
$webEngineConfig = new Config("$repoBasePath/vendor/phpgt/webengine/config.default.ini");
$config = new Config(
"$repoBasePath/config.ini",
[
"database" => $webEngineConfig["database"] ?? [],
]
);

$settings = new Settings(
implode("/", [
implode(DIRECTORY_SEPARATOR, [
$repoBasePath,
$config["database"]["query_path"]
]),
Expand All @@ -55,15 +44,16 @@ $settings = new Settings(
$config["database"]["password"]
);

$migrationPath = implode("/", [
$migrationPath = implode(DIRECTORY_SEPARATOR, [
$repoBasePath,
$config["database"]["query_path"],
$config["database"]["migration_path"],
]);
$migrationTable = $config["database"]["migration_table"];

$migrator = new Migrator($settings, $migrationPath, $migrationTable, $forced);
$migrator->createMigrationTable();
$migrationCount = $migrator->getMigrationCount();
$migrationFileList = $migrator->getMigrationFileList();
$migrator->checkIntegrity($migrationCount, $migrationFileList);
$migrator->checkIntegrity($migrationFileList, $migrationCount);
$migrator->performMigration($migrationFileList, $migrationCount);
7 changes: 1 addition & 6 deletions src/Client.php → src/Database.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
* connections. If only one database connection is required, a name is not
* required as the default name will be used.
*/
class Client {
class Database {
/** @var QueryCollectionFactory[] */
protected $queryCollectionFactoryArray;
/** @var Driver[] */
Expand Down Expand Up @@ -64,11 +64,6 @@ public function update(string $queryName, ...$bindings):int {
}

public function query(string $queryName, ...$bindings):ResultSet {
while(isset($bindings[0])
&& is_array($bindings[0])) {
$bindings = $bindings[0];
}

$queryCollectionName = substr(
$queryName,
0,
Expand Down
9 changes: 5 additions & 4 deletions src/Migration/Migrator.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@
namespace Gt\Database\Migration;

use DirectoryIterator;
use Gt\Database\Client;
use Gt\Database\Database;
use Gt\Database\Connection\Settings;
use Gt\Database\DatabaseException;
use PDOException;
use SplFileInfo;

class Migrator {
Expand Down Expand Up @@ -33,7 +34,7 @@ public function __construct(
$settings = $settings->withoutSchema(); // @codeCoverageIgnore
}

$this->dbClient = new Client($settings);
$this->dbClient = new Database($settings);

if($forced) {
$this->deleteAndRecreateSchema();
Expand Down Expand Up @@ -72,7 +73,7 @@ public function checkMigrationTableExists():bool {

public function createMigrationTable():void {
$this->dbClient->executeSql(implode("\n", [
"create table `{$this->tableName}` (",
"create table if not exists `{$this->tableName}` (",
"`" . self::COLUMN_QUERY_NUMBER . "` int primary key,",
"`" . self::COLUMN_QUERY_HASH . "` varchar(32) not null,",
"`" . self::COLUMN_MIGRATED_AT . "` datetime not null )",
Expand All @@ -88,7 +89,7 @@ public function getMigrationCount():int {
);
$row = $result->fetch();
}
catch(DatabaseException $exception) {
catch(PDOException $exception) {
return 0;
}

Expand Down
35 changes: 23 additions & 12 deletions src/Query/QueryCollection.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,49 +40,60 @@ public function __call($name, $args) {

public function query(
string $name,
iterable $placeholderMap = []
...$placeholderMap
):ResultSet {
$query = $this->queryFactory->create($name);

return $query->execute($placeholderMap);
}

public function insert(
string $name,
iterable $placeholderMap = []
...$placeholderMap
):int {
return (int)$this->query(
$name,
$placeholderMap
...$placeholderMap
)->lastInsertId();
}

public function fetch(
string $name,
iterable $placeholderMap = []
...$placeholderMap
):?Row {
return $this->query($name, $placeholderMap)->current();
return $this->query(
$name,
...$placeholderMap
)->current();
}

public function fetchAll(
string $name,
iterable $placeholderMap = []
...$placeholderMap
):ResultSet {
return $this->query($name, $placeholderMap);
return $this->query(
$name,
...$placeholderMap
);
}

public function update(
string $name,
iterable $placeholderMap = []
...$placeholderMap
):int {
return $this->query($name, $placeholderMap)->affectedRows();
return $this->query(
$name,
...$placeholderMap
)->affectedRows();
}

public function delete(
string $name,
iterable $placeholderMap = []
...$placeholderMap
):int {
return $this->query($name, $placeholderMap)->affectedRows();
return $this->query(
$name,
...$placeholderMap
)->affectedRows();
}

public function getDirectoryPath():string {
Expand Down
30 changes: 30 additions & 0 deletions src/Query/SqlQuery.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ public function getSql(array $bindings = []):string {
}

public function execute(array $bindings = []):ResultSet {
$bindings = $this->flattenBindings($bindings);

$pdo = $this->preparePdo();
$sql = $this->getSql($bindings);
$statement = $this->prepareStatement($pdo, $sql);
Expand Down Expand Up @@ -126,4 +128,32 @@ protected function bindingsEmptyOrNonAssociative(array $bindings):bool {
$bindings === []
|| array_keys($bindings) === range(0, count($bindings) - 1);
}

/**
* $bindings can either be :
* 1) An array of individual values for binding to the question mark placeholder,
* passed in as variable arguments.
* 2) An array containing one single subarray containing key-value-pairs for binding to
* named placeholders.
*
* Due to the use of variable arguments on the Database and QueryCollection classes,
* key-value-pair bindings may be double or triple nested.
*/
protected function flattenBindings(array $bindings):array {
if(!isset($bindings[0])) {
return $bindings;
}

$flatArray = [];
foreach($bindings as $i => $b) {
while(isset($b[0])
&& is_array($b[0])) {
$b = $b[0];
}

$flatArray = array_merge($flatArray, $b);
}

return $flatArray;
}
}
10 changes: 5 additions & 5 deletions test/unit/ClientTest.php → test/unit/DatabaseTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
use Gt\Database\Query\QueryCollection;
use PHPUnit\Framework\TestCase;

class ClientTest extends TestCase {
class DatabaseTest extends TestCase {
public function testInterface() {
$db = new Client();
static::assertInstanceOf(Client::class, $db);
$db = new Database();
static::assertInstanceOf(Database::class, $db);
}

/**
Expand All @@ -21,7 +21,7 @@ public function testQueryCollectionPathExists(string $name, string $path) {
Settings::DRIVER_SQLITE,
Settings::SCHEMA_IN_MEMORY
);
$db = new Client($settings);
$db = new Database($settings);

$queryCollection = $db->queryCollection($name);
static::assertInstanceOf(QueryCollection::class, $queryCollection);
Expand All @@ -39,7 +39,7 @@ public function testQueryCollectionPathNotExists(string $name, string $path) {
Settings::DRIVER_SQLITE,
Settings::SCHEMA_IN_MEMORY
);
$db = new Client($settings);
$db = new Database($settings);
$db->queryCollection($name);
}
}
40 changes: 35 additions & 5 deletions test/unit/IntegrationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,24 +14,24 @@ class IntegrationTest extends TestCase {
private $settings;
/** @var string */
private $queryBase;
/** @var Client */
/** @var Database */
private $db;

public function setUp() {
$this->queryBase = Helper::getTmpDir() . "/query";

$this->db = new Client($this->settingsSingleton());
$this->db = new Database($this->settingsSingleton());
$driver = $this->db->getDriver();

$connection = $driver->getConnection();
$output = $connection->exec("CREATE TABLE test_table ( id INTEGER PRIMARY KEY AUTOINCREMENT, name VARCHAR(32), timestamp DATETIME DEFAULT current_timestamp); CREATE UNIQUE INDEX test_table_name_uindex ON test_table (name);");
$output = $connection->exec("CREATE TABLE test_table ( id INTEGER PRIMARY KEY AUTOINCREMENT, name VARCHAR(32), number integer, timestamp DATETIME DEFAULT current_timestamp); CREATE UNIQUE INDEX test_table_name_uindex ON test_table (name);");

if($output === false) {
$error = $connection->errorInfo();
throw new Exception($error[2]);
}

$insertStatement = $connection->prepare("INSERT INTO test_table (name) VALUES ('one'), ('two'), ('three')");
$insertStatement = $connection->prepare("INSERT INTO test_table (name, number) VALUES ('one', 1), ('two', 2), ('three', 3)");
$success = $insertStatement->execute();
if($success === false) {
$error = $connection->errorInfo();
Expand Down Expand Up @@ -94,7 +94,7 @@ public function testQuestionMarkParameter() {
mkdir($queryCollectionPath, 0775, true);
file_put_contents(
$getByIdQueryPath,
"SELECT id, name FROM test_table WHERE id = ?"
"SELECT id, name, number FROM test_table WHERE id = ?"
);

$result2 = $this->db->fetch("exampleCollection/getById", 2);
Expand All @@ -107,6 +107,36 @@ public function testQuestionMarkParameter() {
static::assertCount(3, $rqr);
}

public function testMultipleParameterUsage() {
$queryCollectionPath = $this->queryBase . "/exampleCollection";
$getByNameNumberQueryPath = $queryCollectionPath . "/getByNameNumber.sql";

mkdir($queryCollectionPath, 0775, true);
file_put_contents(
$getByNameNumberQueryPath,
"SELECT id, name, number FROM test_table WHERE name = :name and number = :number"
);

$result1 = $this->db->fetch("exampleCollection/getByNameNumber", [
"name" => "one",
"number" => 1,
]);
$result2 = $this->db->fetch("exampleCollection/getByNameNumber", [
"name" => "two",
"number" => 2,
]);
$resultNull = $this->db->fetch("exampleCollection/getByNameNumber", [
"name" => "three",
"number" => 55,
]);

$rqr = $this->db->executeSql("SELECT id, name FROM test_table");

static::assertEquals(1, $result1->id);
static::assertEquals(2, $result2->id);
static::assertNull($resultNull);
}

private function settingsSingleton():Settings {
if(is_null($this->settings)) {
$this->settings = new Settings(
Expand Down
Loading

0 comments on commit 698d283

Please sign in to comment.