Compare commits
31 commits
legacy-dig
...
main
Author | SHA1 | Date | |
---|---|---|---|
|
40e1d5df26 | ||
4018095088 | |||
11e6e43072 | |||
da7660fa20 | |||
|
f9a95898d4 | ||
|
2f94e2f7eb | ||
d5dcb941a4 | |||
|
16fbacf072 | ||
110c11dbb2 | |||
|
0d1677a9b4 | ||
bc3fe54e1c | |||
fc506d38a2 | |||
7b125b4d92 | |||
5e9b8b0078 | |||
a452a5f90b | |||
d99c09e575 | |||
5526097af4 | |||
4bf591804c | |||
e088cc47aa | |||
922eac342d | |||
62102fbf9f | |||
5b8d557f19 | |||
9a9f052c1a | |||
|
45c7053db0 | ||
|
770c413d7f | ||
|
94a87f91fa | ||
|
d5ed2af885 | ||
|
39537ba978 | ||
|
fd366a1297 | ||
|
a388c15650 | ||
|
385f6c2b89 |
42 changed files with 1877 additions and 911 deletions
46
.github/workflows/test.yml
vendored
Normal file
46
.github/workflows/test.yml
vendored
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
name: Test suite
|
||||||
|
|
||||||
|
on: push
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
phpunit:
|
||||||
|
name: PHPUnit
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
php: ["7.1", "7.2", "7.3", "7.4"]
|
||||||
|
services:
|
||||||
|
mysql:
|
||||||
|
image: mysql:5.7
|
||||||
|
env:
|
||||||
|
MYSQL_ROOT_PASSWORD: root
|
||||||
|
MYSQL_DATABASE: destructr_test
|
||||||
|
ports:
|
||||||
|
- 3306
|
||||||
|
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
|
||||||
|
mariadb:
|
||||||
|
image: mariadb:10.2
|
||||||
|
env:
|
||||||
|
MARIADB_ROOT_PASSWORD: root
|
||||||
|
MARIADB_DATABASE: destructr_test
|
||||||
|
ports:
|
||||||
|
- 3306
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
- name: Composer install
|
||||||
|
run: composer install -o --no-progress --ignore-platform-reqs
|
||||||
|
- name: Set up PHP
|
||||||
|
uses: shivammathur/setup-php@v2
|
||||||
|
with:
|
||||||
|
php-version: ${{ matrix.php }}
|
||||||
|
extensions: dom, curl, libxml, mbstring, zip, pdo, sqlite, pdo_sqlite, mysql, mysqli, pdo_mysql
|
||||||
|
coverage: none
|
||||||
|
ini-values: variables_order=EGPCS
|
||||||
|
- name: PHPUnit
|
||||||
|
env:
|
||||||
|
TEST_MYSQL_SERVER: 127.0.0.1
|
||||||
|
TEST_MYSQL_PORT: ${{ job.services.mysql.ports['3306'] }}
|
||||||
|
TEST_MARIADB_SERVER: 127.0.0.1
|
||||||
|
TEST_MARIADB_PORT: ${{ job.services.mariadb.ports['3306'] }}
|
||||||
|
run: ./vendor/bin/phpunit
|
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -5,3 +5,5 @@ test.php
|
||||||
test.sqlite
|
test.sqlite
|
||||||
*.test.sqlite
|
*.test.sqlite
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
examples/example.sqlite
|
||||||
|
examples/example.sqlite-journal
|
||||||
|
|
|
@ -1,28 +0,0 @@
|
||||||
image: php:7.1-alpine
|
|
||||||
|
|
||||||
services:
|
|
||||||
- mysql:5.7
|
|
||||||
|
|
||||||
variables:
|
|
||||||
MYSQL_DATABASE: destructr_test
|
|
||||||
MYSQL_ROOT_PASSWORD: badpassword
|
|
||||||
|
|
||||||
before_script:
|
|
||||||
- apk update
|
|
||||||
- apk add git
|
|
||||||
- docker-php-ext-install pdo
|
|
||||||
- curl -sS https://getcomposer.org/installer | php
|
|
||||||
- php composer.phar install
|
|
||||||
|
|
||||||
test:local:
|
|
||||||
script:
|
|
||||||
- php composer.phar test-local
|
|
||||||
|
|
||||||
test:mysql:
|
|
||||||
script:
|
|
||||||
- docker-php-ext-install pdo_mysql
|
|
||||||
- php composer.phar test-mysql
|
|
||||||
|
|
||||||
test:sqlite:
|
|
||||||
script:
|
|
||||||
- php composer.phar test-sqlite
|
|
47
README.md
47
README.md
|
@ -1,5 +1,7 @@
|
||||||
# Destructr
|
# Destructr
|
||||||
|
|
||||||
|
[![PHPUnit Tests](https://github.com/jobyone/destructr/actions/workflows/test.yml/badge.svg)](https://github.com/jobyone/destructr/actions/workflows/test.yml)
|
||||||
|
|
||||||
Destructr is a specialized ORM that allows a seamless mix of structured, relational data with unstructured JSON data.
|
Destructr is a specialized ORM that allows a seamless mix of structured, relational data with unstructured JSON data.
|
||||||
|
|
||||||
## Getting started
|
## Getting started
|
||||||
|
@ -64,19 +66,58 @@ $obj->undelete();
|
||||||
$obj->delete(true);
|
$obj->delete(true);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Searching
|
||||||
|
|
||||||
|
Factories provide an interface for creating `Search` objects, which allow you to enter in various SQL clauses in a structured and abstract fashion.
|
||||||
|
|
||||||
|
```php
|
||||||
|
// get a new search object from the factory
|
||||||
|
$search = $factory->search();
|
||||||
|
|
||||||
|
// Search::where() takes SQL for the WHERE clause of a query
|
||||||
|
// ${path} syntax is used to reference data within objects, and
|
||||||
|
// works everywhere in searches
|
||||||
|
$search->where('${dso.date.modified} > :time');
|
||||||
|
|
||||||
|
// Search::order() takes SQL to go inside an ORDER BY clause
|
||||||
|
// in the final query.
|
||||||
|
$search->order('${dso.date.modified} desc');
|
||||||
|
|
||||||
|
// Search limit/offset methods can be used for pagination
|
||||||
|
// there is also a paginate() method for more conveniently
|
||||||
|
// paginating results
|
||||||
|
$search->paginate(20,1);
|
||||||
|
|
||||||
|
// Search::execute() returns an array of the resulting objects
|
||||||
|
$results = $search->execute();
|
||||||
|
```
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
This system relies **heavily** on the JSON features of the underlying database.
|
This system relies **heavily** on the JSON features of the underlying database.
|
||||||
This means it cannot possibly run without a database that supports JSON features.
|
This means it cannot possibly run without a database that supports JSON features.
|
||||||
Exact requirements are in flux during development, but basically if a database doesn't have JSON functions it's probably impossible for Destructr to ever work with it.
|
Basically if a database doesn't have JSON functions it's probably impossible for Destructr to ever work with it.
|
||||||
|
|
||||||
|
At the moment there is pretty decent support for:
|
||||||
|
|
||||||
|
* MySQL >=5.7.8
|
||||||
|
* MariaDB >=10.2.7
|
||||||
|
* SQLite 3 (with some caveats)
|
||||||
|
|
||||||
In practice this means Destructr will **never** be able to run on less than the following versions of the following popular databases:
|
In practice this means Destructr will **never** be able to run on less than the following versions of the following popular databases:
|
||||||
|
|
||||||
* MySQL >=5.7
|
* MySQL >=5.7.8
|
||||||
* MariaDB >=10.2
|
* MariaDB >=10.2.7
|
||||||
* PostgreSQL >=9.3
|
* PostgreSQL >=9.3
|
||||||
* SQL Server >=2016
|
* SQL Server >=2016
|
||||||
|
|
||||||
Theoretically Destructr is also an excellent fit for NoSQL databases.
|
Theoretically Destructr is also an excellent fit for NoSQL databases.
|
||||||
If I ever find myself needing it there's a good chance it's possible to write drivers for running it on something like MongoDB as well.
|
If I ever find myself needing it there's a good chance it's possible to write drivers for running it on something like MongoDB as well.
|
||||||
It might even be kind of easy.
|
It might even be kind of easy.
|
||||||
|
|
||||||
|
### SQLite caveats
|
||||||
|
|
||||||
|
MySQL and MariaDB drivers set virtual columns to be generated automatically using their native JSON functions.
|
||||||
|
SQLite doesn't have native JSON (in most environments, at least), so Destructr itself manually updates virtual columns whenever objects are inserted or updated.
|
||||||
|
In practice this won't matter *if* you are doing all your insertion and updating via Destructr.
|
||||||
|
If you're doing updates to your database via any other method, however, you need to be aware of this, and manually update the virtual column values.
|
||||||
|
|
|
@ -7,7 +7,6 @@
|
||||||
"prefer-stable": true,
|
"prefer-stable": true,
|
||||||
"require": {
|
"require": {
|
||||||
"php": ">=7.1",
|
"php": ">=7.1",
|
||||||
"mofodojodino/profanity-filter": "^1.3",
|
|
||||||
"byjoby/flatrr": "^1"
|
"byjoby/flatrr": "^1"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
|
@ -18,12 +17,12 @@
|
||||||
"test": [
|
"test": [
|
||||||
"phpunit"
|
"phpunit"
|
||||||
],
|
],
|
||||||
"test-local": [
|
|
||||||
"phpunit --testsuite Local"
|
|
||||||
],
|
|
||||||
"test-mysql": [
|
"test-mysql": [
|
||||||
"phpunit --testsuite MySQL"
|
"phpunit --testsuite MySQL"
|
||||||
],
|
],
|
||||||
|
"test-mariadb": [
|
||||||
|
"phpunit --testsuite MariaDB"
|
||||||
|
],
|
||||||
"test-sqlite": [
|
"test-sqlite": [
|
||||||
"phpunit --testsuite SQLite"
|
"phpunit --testsuite SQLite"
|
||||||
]
|
]
|
||||||
|
|
34
examples/example_factory.php
Normal file
34
examples/example_factory.php
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Destructr\Factory;
|
||||||
|
|
||||||
|
class ExampleFactory extends Factory
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Example factory with a different schema, to index on random_data for faster searching
|
||||||
|
*/
|
||||||
|
protected $schema = [
|
||||||
|
'dso.id' => [
|
||||||
|
'name' => 'dso_id', //column name to be used
|
||||||
|
'type' => 'VARCHAR(16)', //column type
|
||||||
|
'index' => 'BTREE', //whether/how to index
|
||||||
|
'unique' => true, //whether column should be unique
|
||||||
|
'primary' => true, //whether column should be the primary key
|
||||||
|
],
|
||||||
|
'dso.type' => [
|
||||||
|
'name' => 'dso_type',
|
||||||
|
'type' => 'VARCHAR(30)',
|
||||||
|
'index' => 'BTREE',
|
||||||
|
],
|
||||||
|
'dso.deleted' => [
|
||||||
|
'name' => 'dso_deleted',
|
||||||
|
'type' => 'BIGINT',
|
||||||
|
'index' => 'BTREE',
|
||||||
|
],
|
||||||
|
'random_data' => [
|
||||||
|
'name' => 'random_data',
|
||||||
|
'type' => 'VARCHAR(64)',
|
||||||
|
'index' => 'BTREE',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
50
examples/example_use.php
Normal file
50
examples/example_use.php
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* This file demonstrates some basic uses of Destructr, from the creation
|
||||||
|
* of a connection and factory, through to creating, inserting, updating,
|
||||||
|
* deleting, and querying data.
|
||||||
|
*/
|
||||||
|
include __DIR__ . '/../vendor/autoload.php';
|
||||||
|
|
||||||
|
/*
|
||||||
|
SQLite drivers can be created by the default factory.
|
||||||
|
A charset of UTF8 should be specified, to avoid character encoding
|
||||||
|
issues.
|
||||||
|
*/
|
||||||
|
$driver = \Destructr\DriverFactory::factory(
|
||||||
|
'sqlite:' . __DIR__ . '/example.sqlite'
|
||||||
|
);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Creates a factory using the table 'example_table', and creates
|
||||||
|
the necessary table. Note that prepareEnvironment() can safely be called
|
||||||
|
multiple times. updateEnvironment shouldn't be used this way in production,
|
||||||
|
as if it is called more than once per second during a schema change, errors
|
||||||
|
may be introduced.
|
||||||
|
*/
|
||||||
|
include __DIR__ . '/example_factory.php';
|
||||||
|
$factory = new ExampleFactory($driver, 'example_table');
|
||||||
|
$factory->prepareEnvironment();
|
||||||
|
$factory->updateEnvironment();
|
||||||
|
|
||||||
|
/*
|
||||||
|
Inserting a record
|
||||||
|
*/
|
||||||
|
$obj = $factory->create(
|
||||||
|
[
|
||||||
|
'dso.type'=>'foobar',
|
||||||
|
'random_data' => md5(rand())
|
||||||
|
]
|
||||||
|
);
|
||||||
|
$obj->insert();
|
||||||
|
|
||||||
|
/*
|
||||||
|
Search by random data field, which is indexed due to the
|
||||||
|
ExampleFactory class' $schema property.
|
||||||
|
*/
|
||||||
|
$search = $factory->search();
|
||||||
|
$search->where('${random_data} LIKE :q');
|
||||||
|
$result = $search->execute(['q'=>'ab%']);
|
||||||
|
foreach($result as $r) {
|
||||||
|
var_dump($r->get());
|
||||||
|
}
|
10
phpunit.xml
10
phpunit.xml
|
@ -1,15 +1,13 @@
|
||||||
<phpunit bootstrap="vendor/autoload.php">
|
<phpunit bootstrap="vendor/autoload.php">
|
||||||
<testsuites>
|
<testsuites>
|
||||||
<testsuite name="Local">
|
|
||||||
<directory>tests</directory>
|
|
||||||
<exclude>tests/Drivers</exclude>
|
|
||||||
<exclude>tests/LegacyDrivers</exclude>
|
|
||||||
</testsuite>
|
|
||||||
<testsuite name="MySQL">
|
<testsuite name="MySQL">
|
||||||
<directory>tests/Drivers/MySQL</directory>
|
<directory>tests/Drivers/MySQL</directory>
|
||||||
</testsuite>
|
</testsuite>
|
||||||
|
<testsuite name="MariaDB">
|
||||||
|
<directory>tests/Drivers/MariaDB</directory>
|
||||||
|
</testsuite>
|
||||||
<testsuite name="SQLite">
|
<testsuite name="SQLite">
|
||||||
<directory>tests/LegacyDrivers/SQLite</directory>
|
<directory>tests/Drivers/SQLite</directory>
|
||||||
</testsuite>
|
</testsuite>
|
||||||
</testsuites>
|
</testsuites>
|
||||||
</phpunit>
|
</phpunit>
|
||||||
|
|
29
src/DSO.php
29
src/DSO.php
|
@ -1,11 +1,11 @@
|
||||||
<?php
|
<?php
|
||||||
/* Destructr | https://gitlab.com/byjoby/destructr | MIT License */
|
/* Destructr | https://github.com/jobyone/destructr | MIT License */
|
||||||
namespace Destructr;
|
namespace Destructr;
|
||||||
|
|
||||||
use \Flatrr\FlatArray;
|
use \Flatrr\FlatArray;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface for DeStructure Objects (DSOs). These are the class that is
|
* Interface for DeStructured Objects (DSOs). These are the class that is
|
||||||
* actually used for storing and retrieving partially-structured data from the
|
* actually used for storing and retrieving partially-structured data from the
|
||||||
* database.
|
* database.
|
||||||
*/
|
*/
|
||||||
|
@ -15,7 +15,7 @@ class DSO extends FlatArray implements DSOInterface
|
||||||
protected $changes;
|
protected $changes;
|
||||||
protected $removals;
|
protected $removals;
|
||||||
|
|
||||||
public function __construct(array $data = null, DSOFactoryInterface &$factory = null)
|
public function __construct(array $data = null, Factory $factory = null)
|
||||||
{
|
{
|
||||||
$this->resetChanges();
|
$this->resetChanges();
|
||||||
parent::__construct($data);
|
parent::__construct($data);
|
||||||
|
@ -32,22 +32,22 @@ class DSO extends FlatArray implements DSOInterface
|
||||||
//does nothing
|
//does nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
public function delete(bool $permanent = false) : bool
|
public function delete(bool $permanent = false): bool
|
||||||
{
|
{
|
||||||
return $this->factory->delete($this, $permanent);
|
return $this->factory->delete($this, $permanent);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function undelete() : bool
|
public function undelete(): bool
|
||||||
{
|
{
|
||||||
return $this->factory->undelete($this);
|
return $this->factory->undelete($this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function insert() : bool
|
public function insert(): bool
|
||||||
{
|
{
|
||||||
return $this->factory()->insert($this);
|
return $this->factory()->insert($this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function update() : bool
|
public function update(bool $sneaky = false): bool
|
||||||
{
|
{
|
||||||
return $this->factory()->update($this);
|
return $this->factory()->update($this);
|
||||||
}
|
}
|
||||||
|
@ -58,17 +58,17 @@ class DSO extends FlatArray implements DSOInterface
|
||||||
$this->removals = new FlatArray();
|
$this->removals = new FlatArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function changes() : array
|
public function changes(): array
|
||||||
{
|
{
|
||||||
return $this->changes->get();
|
return $this->changes->get();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function removals() : array
|
public function removals(): array
|
||||||
{
|
{
|
||||||
return $this->removals->get();
|
return $this->removals->get();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function set(string $name = null, $value, $force=false)
|
public function set(?string $name, $value, $force = false)
|
||||||
{
|
{
|
||||||
$name = strtolower($name);
|
$name = strtolower($name);
|
||||||
if ($this->get($name) === $value) {
|
if ($this->get($name) === $value) {
|
||||||
|
@ -80,7 +80,7 @@ class DSO extends FlatArray implements DSOInterface
|
||||||
foreach ($this->get($name) as $k => $v) {
|
foreach ($this->get($name) as $k => $v) {
|
||||||
if (!isset($value[$k])) {
|
if (!isset($value[$k])) {
|
||||||
if ($name) {
|
if ($name) {
|
||||||
$k = $name.'.'.$k;
|
$k = $name . '.' . $k;
|
||||||
}
|
}
|
||||||
$this->unset($k);
|
$this->unset($k);
|
||||||
}
|
}
|
||||||
|
@ -91,7 +91,7 @@ class DSO extends FlatArray implements DSOInterface
|
||||||
//recursively set individual values so we can track them
|
//recursively set individual values so we can track them
|
||||||
foreach ($value as $k => $v) {
|
foreach ($value as $k => $v) {
|
||||||
if ($name) {
|
if ($name) {
|
||||||
$k = $name.'.'.$k;
|
$k = $name . '.' . $k;
|
||||||
}
|
}
|
||||||
$this->set($k, $v, $force);
|
$this->set($k, $v, $force);
|
||||||
}
|
}
|
||||||
|
@ -102,8 +102,7 @@ class DSO extends FlatArray implements DSOInterface
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function unset(?string $name)
|
function unset(?string $name) {
|
||||||
{
|
|
||||||
if (isset($this[$name])) {
|
if (isset($this[$name])) {
|
||||||
$this->removals->set($name, $this->get($name));
|
$this->removals->set($name, $this->get($name));
|
||||||
unset($this->changes[$name]);
|
unset($this->changes[$name]);
|
||||||
|
@ -111,7 +110,7 @@ class DSO extends FlatArray implements DSOInterface
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function factory(DSOFactoryInterface &$factory = null) : ?DSOFactoryInterface
|
public function factory(Factory $factory = null): ?Factory
|
||||||
{
|
{
|
||||||
if ($factory) {
|
if ($factory) {
|
||||||
$this->factory = $factory;
|
$this->factory = $factory;
|
||||||
|
|
|
@ -1,20 +0,0 @@
|
||||||
<?php
|
|
||||||
/* Destructr | https://gitlab.com/byjoby/destructr | MIT License */
|
|
||||||
namespace Destructr;
|
|
||||||
|
|
||||||
interface DSOFactoryInterface
|
|
||||||
{
|
|
||||||
public function __construct(Drivers\DSODriverInterface &$driver, string $table);
|
|
||||||
|
|
||||||
public function class(array $data) : ?string;
|
|
||||||
|
|
||||||
public function createTable() : bool;
|
|
||||||
public function create(array $data = array()) : DSOInterface;
|
|
||||||
public function read(string $value, string $field = 'dso.id', $deleted = false) : ?DSOInterface;
|
|
||||||
public function insert(DSOInterface &$dso) : bool;
|
|
||||||
public function update(DSOInterface &$dso) : bool;
|
|
||||||
public function delete(DSOInterface &$dso, bool $permanent = false) : bool;
|
|
||||||
|
|
||||||
public function search() : Search;
|
|
||||||
public function executeSearch(Search $search, array $params = array(), $deleted = false) : array;
|
|
||||||
}
|
|
|
@ -1,5 +1,5 @@
|
||||||
<?php
|
<?php
|
||||||
/* Destructr | https://gitlab.com/byjoby/destructr | MIT License */
|
/* Destructr | https://github.com/jobyone/destructr | MIT License */
|
||||||
namespace Destructr;
|
namespace Destructr;
|
||||||
|
|
||||||
use Flatrr\FlatArrayInterface;
|
use Flatrr\FlatArrayInterface;
|
||||||
|
@ -11,17 +11,17 @@ use Flatrr\FlatArrayInterface;
|
||||||
*/
|
*/
|
||||||
interface DSOInterface extends FlatArrayInterface
|
interface DSOInterface extends FlatArrayInterface
|
||||||
{
|
{
|
||||||
public function __construct(array $data = null, DSOFactoryInterface &$factory = null);
|
public function __construct(array $data = null, Factory $factory = null);
|
||||||
public function factory(DSOFactoryInterface &$factory = null) : ?DSOFactoryInterface;
|
public function factory(Factory $factory = null): ?Factory;
|
||||||
|
|
||||||
public function set(string $name = null, $value, $force=false);
|
public function set(?string $name, $value, $force = false);
|
||||||
|
|
||||||
public function resetChanges();
|
public function resetChanges();
|
||||||
public function changes() : array;
|
public function changes(): array;
|
||||||
public function removals() : array;
|
public function removals(): array;
|
||||||
|
|
||||||
public function insert() : bool;
|
public function insert(): bool;
|
||||||
public function update() : bool;
|
public function update(bool $sneaky = false): bool;
|
||||||
public function delete(bool $permanent = false) : bool;
|
public function delete(bool $permanent = false): bool;
|
||||||
public function undelete() : bool;
|
public function undelete(): bool;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,16 @@
|
||||||
<?php
|
<?php
|
||||||
/* Destructr | https://gitlab.com/byjoby/destructr | MIT License */
|
/* Destructr | https://github.com/jobyone/destructr | MIT License */
|
||||||
namespace Destructr;
|
namespace Destructr;
|
||||||
|
|
||||||
class DriverFactory
|
class DriverFactory
|
||||||
{
|
{
|
||||||
public static $map = [
|
public static $map = [
|
||||||
|
'mariadb' => Drivers\MariaDBDriver::class,
|
||||||
'mysql' => Drivers\MySQLDriver::class,
|
'mysql' => Drivers\MySQLDriver::class,
|
||||||
'sqlite' => LegacyDrivers\SQLiteDriver::class
|
'sqlite' => Drivers\SQLiteDriver::class,
|
||||||
];
|
];
|
||||||
|
|
||||||
public static function factory(string $dsn, string $username=null, string $password=null, array $options=null, string $type = null) : ?Drivers\DSODriverInterface
|
public static function factory(string $dsn, string $username = null, string $password = null, array $options = null, string $type = null): ?Drivers\AbstractDriver
|
||||||
{
|
{
|
||||||
if (!$type) {
|
if (!$type) {
|
||||||
$type = @array_shift(explode(':', $dsn, 2));
|
$type = @array_shift(explode(':', $dsn, 2));
|
||||||
|
@ -21,4 +22,19 @@ class DriverFactory
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function factoryFromPDO(\PDO $pdo, string $type = null): ?Drivers\AbstractDriver
|
||||||
|
{
|
||||||
|
if (!$type) {
|
||||||
|
$type = $pdo->getAttribute(\PDO::ATTR_DRIVER_NAME);
|
||||||
|
}
|
||||||
|
$type = strtolower($type);
|
||||||
|
if ($class = @static::$map[$type]) {
|
||||||
|
$f = new $class();
|
||||||
|
$f->pdo($pdo);
|
||||||
|
return $f;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,120 +1,24 @@
|
||||||
<?php
|
<?php
|
||||||
/* Destructr | https://gitlab.com/byjoby/destructr | MIT License */
|
/* Destructr | https://github.com/jobyone/destructr | MIT License */
|
||||||
namespace Destructr\Drivers;
|
namespace Destructr\Drivers;
|
||||||
|
|
||||||
use Destructr\DSOInterface;
|
use Destructr\DSOInterface;
|
||||||
use Destructr\Search;
|
use Destructr\Search;
|
||||||
|
|
||||||
//TODO: Caching? It should happen somewhere in this class I think.
|
abstract class AbstractDriver
|
||||||
abstract class AbstractDriver implements DSODriverInterface
|
|
||||||
{
|
{
|
||||||
public $lastPreparationErrorOn;
|
const SCHEMA_TABLE = 'destructr_schema';
|
||||||
public $pdo;
|
|
||||||
const EXTENSIBLE_VIRTUAL_COLUMNS = true;
|
|
||||||
|
|
||||||
public function __construct(string $dsn, string $username=null, string $password=null, array $options=null)
|
abstract public function errorInfo();
|
||||||
{
|
abstract public function update(string $table, DSOInterface $dso): bool;
|
||||||
if (!$this->pdo = new \PDO($dsn, $username, $password, $options)) {
|
abstract public function delete(string $table, DSOInterface $dso): bool;
|
||||||
throw new \Exception("Error creating PDO connection");
|
abstract public function count(string $table, Search $search, array $params): int;
|
||||||
}
|
abstract public function select(string $table, Search $search, array $params);
|
||||||
}
|
abstract public function insert(string $table, DSOInterface $dso): bool;
|
||||||
|
abstract public function beginTransaction(): bool;
|
||||||
protected function expandPaths($value)
|
abstract public function commit(): bool;
|
||||||
{
|
abstract public function rollBack(): bool;
|
||||||
if ($value === null) {
|
abstract public function prepareEnvironment(string $table, array $schema): bool;
|
||||||
return null;
|
abstract public function updateEnvironment(string $table, array $schema): bool;
|
||||||
}
|
abstract public function checkEnvironment(string $table, array $schema): bool;
|
||||||
$value = preg_replace_callback(
|
|
||||||
'/\$\{([^\}\\\]+)\}/',
|
|
||||||
function ($matches) {
|
|
||||||
return $this->expandPath($matches[1]);
|
|
||||||
},
|
|
||||||
$value
|
|
||||||
);
|
|
||||||
return $value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function errorInfo()
|
|
||||||
{
|
|
||||||
return $this->pdo->errorInfo();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function createTable(string $table, array $virtualColumns) : bool
|
|
||||||
{
|
|
||||||
$sql = $this->sql_ddl([
|
|
||||||
'table'=>$table,
|
|
||||||
'virtualColumns'=>$virtualColumns
|
|
||||||
]);
|
|
||||||
return $this->pdo->exec($sql) !== false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function update(string $table, DSOInterface $dso) : bool
|
|
||||||
{
|
|
||||||
if (!$dso->changes() && !$dso->removals()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
$s = $this->getStatement(
|
|
||||||
'setJSON',
|
|
||||||
['table'=>$table]
|
|
||||||
);
|
|
||||||
return $s->execute([
|
|
||||||
':dso_id' => $dso['dso.id'],
|
|
||||||
':data' => json_encode($dso->get())
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function delete(string $table, DSOInterface $dso) : bool
|
|
||||||
{
|
|
||||||
$s = $this->getStatement(
|
|
||||||
'delete',
|
|
||||||
['table'=>$table]
|
|
||||||
);
|
|
||||||
return $s->execute([
|
|
||||||
':dso_id' => $dso['dso.id']
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function select(string $table, Search $search, array $params)
|
|
||||||
{
|
|
||||||
$s = $this->getStatement(
|
|
||||||
'select',
|
|
||||||
['table'=>$table,'search'=>$search]
|
|
||||||
);
|
|
||||||
if (!$s->execute($params)) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
return @$s->fetchAll(\PDO::FETCH_ASSOC);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function insert(string $table, DSOInterface $dso) : bool
|
|
||||||
{
|
|
||||||
return $this->getStatement(
|
|
||||||
'insert',
|
|
||||||
['table'=>$table]
|
|
||||||
)->execute(
|
|
||||||
[':data'=>json_encode($dso->get())]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function getStatement(string $type, $args=array()) : \PDOStatement
|
|
||||||
{
|
|
||||||
$fn = 'sql_'.$type;
|
|
||||||
if (!method_exists($this, $fn)) {
|
|
||||||
throw new \Exception("Error getting SQL statement, driver doesn't have a method named $fn");
|
|
||||||
}
|
|
||||||
$sql = $this->$fn($args);
|
|
||||||
$stmt = $this->pdo->prepare($sql);
|
|
||||||
if (!$stmt) {
|
|
||||||
$this->lastPreparationErrorOn = $sql;
|
|
||||||
throw new \Exception("Error preparing statement: ".implode(': ', $this->pdo->errorInfo()), 1);
|
|
||||||
}
|
|
||||||
return $stmt;
|
|
||||||
//TODO: turn this on someday and see if caching statements helps in the real world
|
|
||||||
// $sql = $this->$fn($args);
|
|
||||||
// $id = md5($sql);
|
|
||||||
// if (!isset($this->prepared[$id])) {
|
|
||||||
// $this->prepared[$id] = $this->pdo->prepare($sql);
|
|
||||||
// }
|
|
||||||
// return @$this->prepared[$id];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
384
src/Drivers/AbstractSQLDriver.php
Normal file
384
src/Drivers/AbstractSQLDriver.php
Normal file
|
@ -0,0 +1,384 @@
|
||||||
|
<?php
|
||||||
|
/* Destructr | https://github.com/jobyone/destructr | MIT License */
|
||||||
|
|
||||||
|
namespace Destructr\Drivers;
|
||||||
|
|
||||||
|
use Destructr\DSOInterface;
|
||||||
|
use Destructr\Search;
|
||||||
|
use PDO;
|
||||||
|
|
||||||
|
abstract class AbstractSQLDriver extends AbstractDriver
|
||||||
|
{
|
||||||
|
public $lastPreparationErrorOn;
|
||||||
|
public $pdo;
|
||||||
|
protected $schemas = [];
|
||||||
|
protected $transactionsEnabled = true;
|
||||||
|
|
||||||
|
abstract protected function sql_ddl(array $args = []): string;
|
||||||
|
abstract protected function expandPath(string $path): string;
|
||||||
|
abstract protected function sql_set_json(array $args): string;
|
||||||
|
abstract protected function sql_insert(array $args): string;
|
||||||
|
abstract protected function sql_create_schema_table(): string;
|
||||||
|
abstract protected function sql_table_exists(string $table): string;
|
||||||
|
abstract protected function buildIndexes(string $table, array $schema): bool;
|
||||||
|
abstract protected function addColumns($table, $schema): bool;
|
||||||
|
abstract protected function removeColumns($table, $schema): bool;
|
||||||
|
abstract protected function rebuildSchema($table, $schema): bool;
|
||||||
|
|
||||||
|
public function __construct(string $dsn = null, string $username = null, string $password = null, array $options = null)
|
||||||
|
{
|
||||||
|
if ($dsn) {
|
||||||
|
if (!($pdo = new \PDO($dsn, $username, $password, $options))) {
|
||||||
|
throw new \Exception("Error creating PDO connection");
|
||||||
|
}
|
||||||
|
$this->pdo($pdo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function tableExists(string $table): bool
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$stmt = $this->pdo()->prepare($this->sql_table_exists($table));
|
||||||
|
if ($stmt && $stmt->execute() !== false) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} catch (\Throwable $th) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createSchemaTable()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$this->pdo->exec($this->sql_create_schema_table());
|
||||||
|
} catch (\Throwable $th) {
|
||||||
|
}
|
||||||
|
return $this->tableExists(AbstractDriver::SCHEMA_TABLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function pdo(\PDO $pdo = null): ?\PDO
|
||||||
|
{
|
||||||
|
if ($pdo) {
|
||||||
|
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
|
||||||
|
$this->pdo = $pdo;
|
||||||
|
}
|
||||||
|
return $this->pdo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function disableTransactions()
|
||||||
|
{
|
||||||
|
$this->transactionsEnabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function enableTransactions()
|
||||||
|
{
|
||||||
|
$this->transactionsEnabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function beginTransaction(): bool
|
||||||
|
{
|
||||||
|
if (!$this->transactionsEnabled) return true;
|
||||||
|
return $this->pdo->beginTransaction();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function commit(): bool
|
||||||
|
{
|
||||||
|
if (!$this->transactionsEnabled) return true;
|
||||||
|
return $this->pdo->commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function rollBack(): bool
|
||||||
|
{
|
||||||
|
if (!$this->transactionsEnabled) return true;
|
||||||
|
return $this->pdo->rollBack();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function expandPaths($value)
|
||||||
|
{
|
||||||
|
if ($value === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
$value = preg_replace_callback(
|
||||||
|
'/\$\{([^\}\\\]+)\}/',
|
||||||
|
function ($matches) {
|
||||||
|
return $this->expandPath($matches[1]);
|
||||||
|
},
|
||||||
|
$value
|
||||||
|
);
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function errorInfo()
|
||||||
|
{
|
||||||
|
return $this->pdo->errorInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function prepareEnvironment(string $table, array $schema): bool
|
||||||
|
{
|
||||||
|
$this->beginTransaction();
|
||||||
|
if ($this->createSchemaTable() && $this->createTable($table, $schema)) {
|
||||||
|
$this->commit();
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
$this->rollBack();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updateEnvironment(string $table, array $schema): bool
|
||||||
|
{
|
||||||
|
$this->beginTransaction();
|
||||||
|
if ($this->updateTable($table, $schema)) {
|
||||||
|
$this->commit();
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
$this->rollBack();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function checkEnvironment(string $table, array $schema): bool
|
||||||
|
{
|
||||||
|
return $this->tableExists(AbstractDriver::SCHEMA_TABLE) && $this->getSchema($table) == $schema;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function updateTable($table, $schema): bool
|
||||||
|
{
|
||||||
|
$current = $this->getSchema($table);
|
||||||
|
$new = $schema;
|
||||||
|
if (!$current || $schema == $current) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
//do nothing with totally unchanged columns
|
||||||
|
foreach ($current as $c_id => $c) {
|
||||||
|
foreach ($schema as $n_id => $n) {
|
||||||
|
if ($n == $c && $n_id == $c_id) {
|
||||||
|
unset($current[$c_id]);
|
||||||
|
unset($new[$n_id]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$removed = $current;
|
||||||
|
$added = $new;
|
||||||
|
//apply changes
|
||||||
|
$out = [
|
||||||
|
'removeColumns' => $this->removeColumns($table, $removed),
|
||||||
|
'addColumns' => $this->addColumns($table, $added),
|
||||||
|
'rebuildSchema' => $this->rebuildSchema($table, $schema),
|
||||||
|
'buildIndexes' => $this->buildIndexes($table, $schema),
|
||||||
|
'saveSchema' => $this->saveSchema($table, $schema),
|
||||||
|
];
|
||||||
|
foreach ($out as $k => $v) {
|
||||||
|
if (!$v) {
|
||||||
|
user_error("An error occurred during updateTable for $table. The error happened during $k.", E_USER_WARNING);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return !!array_filter($out);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createTable(string $table, array $schema): bool
|
||||||
|
{
|
||||||
|
// check if table exists, if it doesn't, save into schema table
|
||||||
|
$tableExists = $this->tableExists($table);
|
||||||
|
// if table exists, but no schema does, assume table matches schema and save schema
|
||||||
|
if ($tableExists && !$this->getSchema($table)) {
|
||||||
|
$this->saveSchema($table, $schema);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// create table from scratch
|
||||||
|
$sql = $this->sql_ddl([
|
||||||
|
'table' => $table,
|
||||||
|
'schema' => $schema,
|
||||||
|
]);
|
||||||
|
$out = $this->pdo->exec($sql) !== false;
|
||||||
|
if ($out) {
|
||||||
|
$this->buildIndexes($table, $schema);
|
||||||
|
if (!$tableExists) {
|
||||||
|
$this->saveSchema($table, $schema);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $out;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSchema(string $table): ?array
|
||||||
|
{
|
||||||
|
if (!isset($this->schemas[$table])) {
|
||||||
|
$s = $this->getStatement(
|
||||||
|
'get_schema',
|
||||||
|
['table' => $table]
|
||||||
|
);
|
||||||
|
if (!$s->execute(['table' => $table])) {
|
||||||
|
$this->schemas[$table] = null;
|
||||||
|
} else {
|
||||||
|
if ($row = $s->fetch(\PDO::FETCH_ASSOC)) {
|
||||||
|
$this->schemas[$table] = @json_decode($row['schema_schema'], true);
|
||||||
|
} else {
|
||||||
|
$this->schemas[$table] = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return @$this->schemas[$table];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function saveSchema(string $table, array $schema): bool
|
||||||
|
{
|
||||||
|
$out = $this->pdo->exec(
|
||||||
|
$this->sql_save_schema($table, $schema)
|
||||||
|
) !== false;
|
||||||
|
unset($this->schemas[$table]);
|
||||||
|
return $out;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function sql_save_schema(string $table, array $schema)
|
||||||
|
{
|
||||||
|
$time = time();
|
||||||
|
$table = $this->pdo->quote($table);
|
||||||
|
$schema = $this->pdo->quote(json_encode($schema));
|
||||||
|
return <<<EOT
|
||||||
|
INSERT INTO `destructr_schema`
|
||||||
|
(schema_time,schema_table,schema_schema)
|
||||||
|
VALUES ($time,$table,$schema);
|
||||||
|
EOT;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function sql_get_schema(array $args)
|
||||||
|
{
|
||||||
|
return <<<EOT
|
||||||
|
SELECT * FROM `destructr_schema`
|
||||||
|
WHERE `schema_table` = :table
|
||||||
|
ORDER BY `schema_time` desc
|
||||||
|
LIMIT 1
|
||||||
|
EOT;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(string $table, DSOInterface $dso): bool
|
||||||
|
{
|
||||||
|
if (!$dso->changes() && !$dso->removals()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
$s = $this->getStatement(
|
||||||
|
'set_json',
|
||||||
|
['table' => $table]
|
||||||
|
);
|
||||||
|
return $s->execute([
|
||||||
|
':dso_id' => $dso['dso.id'],
|
||||||
|
':data' => json_encode($dso->get()),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function delete(string $table, DSOInterface $dso): bool
|
||||||
|
{
|
||||||
|
$s = $this->getStatement(
|
||||||
|
'delete',
|
||||||
|
['table' => $table]
|
||||||
|
);
|
||||||
|
return $s->execute([
|
||||||
|
':dso_id' => $dso['dso.id'],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function count(string $table, Search $search, array $params): int
|
||||||
|
{
|
||||||
|
$s = $this->getStatement(
|
||||||
|
'count',
|
||||||
|
['table' => $table, 'search' => $search]
|
||||||
|
);
|
||||||
|
if (!$s->execute($params)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return intval($s->fetchAll(\PDO::FETCH_COLUMN)[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function select(string $table, Search $search, array $params)
|
||||||
|
{
|
||||||
|
$s = $this->getStatement(
|
||||||
|
'select',
|
||||||
|
['table' => $table, 'search' => $search]
|
||||||
|
);
|
||||||
|
if (!$s->execute($params)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return @$s->fetchAll(\PDO::FETCH_ASSOC);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function insert(string $table, DSOInterface $dso): bool
|
||||||
|
{
|
||||||
|
return $this->getStatement(
|
||||||
|
'insert',
|
||||||
|
['table' => $table]
|
||||||
|
)->execute(
|
||||||
|
[':data' => json_encode($dso->get())]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getStatement(string $type, $args = array()): \PDOStatement
|
||||||
|
{
|
||||||
|
$fn = 'sql_' . $type;
|
||||||
|
if (!method_exists($this, $fn)) {
|
||||||
|
throw new \Exception("Error getting SQL statement, driver doesn't have a method named $fn");
|
||||||
|
}
|
||||||
|
$sql = $this->$fn($args);
|
||||||
|
$stmt = $this->pdo->prepare($sql);
|
||||||
|
if (!$stmt) {
|
||||||
|
$this->lastPreparationErrorOn = $sql;
|
||||||
|
throw new \Exception("Error preparing statement: " . implode(': ', $this->pdo->errorInfo()), 1);
|
||||||
|
}
|
||||||
|
return $stmt;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Within the search we expand strings like ${dso.id} into JSON queries.
|
||||||
|
* Note that the Search will have already had these strings expanded into
|
||||||
|
* column names if there are virtual columns configured for them. That
|
||||||
|
* happens in the Factory before it gets here.
|
||||||
|
*/
|
||||||
|
protected function sql_select(array $args): string
|
||||||
|
{
|
||||||
|
//extract query parts from Search and expand paths
|
||||||
|
$where = $this->expandPaths($args['search']->where());
|
||||||
|
$order = $this->expandPaths($args['search']->order());
|
||||||
|
$limit = $args['search']->limit();
|
||||||
|
$offset = $args['search']->offset();
|
||||||
|
//select from
|
||||||
|
$out = ["SELECT * FROM `{$args['table']}`"];
|
||||||
|
//where statement
|
||||||
|
if ($where !== null) {
|
||||||
|
$out[] = "WHERE " . $where;
|
||||||
|
}
|
||||||
|
//order statement
|
||||||
|
if ($order !== null) {
|
||||||
|
$out[] = "ORDER BY " . $order;
|
||||||
|
}
|
||||||
|
//limit
|
||||||
|
if ($limit !== null) {
|
||||||
|
$out[] = "LIMIT " . $limit;
|
||||||
|
}
|
||||||
|
//offset
|
||||||
|
if ($offset !== null) {
|
||||||
|
$out[] = "OFFSET " . $offset;
|
||||||
|
}
|
||||||
|
//return
|
||||||
|
return implode(PHP_EOL, $out) . ';';
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function sql_count(array $args): string
|
||||||
|
{
|
||||||
|
//extract query parts from Search and expand paths
|
||||||
|
$where = $this->expandPaths($args['search']->where());
|
||||||
|
//select from
|
||||||
|
$out = ["SELECT count(dso_id) FROM `{$args['table']}`"];
|
||||||
|
//where statement
|
||||||
|
if ($where !== null) {
|
||||||
|
$out[] = "WHERE " . $where;
|
||||||
|
}
|
||||||
|
//return
|
||||||
|
return implode(PHP_EOL, $out) . ';';
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function sql_delete(array $args): string
|
||||||
|
{
|
||||||
|
return 'DELETE FROM `' . $args['table'] . '` WHERE `dso_id` = :dso_id;';
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,19 +0,0 @@
|
||||||
<?php
|
|
||||||
/* Destructr | https://gitlab.com/byjoby/destructr | MIT License */
|
|
||||||
namespace Destructr\Drivers;
|
|
||||||
|
|
||||||
use Destructr\DSOInterface;
|
|
||||||
use Destructr\Search;
|
|
||||||
|
|
||||||
interface DSODriverInterface
|
|
||||||
{
|
|
||||||
public function __construct(string $dsn, string $username=null, string $password=null, array $options=null);
|
|
||||||
|
|
||||||
public function createTable(string $table, array $virtualColumns) : bool;
|
|
||||||
public function select(string $table, Search $search, array $params);
|
|
||||||
public function insert(string $table, DSOInterface $dso) : bool;
|
|
||||||
public function update(string $table, DSOInterface $dso) : bool;
|
|
||||||
public function delete(string $table, DSOInterface $dso) : bool;
|
|
||||||
|
|
||||||
public function errorInfo();
|
|
||||||
}
|
|
65
src/Drivers/MariaDBDriver.php
Normal file
65
src/Drivers/MariaDBDriver.php
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
<?php
|
||||||
|
/* Destructr | https://github.com/jobyone/destructr | MIT License */
|
||||||
|
|
||||||
|
namespace Destructr\Drivers;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* What this driver supports: MariaDB >= 10.2.7
|
||||||
|
*/
|
||||||
|
class MariaDBDriver extends MySQLDriver
|
||||||
|
{
|
||||||
|
protected function sql_ddl(array $args = []): string
|
||||||
|
{
|
||||||
|
$out = [];
|
||||||
|
$out[] = "CREATE TABLE IF NOT EXISTS `{$args['table']}` (";
|
||||||
|
$lines = [];
|
||||||
|
$lines[] = "`json_data` JSON DEFAULT NULL";
|
||||||
|
foreach ($args['schema'] as $path => $col) {
|
||||||
|
$line = "`{$col['name']}` {$col['type']} GENERATED ALWAYS AS (" . $this->expandPath($path) . ") VIRTUAL";
|
||||||
|
$lines[] = $line;
|
||||||
|
}
|
||||||
|
$out[] = implode(',' . PHP_EOL, $lines);
|
||||||
|
$out[] = ") ENGINE=InnoDB DEFAULT CHARSET=utf8;";
|
||||||
|
$out = implode(PHP_EOL, $out);
|
||||||
|
return $out;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function buildIndexes(string $table, array $schema): bool
|
||||||
|
{
|
||||||
|
foreach ($schema as $path => $col) {
|
||||||
|
try {
|
||||||
|
if (@$col['primary']) {
|
||||||
|
$this->pdo->exec(
|
||||||
|
"CREATE UNIQUE INDEX `{$table}_{$col['name']}_idx` ON {$table} (`{$col['name']}`) USING BTREE"
|
||||||
|
);
|
||||||
|
} elseif (@$col['unique'] && $as = @$col['index']) {
|
||||||
|
$this->pdo->exec(
|
||||||
|
"CREATE UNIQUE INDEX `{$table}_{$col['name']}_idx` ON {$table} (`{$col['name']}`) USING $as"
|
||||||
|
);
|
||||||
|
} elseif ($as = @$col['index']) {
|
||||||
|
$this->pdo->exec(
|
||||||
|
"CREATE INDEX `{$table}_{$col['name']}_idx` ON {$table} (`{$col['name']}`) USING $as"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (\Throwable $th) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function addColumns($table, $schema): bool
|
||||||
|
{
|
||||||
|
$out = true;
|
||||||
|
foreach ($schema as $path => $col) {
|
||||||
|
$line = "ALTER TABLE `{$table}` ADD COLUMN `${col['name']}` {$col['type']} GENERATED ALWAYS AS (" . $this->expandPath($path) . ")";
|
||||||
|
if (@$col['primary']) {
|
||||||
|
$line .= ' PERSISTENT;';
|
||||||
|
} else {
|
||||||
|
$line .= ' VIRTUAL;';
|
||||||
|
}
|
||||||
|
$out = $out &&
|
||||||
|
$this->pdo->exec($line) !== false;
|
||||||
|
}
|
||||||
|
return $out;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,97 +1,119 @@
|
||||||
<?php
|
<?php
|
||||||
/* Destructr | https://gitlab.com/byjoby/destructr | MIT License */
|
/* Destructr | https://github.com/jobyone/destructr | MIT License */
|
||||||
|
|
||||||
namespace Destructr\Drivers;
|
namespace Destructr\Drivers;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* What this driver supports: MySQL and MariaDB databases new enough to support
|
* What this driver supports: MySQL >= 5.7.8
|
||||||
* JSON functions. This means:
|
|
||||||
* * MySQL >= 5.7
|
|
||||||
* * MariaDB >= 10.2
|
|
||||||
*/
|
*/
|
||||||
class MySQLDriver extends AbstractDriver
|
class MySQLDriver extends AbstractSQLDriver
|
||||||
{
|
{
|
||||||
/**
|
protected function sql_ddl(array $args = []): string
|
||||||
* Within the search we expand strings like ${dso.id} into JSON queries.
|
|
||||||
* Note that the Search will have already had these strings expanded into
|
|
||||||
* column names if there are virtual columns configured for them. That
|
|
||||||
* happens in the Factory before it gets here.
|
|
||||||
*/
|
|
||||||
protected function sql_select($args)
|
|
||||||
{
|
|
||||||
//extract query parts from Search and expand paths
|
|
||||||
$where = $this->expandPaths($args['search']->where());
|
|
||||||
$order = $this->expandPaths($args['search']->order());
|
|
||||||
$limit = $args['search']->limit();
|
|
||||||
$offset = $args['search']->offset();
|
|
||||||
//select from
|
|
||||||
$out = ["SELECT * FROM `{$args['table']}`"];
|
|
||||||
//where statement
|
|
||||||
if ($where !== null) {
|
|
||||||
$out[] = "WHERE ".$where;
|
|
||||||
}
|
|
||||||
//order statement
|
|
||||||
if ($order !== null) {
|
|
||||||
$out[] = "ORDER BY ".$order;
|
|
||||||
}
|
|
||||||
//limit
|
|
||||||
if ($limit !== null) {
|
|
||||||
$out[] = "LIMIT ".$limit;
|
|
||||||
}
|
|
||||||
//offset
|
|
||||||
if ($offset !== null) {
|
|
||||||
$out[] = "OFFSET ".$offset;
|
|
||||||
}
|
|
||||||
//return
|
|
||||||
return implode(PHP_EOL, $out).';';
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function sql_ddl($args=array())
|
|
||||||
{
|
{
|
||||||
$out = [];
|
$out = [];
|
||||||
$out[] = "CREATE TABLE `{$args['table']}` (";
|
$out[] = "CREATE TABLE IF NOT EXISTS `{$args['table']}` (";
|
||||||
$lines = [];
|
$lines = [];
|
||||||
$lines[] = "`json_data` JSON DEFAULT NULL";
|
$lines[] = "`json_data` JSON DEFAULT NULL";
|
||||||
foreach ($args['virtualColumns'] as $path => $col) {
|
foreach ($args['schema'] as $path => $col) {
|
||||||
$line = "`{$col['name']}` {$col['type']} GENERATED ALWAYS AS (".$this->expandPath($path).")";
|
$line = "`{$col['name']}` {$col['type']} GENERATED ALWAYS AS (" . $this->expandPath($path) . ")";
|
||||||
if (@$col['primary']) {
|
if (@$col['primary']) {
|
||||||
//this needs to be "PERSISTENT" for MariaDB -- I guess there are going to be two drivers now
|
|
||||||
$line .= ' STORED';
|
$line .= ' STORED';
|
||||||
} else {
|
} else {
|
||||||
$line .= ' VIRTUAL';
|
$line .= ' VIRTUAL';
|
||||||
}
|
}
|
||||||
$lines[] = $line;
|
$lines[] = $line;
|
||||||
}
|
}
|
||||||
foreach ($args['virtualColumns'] as $path => $col) {
|
$out[] = implode(',' . PHP_EOL, $lines);
|
||||||
if (@$col['primary']) {
|
|
||||||
$lines[] = "PRIMARY KEY (`{$col['name']}`)";
|
|
||||||
} elseif (@$col['unique'] && $as = @$col['index']) {
|
|
||||||
$lines[] = "UNIQUE KEY `{$args['table']}_{$col['name']}_idx` (`{$col['name']}`) USING $as";
|
|
||||||
} elseif ($as = @$col['index']) {
|
|
||||||
$lines[] = "KEY `{$args['table']}_{$col['name']}_idx` (`{$col['name']}`) USING $as";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$out[] = implode(','.PHP_EOL, $lines);
|
|
||||||
$out[] = ") ENGINE=InnoDB DEFAULT CHARSET=utf8;";
|
$out[] = ") ENGINE=InnoDB DEFAULT CHARSET=utf8;";
|
||||||
return implode(PHP_EOL, $out);
|
$out = implode(PHP_EOL, $out);
|
||||||
|
return $out;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function expandPath(string $path) : string
|
protected function buildIndexes(string $table, array $schema): bool
|
||||||
|
{
|
||||||
|
foreach ($schema as $path => $col) {
|
||||||
|
try {
|
||||||
|
if (@$col['primary']) {
|
||||||
|
$this->pdo->exec(
|
||||||
|
"CREATE UNIQUE INDEX `{$table}_{$col['name']}_idx` ON {$table} (`{$col['name']}`) USING BTREE"
|
||||||
|
);
|
||||||
|
} elseif (@$col['unique'] && $as = @$col['index']) {
|
||||||
|
$this->pdo->exec(
|
||||||
|
"CREATE UNIQUE INDEX `{$table}_{$col['name']}_idx` ON {$table} (`{$col['name']}`) USING $as"
|
||||||
|
);
|
||||||
|
} elseif ($as = @$col['index']) {
|
||||||
|
$this->pdo->exec(
|
||||||
|
"CREATE INDEX `{$table}_{$col['name']}_idx` ON {$table} (`{$col['name']}`) USING $as"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (\Throwable $th) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function expandPath(string $path): string
|
||||||
{
|
{
|
||||||
return "JSON_UNQUOTE(JSON_EXTRACT(`json_data`,'$.{$path}'))";
|
return "JSON_UNQUOTE(JSON_EXTRACT(`json_data`,'$.{$path}'))";
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function sql_setJSON($args)
|
protected function sql_set_json(array $args): string
|
||||||
{
|
{
|
||||||
return 'UPDATE `'.$args['table'].'` SET `json_data` = :data WHERE `dso_id` = :dso_id;';
|
return 'UPDATE `' . $args['table'] . '` SET `json_data` = :data WHERE `dso_id` = :dso_id;';
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function sql_insert($args)
|
protected function sql_insert(array $args): string
|
||||||
{
|
{
|
||||||
return "INSERT INTO `{$args['table']}` (`json_data`) VALUES (:data);";
|
return "INSERT INTO `{$args['table']}` (`json_data`) VALUES (:data);";
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function sql_delete($args)
|
protected function addColumns($table, $schema): bool
|
||||||
{
|
{
|
||||||
return 'DELETE FROM `'.$args['table'].'` WHERE `dso_id` = :dso_id;';
|
$out = true;
|
||||||
|
foreach ($schema as $path => $col) {
|
||||||
|
$line = "ALTER TABLE `{$table}` ADD COLUMN `${col['name']}` {$col['type']} GENERATED ALWAYS AS (" . $this->expandPath($path) . ")";
|
||||||
|
if (@$col['primary']) {
|
||||||
|
$line .= ' STORED;';
|
||||||
|
} else {
|
||||||
|
$line .= ' VIRTUAL;';
|
||||||
|
}
|
||||||
|
$out = $out &&
|
||||||
|
$this->pdo->exec($line) !== false;
|
||||||
|
}
|
||||||
|
return $out;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function removeColumns($table, $schema): bool
|
||||||
|
{
|
||||||
|
$out = true;
|
||||||
|
foreach ($schema as $path => $col) {
|
||||||
|
$out = $out &&
|
||||||
|
$this->pdo->exec("ALTER TABLE `{$table}` DROP COLUMN `${col['name']}`;") !== false;
|
||||||
|
}
|
||||||
|
return $out;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function rebuildSchema($table, $schema): bool
|
||||||
|
{
|
||||||
|
//this does nothing in databases that can generate columns themselves
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function sql_create_schema_table(): string
|
||||||
|
{
|
||||||
|
return <<<EOT
|
||||||
|
CREATE TABLE `destructr_schema` (
|
||||||
|
`schema_time` bigint NOT NULL,
|
||||||
|
`schema_table` varchar(100) NOT NULL,
|
||||||
|
`schema_schema` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL CHECK (json_valid(`schema_schema`)),
|
||||||
|
PRIMARY KEY (`schema_time`,`schema_table`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||||
|
EOT;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function sql_table_exists(string $table): string
|
||||||
|
{
|
||||||
|
$table = preg_replace('/[^a-zA-Z0-9\-_]/', '', $table);
|
||||||
|
return 'SELECT 1 FROM ' . $table . ' LIMIT 1';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
272
src/Drivers/SQLiteDriver.php
Normal file
272
src/Drivers/SQLiteDriver.php
Normal file
|
@ -0,0 +1,272 @@
|
||||||
|
<?php
|
||||||
|
/* Destructr | https://github.com/jobyone/destructr | MIT License */
|
||||||
|
|
||||||
|
namespace Destructr\Drivers;
|
||||||
|
|
||||||
|
use Destructr\DSOInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* What this driver supports: Any version of SQLite3 in PHP environments that allow
|
||||||
|
* pdo::sqliteCreateFunction
|
||||||
|
*
|
||||||
|
* Note that unlike databases with native JSON functions, this driver's generated
|
||||||
|
* columns are NOT generated in the database. They are updated by this class whenever
|
||||||
|
* the data they reference changes. This doesn't matter much if you're doing all
|
||||||
|
* your updating through Destructr, but is something to be cognizant of if your
|
||||||
|
* data is being updated outside Destructr.
|
||||||
|
*/
|
||||||
|
class SQLiteDriver extends AbstractSQLDriver
|
||||||
|
{
|
||||||
|
public function update(string $table, DSOInterface $dso): bool
|
||||||
|
{
|
||||||
|
if (!$dso->changes() && !$dso->removals()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
$columns = $this->dso_columns($dso);
|
||||||
|
$s = $this->getStatement(
|
||||||
|
'set_json',
|
||||||
|
[
|
||||||
|
'table' => $table,
|
||||||
|
'columns' => $columns,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
return $s->execute($columns);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function insert(string $table, DSOInterface $dso): bool
|
||||||
|
{
|
||||||
|
$columns = $this->dso_columns($dso);
|
||||||
|
$s = $this->getStatement(
|
||||||
|
'insert',
|
||||||
|
[
|
||||||
|
'table' => $table,
|
||||||
|
'columns' => $columns,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
return $s->execute($columns);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function updateTable($table, $schema): bool
|
||||||
|
{
|
||||||
|
$current = $this->getSchema($table);
|
||||||
|
if (!$current || $schema == $current) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
//create new table
|
||||||
|
$table_tmp = "{$table}_tmp_" . md5(rand());
|
||||||
|
$sql = $this->sql_ddl([
|
||||||
|
'table' => $table_tmp,
|
||||||
|
'schema' => $schema,
|
||||||
|
]);
|
||||||
|
if ($this->pdo->exec($sql) === false) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
//copy data into it
|
||||||
|
$sql = ["INSERT INTO $table_tmp"];
|
||||||
|
$cols = ["json_data"];
|
||||||
|
$srcs = ["json_data"];
|
||||||
|
foreach ($schema as $path => $col) {
|
||||||
|
$cols[] = $col['name'];
|
||||||
|
$srcs[] = $this->expandPath($path);
|
||||||
|
}
|
||||||
|
$sql[] = '(' . implode(',', $cols) . ')';
|
||||||
|
$sql[] = 'SELECT';
|
||||||
|
$sql[] = implode(',', $srcs);
|
||||||
|
$sql[] = "FROM $table";
|
||||||
|
$sql = implode(PHP_EOL, $sql);
|
||||||
|
if ($this->pdo->exec($sql) === false) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
//remove old table, rename new table to old table
|
||||||
|
if ($this->pdo->exec("DROP TABLE $table") === false) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if ($this->pdo->exec("ALTER TABLE $table_tmp RENAME TO $table") === false) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
//set up indexes
|
||||||
|
if (!$this->buildIndexes($table, $schema)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
//save schema
|
||||||
|
$this->saveSchema($table, $schema);
|
||||||
|
//return result
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function addColumns($table, $schema): bool
|
||||||
|
{
|
||||||
|
//does nothing
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function removeColumns($table, $schema): bool
|
||||||
|
{
|
||||||
|
//does nothing
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function rebuildSchema($table, $schema): bool
|
||||||
|
{
|
||||||
|
//does nothing
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function sql_insert(array $args): string
|
||||||
|
{
|
||||||
|
$out = [];
|
||||||
|
$names = array_map(
|
||||||
|
function ($e) {
|
||||||
|
return preg_replace('/^:/', '', $e);
|
||||||
|
},
|
||||||
|
array_keys($args['columns'])
|
||||||
|
);
|
||||||
|
$out[] = 'INSERT INTO `' . $args['table'] . '`';
|
||||||
|
$out[] = '(`' . implode('`,`', $names) . '`)';
|
||||||
|
$out[] = 'VALUES (:' . implode(',:', $names) . ')';
|
||||||
|
$out = implode(PHP_EOL, $out) . ';';
|
||||||
|
return $out;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function sql_set_json(array $args): string
|
||||||
|
{
|
||||||
|
$names = array_map(
|
||||||
|
function ($e) {
|
||||||
|
return '`' . preg_replace('/^:/', '', $e) . '` = ' . $e;
|
||||||
|
},
|
||||||
|
array_keys($args['columns'])
|
||||||
|
);
|
||||||
|
$out = [];
|
||||||
|
$out[] = 'UPDATE `' . $args['table'] . '`';
|
||||||
|
$out[] = 'SET';
|
||||||
|
$out[] = implode(',' . PHP_EOL, $names);
|
||||||
|
$out[] = 'WHERE `dso_id` = :dso_id';
|
||||||
|
$out = implode(PHP_EOL, $out) . ';';
|
||||||
|
return $out;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function delete(string $table, DSOInterface $dso): bool
|
||||||
|
{
|
||||||
|
$s = $this->getStatement(
|
||||||
|
'delete',
|
||||||
|
['table' => $table]
|
||||||
|
);
|
||||||
|
return $s->execute([
|
||||||
|
':dso_id' => $dso['dso.id'],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to extract a list of column/parameter names for a given DSO, based
|
||||||
|
* on the current values.
|
||||||
|
*
|
||||||
|
* @param DSOInterface $dso
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
protected function dso_columns(DSOInterface $dso)
|
||||||
|
{
|
||||||
|
$columns = [':json_data' => json_encode($dso->get())];
|
||||||
|
foreach ($this->getSchema($dso->factory()->table()) ?? [] as $vk => $vv) {
|
||||||
|
$columns[':' . $vv['name']] = $dso->get($vk);
|
||||||
|
}
|
||||||
|
return $columns;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Intercept calls to set PDO, and add a custom function to SQLite so that it
|
||||||
|
* can extract JSON values. It's not actually terribly slow, and allows us to
|
||||||
|
* use JSON seamlessly, almost as if it were native.
|
||||||
|
*
|
||||||
|
* @param \PDO $pdo
|
||||||
|
* @return \PDO|null
|
||||||
|
*/
|
||||||
|
public function pdo(\PDO $pdo = null): ?\PDO
|
||||||
|
{
|
||||||
|
if ($pdo) {
|
||||||
|
$this->pdo = $pdo;
|
||||||
|
$this->pdo->sqliteCreateFunction(
|
||||||
|
'DESTRUCTR_JSON_EXTRACT',
|
||||||
|
'\\Destructr\\Drivers\\SQLiteDriver::JSON_EXTRACT',
|
||||||
|
2
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return $this->pdo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function JSON_EXTRACT($json, $path)
|
||||||
|
{
|
||||||
|
$path = substr($path, 2);
|
||||||
|
$path = explode('.', $path);
|
||||||
|
$arr = json_decode($json, true);
|
||||||
|
$out = &$arr;
|
||||||
|
while ($key = array_shift($path)) {
|
||||||
|
if (isset($out[$key])) {
|
||||||
|
$out = &$out[$key];
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return @"$out";
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function buildIndexes(string $table, array $schema): bool
|
||||||
|
{
|
||||||
|
$result = true;
|
||||||
|
foreach ($schema as $key => $vcol) {
|
||||||
|
try {
|
||||||
|
if (@$vcol['primary']) {
|
||||||
|
//sqlite automatically creates this index
|
||||||
|
} elseif (@$vcol['unique']) {
|
||||||
|
$result = $result &&
|
||||||
|
$this->pdo->exec('CREATE UNIQUE INDEX ' . $table . '_' . $vcol['name'] . '_idx on `' . $table . '`(`' . $vcol['name'] . '`)') !== false;
|
||||||
|
} elseif (@$vcol['index']) {
|
||||||
|
$idxResult = $result &&
|
||||||
|
$this->pdo->exec('CREATE INDEX ' . $table . '_' . $vcol['name'] . '_idx on `' . $table . '`(`' . $vcol['name'] . '`)') !== false;
|
||||||
|
}
|
||||||
|
} catch (\Throwable $th) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function sql_ddl(array $args = []): string
|
||||||
|
{
|
||||||
|
$out = [];
|
||||||
|
$out[] = "CREATE TABLE IF NOT EXISTS `{$args['table']}` (";
|
||||||
|
$lines = [];
|
||||||
|
$lines[] = "`json_data` TEXT DEFAULT NULL";
|
||||||
|
foreach ($args['schema'] as $path => $col) {
|
||||||
|
$line = "`{$col['name']}` {$col['type']}";
|
||||||
|
if (@$col['primary']) {
|
||||||
|
$line .= ' PRIMARY KEY';
|
||||||
|
}
|
||||||
|
$lines[] = $line;
|
||||||
|
}
|
||||||
|
$out[] = implode(',' . PHP_EOL, $lines);
|
||||||
|
$out[] = ");";
|
||||||
|
$out = implode(PHP_EOL, $out);
|
||||||
|
return $out;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function expandPath(string $path): string
|
||||||
|
{
|
||||||
|
return "DESTRUCTR_JSON_EXTRACT(`json_data`,'$.{$path}')";
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function sql_create_schema_table(): string
|
||||||
|
{
|
||||||
|
return <<<EOT
|
||||||
|
CREATE TABLE IF NOT EXISTS `destructr_schema`(
|
||||||
|
schema_time BIGINT NOT NULL,
|
||||||
|
schema_table VARCHAR(100) NOT NULL,
|
||||||
|
schema_schema TEXT NOT NULL
|
||||||
|
);
|
||||||
|
EOT;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function sql_table_exists(string $table): string
|
||||||
|
{
|
||||||
|
$table = preg_replace('/[^a-zA-Z0-9\-_]/', '', $table);
|
||||||
|
return 'SELECT 1 FROM ' . $table . ' LIMIT 1';
|
||||||
|
}
|
||||||
|
}
|
225
src/Factory.php
225
src/Factory.php
|
@ -1,8 +1,9 @@
|
||||||
<?php
|
<?php
|
||||||
/* Destructr | https://gitlab.com/byjoby/destructr | MIT License */
|
/* Destructr | https://github.com/jobyone/destructr | MIT License */
|
||||||
|
|
||||||
namespace Destructr;
|
namespace Destructr;
|
||||||
|
|
||||||
use mofodojodino\ProfanityFilter\Check;
|
use Destructr\Drivers\AbstractDriver;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Factory is responsible for keeping track of which columns may or may not
|
* The Factory is responsible for keeping track of which columns may or may not
|
||||||
|
@ -19,67 +20,100 @@ use mofodojodino\ProfanityFilter\Check;
|
||||||
* * Executing Searches (which largely consists of passing them to the Driver)
|
* * Executing Searches (which largely consists of passing them to the Driver)
|
||||||
* * Inspecting unstructured data straight from the database and figuring out what class to make it (defaults to just DSO)
|
* * Inspecting unstructured data straight from the database and figuring out what class to make it (defaults to just DSO)
|
||||||
*/
|
*/
|
||||||
class Factory implements DSOFactoryInterface
|
class Factory
|
||||||
{
|
{
|
||||||
const ID_CHARS = 'abcdefghijkmnorstuvwxyz0123456789';
|
const ID_CHARS = 'abcdefghijklmnopqrstuvwxyz0123456789';
|
||||||
const ID_LENGTH = 16;
|
const ID_LENGTH = 8;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var Drivers\AbstractDriver
|
||||||
|
*/
|
||||||
protected $driver;
|
protected $driver;
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
protected $table;
|
protected $table;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Virtual columns are only supported by modern SQL servers. Most of the
|
* Virtual columns that should be created for sorting/indexing in the SQL server
|
||||||
* legacy drivers will only use the ones defined in CORE_VIRTUAL_COLUMNS,
|
|
||||||
* but that should be handled automatically.
|
|
||||||
*/
|
*/
|
||||||
protected $virtualColumns = [
|
protected $schema = [
|
||||||
'dso.id' => [
|
'dso.id' => [
|
||||||
'name'=>'dso_id',
|
'name' => 'dso_id', //column name to be used
|
||||||
'type'=>'VARCHAR(16)',
|
'type' => 'VARCHAR(16)', //column type
|
||||||
'index' => 'BTREE',
|
'index' => 'BTREE', //whether/how to index
|
||||||
'unique' => true,
|
'unique' => true, //whether column should be unique
|
||||||
'primary' => true
|
'primary' => true, //whether column should be the primary key
|
||||||
],
|
],
|
||||||
'dso.type' => [
|
'dso.type' => [
|
||||||
'name'=>'dso_type',
|
'name' => 'dso_type',
|
||||||
'type'=>'VARCHAR(30)',
|
'type' => 'VARCHAR(30)',
|
||||||
'index'=>'BTREE'
|
|
||||||
],
|
|
||||||
'dso.deleted' => [
|
|
||||||
'name'=>'dso_deleted',
|
|
||||||
'type'=>'BIGINT',
|
|
||||||
'index'=>'BTREE'
|
|
||||||
]
|
|
||||||
];
|
|
||||||
/**
|
|
||||||
* This cannot be modified by extending classes, it's used by legacy drivers
|
|
||||||
*/
|
|
||||||
const CORE_VIRTUAL_COLUMNS = [
|
|
||||||
'dso.id' => [
|
|
||||||
'name'=>'dso_id',
|
|
||||||
'type'=>'VARCHAR(16)',
|
|
||||||
'index' => 'BTREE',
|
'index' => 'BTREE',
|
||||||
'unique' => true,
|
|
||||||
'primary' => true
|
|
||||||
],
|
|
||||||
'dso.type' => [
|
|
||||||
'name'=>'dso_type',
|
|
||||||
'type'=>'VARCHAR(30)',
|
|
||||||
'index'=>'BTREE'
|
|
||||||
],
|
],
|
||||||
'dso.deleted' => [
|
'dso.deleted' => [
|
||||||
'name'=>'dso_deleted',
|
'name' => 'dso_deleted',
|
||||||
'type'=>'BIGINT',
|
'type' => 'BIGINT',
|
||||||
'index'=>'BTREE'
|
'index' => 'BTREE',
|
||||||
]
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
public function __construct(Drivers\DSODriverInterface &$driver, string $table)
|
public function __construct(Drivers\AbstractDriver $driver, string $table)
|
||||||
{
|
{
|
||||||
$this->driver = $driver;
|
$this->driver = $driver;
|
||||||
$this->table = $table;
|
$this->table = $table;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function hook_create(DSOInterface &$dso)
|
public function checkEnvironment(): bool
|
||||||
|
{
|
||||||
|
return $this->driver->checkEnvironment(
|
||||||
|
$this->table,
|
||||||
|
$this->schema
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function prepareEnvironment(): bool
|
||||||
|
{
|
||||||
|
return $this->driver->prepareEnvironment(
|
||||||
|
$this->table,
|
||||||
|
$this->schema
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updateEnvironment(): bool
|
||||||
|
{
|
||||||
|
return $this->driver->updateEnvironment(
|
||||||
|
$this->table,
|
||||||
|
$this->schema
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function table(): string
|
||||||
|
{
|
||||||
|
return $this->table;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function driver(): AbstractDriver
|
||||||
|
{
|
||||||
|
return $this->driver;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function tableExists(): bool
|
||||||
|
{
|
||||||
|
return $this->driver->tableExists($this->table);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createSchemaTable(): bool
|
||||||
|
{
|
||||||
|
$this->driver->createSchemaTable(AbstractDriver::SCHEMA_TABLE);
|
||||||
|
return $this->driver->tableExists(AbstractDriver::SCHEMA_TABLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function quote(string $str): string
|
||||||
|
{
|
||||||
|
return $this->driver->pdo()->quote($str);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function hook_create(DSOInterface $dso)
|
||||||
{
|
{
|
||||||
if (!$dso->get('dso.id')) {
|
if (!$dso->get('dso.id')) {
|
||||||
$dso->set('dso.id', static::generate_id(static::ID_CHARS, static::ID_LENGTH), true);
|
$dso->set('dso.id', static::generate_id(static::ID_CHARS, static::ID_LENGTH), true);
|
||||||
|
@ -88,37 +122,46 @@ class Factory implements DSOFactoryInterface
|
||||||
$dso->set('dso.created.date', time());
|
$dso->set('dso.created.date', time());
|
||||||
}
|
}
|
||||||
if (!$dso->get('dso.created.user')) {
|
if (!$dso->get('dso.created.user')) {
|
||||||
$dso->set('dso.created.user', ['ip'=>@$_SERVER['REMOTE_ADDR']]);
|
$dso->set('dso.created.user', ['ip' => @$_SERVER['REMOTE_ADDR']]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function hook_update(DSOInterface &$dso)
|
protected function hook_update(DSOInterface $dso)
|
||||||
{
|
{
|
||||||
$dso->set('dso.modified.date', time());
|
$dso->set('dso.modified.date', time());
|
||||||
$dso->set('dso.modified.user', ['ip'=>@$_SERVER['REMOTE_ADDR']]);
|
$dso->set('dso.modified.user', ['ip' => @$_SERVER['REMOTE_ADDR']]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function class(array $data) : ?string
|
/**
|
||||||
|
* Override this function to allow a factory to create different
|
||||||
|
* sub-classes of DSO based on attributes of the given object's
|
||||||
|
* data. For example, you could use a property like dso.class to
|
||||||
|
* select a class from an associative array.
|
||||||
|
*
|
||||||
|
* @param array $data
|
||||||
|
* @return string|null
|
||||||
|
*/
|
||||||
|
function class(?array $data): ?string
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function delete(DSOInterface &$dso, bool $permanent = false) : bool
|
public function delete(DSOInterface $dso, bool $permanent = false): bool
|
||||||
{
|
{
|
||||||
if ($permanent) {
|
if ($permanent) {
|
||||||
return $this->driver->delete($this->table, $dso);
|
return $this->driver->delete($this->table, $dso);
|
||||||
}
|
}
|
||||||
$dso['dso.deleted'] = time();
|
$dso['dso.deleted'] = time();
|
||||||
return $this->update($dso);
|
return $this->update($dso, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function undelete(DSOInterface &$dso) : bool
|
public function undelete(DSOInterface $dso): bool
|
||||||
{
|
{
|
||||||
unset($dso['dso.deleted']);
|
unset($dso['dso.deleted']);
|
||||||
return $this->update($dso);
|
return $this->update($dso, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function create(array $data = array()) : DSOInterface
|
public function create(?array $data = array()): DSOInterface
|
||||||
{
|
{
|
||||||
if (!($class = $this->class($data))) {
|
if (!($class = $this->class($data))) {
|
||||||
$class = DSO::class;
|
$class = DSO::class;
|
||||||
|
@ -130,37 +173,31 @@ class Factory implements DSOFactoryInterface
|
||||||
return $dso;
|
return $dso;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function createTable() : bool
|
public function schema(): array
|
||||||
{
|
{
|
||||||
return $this->driver->createTable(
|
return $this->driver->getSchema($this->table) ?? $this->schema;
|
||||||
$this->table,
|
|
||||||
($this->driver::EXTENSIBLE_VIRTUAL_COLUMNS?$this->virtualColumns:$this::CORE_VIRTUAL_COLUMNS)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function virtualColumnName($path) : ?string
|
protected function virtualColumnName($path): ?string
|
||||||
{
|
{
|
||||||
if ($this->driver::EXTENSIBLE_VIRTUAL_COLUMNS) {
|
return @$this->schema()[$path]['name'];
|
||||||
$vcols = $this->virtualColumns;
|
|
||||||
} else {
|
|
||||||
$vcols = static::CORE_VIRTUAL_COLUMNS;
|
|
||||||
}
|
|
||||||
return @$vcols[$path]['name'];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function update(DSOInterface &$dso) : bool
|
public function update(DSOInterface $dso, bool $sneaky = false): bool
|
||||||
{
|
{
|
||||||
if (!$dso->changes() && !$dso->removals()) {
|
if (!$dso->changes() && !$dso->removals()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
$this->hook_update($dso);
|
if (!$sneaky) {
|
||||||
$dso->hook_update();
|
$this->hook_update($dso);
|
||||||
|
$dso->hook_update();
|
||||||
|
}
|
||||||
$out = $this->driver->update($this->table, $dso);
|
$out = $this->driver->update($this->table, $dso);
|
||||||
$dso->resetChanges();
|
$dso->resetChanges();
|
||||||
return $out;
|
return $out;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function search() : Search
|
public function search(): Search
|
||||||
{
|
{
|
||||||
return new Search($this);
|
return new Search($this);
|
||||||
}
|
}
|
||||||
|
@ -179,7 +216,19 @@ class Factory implements DSOFactoryInterface
|
||||||
return $arr;
|
return $arr;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function executeSearch(Search $search, array $params = array(), $deleted = false) : array
|
public function executeCount(Search $search, array $params = array(), $deleted = false): ?int
|
||||||
|
{
|
||||||
|
//add deletion clause and expand column names
|
||||||
|
$search = $this->preprocessSearch($search, $deleted);
|
||||||
|
//run select
|
||||||
|
return $this->driver->count(
|
||||||
|
$this->table,
|
||||||
|
$search,
|
||||||
|
$params
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function executeSearch(Search $search, array $params = array(), $deleted = false): array
|
||||||
{
|
{
|
||||||
//add deletion clause and expand column names
|
//add deletion clause and expand column names
|
||||||
$search = $this->preprocessSearch($search, $deleted);
|
$search = $this->preprocessSearch($search, $deleted);
|
||||||
|
@ -192,17 +241,17 @@ class Factory implements DSOFactoryInterface
|
||||||
return $this->makeObjectsFromRows($r);
|
return $this->makeObjectsFromRows($r);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function read(string $value, string $field = 'dso.id', $deleted = false) : ?DSOInterface
|
public function read(string $value, string $field = 'dso.id', $deleted = false): ?DSOInterface
|
||||||
{
|
{
|
||||||
$search = $this->search();
|
$search = $this->search();
|
||||||
$search->where('${'.$field.'} = :value');
|
$search->where('${' . $field . '} = :value');
|
||||||
if ($results = $search->execute([':value'=>$value], $deleted)) {
|
if ($results = $search->execute([':value' => $value], $deleted)) {
|
||||||
return array_shift($results);
|
return array_shift($results);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function insert(DSOInterface &$dso) : bool
|
public function insert(DSOInterface $dso): bool
|
||||||
{
|
{
|
||||||
$this->hook_update($dso);
|
$this->hook_update($dso);
|
||||||
$dso->hook_update();
|
$dso->hook_update();
|
||||||
|
@ -216,6 +265,8 @@ class Factory implements DSOFactoryInterface
|
||||||
$search = new Search($this);
|
$search = new Search($this);
|
||||||
$search->where($input->where());
|
$search->where($input->where());
|
||||||
$search->order($input->order());
|
$search->order($input->order());
|
||||||
|
$search->limit($input->limit());
|
||||||
|
$search->offset($input->offset());
|
||||||
/* add deletion awareness to where clause */
|
/* add deletion awareness to where clause */
|
||||||
if ($deleted !== null) {
|
if ($deleted !== null) {
|
||||||
$where = $search->where();
|
$where = $search->where();
|
||||||
|
@ -225,14 +276,14 @@ class Factory implements DSOFactoryInterface
|
||||||
$added = '${dso.deleted} is null';
|
$added = '${dso.deleted} is null';
|
||||||
}
|
}
|
||||||
if ($where) {
|
if ($where) {
|
||||||
$where = '('.$where.') AND '.$added;
|
$where = '(' . $where . ') AND ' . $added;
|
||||||
} else {
|
} else {
|
||||||
$where = $added;
|
$where = $added;
|
||||||
}
|
}
|
||||||
$search->where($where);
|
$search->where($where);
|
||||||
}
|
}
|
||||||
/* expand virtual column names */
|
/* expand virtual column names */
|
||||||
foreach (['where','order'] as $clause) {
|
foreach (['where', 'order'] as $clause) {
|
||||||
if ($value = $search->$clause()) {
|
if ($value = $search->$clause()) {
|
||||||
$value = preg_replace_callback(
|
$value = preg_replace_callback(
|
||||||
'/\$\{([^\}\\\]+)\}/',
|
'/\$\{([^\}\\\]+)\}/',
|
||||||
|
@ -252,19 +303,17 @@ class Factory implements DSOFactoryInterface
|
||||||
return $search;
|
return $search;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static function generate_id($chars, $length) : string
|
protected static function generate_id($chars, $length, string $prefix = null): string
|
||||||
{
|
{
|
||||||
$check = new Check();
|
$id = '';
|
||||||
do {
|
while (strlen($id) < $length) {
|
||||||
$id = '';
|
$id .= substr(
|
||||||
while (strlen($id) < $length) {
|
$chars,
|
||||||
$id .= substr(
|
rand(0, strlen($chars) - 1),
|
||||||
$chars,
|
1
|
||||||
rand(0, strlen($chars)-1),
|
);
|
||||||
1
|
}
|
||||||
);
|
if ($prefix) $id = $prefix . '_' . $id;
|
||||||
}
|
|
||||||
} while ($check->hasProfanity($id));
|
|
||||||
return $id;
|
return $id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,183 +0,0 @@
|
||||||
<?php
|
|
||||||
/* Destructr | https://gitlab.com/byjoby/destructr | MIT License */
|
|
||||||
namespace Destructr\LegacyDrivers;
|
|
||||||
|
|
||||||
use Destructr\Drivers\AbstractDriver;
|
|
||||||
use Destructr\DSOInterface;
|
|
||||||
use Destructr\Factory;
|
|
||||||
use Destructr\Search;
|
|
||||||
use Flatrr\FlatArray;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This driver is for supporting older SQL servers that don't have their own
|
|
||||||
* JSON functions. It uses a highly suspect alternative JSON serialization and
|
|
||||||
* user-defined function.
|
|
||||||
*
|
|
||||||
* There are also DEFINITELY bugs in legacy drivers. They should only be used
|
|
||||||
* for very simple queries. These are probably also bugs that cannot be fixed.
|
|
||||||
* Legacy drivers shouldn't really be considered "supported" per se.
|
|
||||||
*/
|
|
||||||
class AbstractLegacyDriver extends AbstractDriver
|
|
||||||
{
|
|
||||||
const EXTENSIBLE_VIRTUAL_COLUMNS = false;
|
|
||||||
|
|
||||||
public function select(string $table, Search $search, array $params)
|
|
||||||
{
|
|
||||||
$results = parent::select($table, $search, $params);
|
|
||||||
foreach ($results as $rkey => $row) {
|
|
||||||
$new = new FlatArray();
|
|
||||||
foreach (json_decode($row['json_data'], true) as $key => $value) {
|
|
||||||
$new->set(str_replace('|', '.', $key), $value);
|
|
||||||
}
|
|
||||||
$results[$rkey]['json_data'] = json_encode($new->get());
|
|
||||||
}
|
|
||||||
return $results;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function sql_select($args)
|
|
||||||
{
|
|
||||||
//extract query parts from Search and expand paths
|
|
||||||
$where = $this->expandPaths($args['search']->where());
|
|
||||||
$order = $this->expandPaths($args['search']->order());
|
|
||||||
$limit = $args['search']->limit();
|
|
||||||
$offset = $args['search']->offset();
|
|
||||||
//select from
|
|
||||||
$out = ["SELECT * FROM `{$args['table']}`"];
|
|
||||||
//where statement
|
|
||||||
if ($where !== null) {
|
|
||||||
$out[] = "WHERE ".$where;
|
|
||||||
}
|
|
||||||
//order statement
|
|
||||||
if ($order !== null) {
|
|
||||||
$out[] = "ORDER BY ".$order;
|
|
||||||
}
|
|
||||||
//limit
|
|
||||||
if ($limit !== null) {
|
|
||||||
$out[] = "LIMIT ".$limit;
|
|
||||||
}
|
|
||||||
//offset
|
|
||||||
if ($offset !== null) {
|
|
||||||
$out[] = "OFFSET ".$offset;
|
|
||||||
}
|
|
||||||
//return
|
|
||||||
return implode(PHP_EOL, $out).';';
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function sql_ddl($args=array())
|
|
||||||
{
|
|
||||||
$out = [];
|
|
||||||
$out[] = "CREATE TABLE `{$args['table']}` (";
|
|
||||||
$lines = [];
|
|
||||||
$lines[] = "`json_data` TEXT DEFAULT NULL";
|
|
||||||
foreach ($args['virtualColumns'] as $path => $col) {
|
|
||||||
$lines[] = "`{$col['name']}` {$col['type']}";
|
|
||||||
}
|
|
||||||
foreach ($args['virtualColumns'] as $path => $col) {
|
|
||||||
if (@$col['unique'] && $as = @$col['index']) {
|
|
||||||
$lines[] = "UNIQUE KEY `{$args['table']}_{$col['name']}_idx` (`{$col['name']}`) USING $as";
|
|
||||||
} elseif ($as = @$col['index']) {
|
|
||||||
$lines[] = "KEY `{$args['table']}_{$col['name']}_idx` (`{$col['name']}`) USING $as";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$out[] = implode(','.PHP_EOL, $lines);
|
|
||||||
$out[] = ") ENGINE=InnoDB DEFAULT CHARSET=utf8;";
|
|
||||||
return implode(PHP_EOL, $out);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function update(string $table, DSOInterface $dso) : bool
|
|
||||||
{
|
|
||||||
if (!$dso->changes() && !$dso->removals()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
$s = $this->getStatement(
|
|
||||||
'setJSON',
|
|
||||||
['table'=>$table]
|
|
||||||
);
|
|
||||||
$params = $this->legacyParams($dso);
|
|
||||||
$out = $s->execute($params);
|
|
||||||
return $out;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function sql_setJSON($args)
|
|
||||||
{
|
|
||||||
$out = [];
|
|
||||||
$out[] = 'UPDATE `'.$args['table'].'`';
|
|
||||||
$out[] = 'SET';
|
|
||||||
foreach (Factory::CORE_VIRTUAL_COLUMNS as $v) {
|
|
||||||
$out[] = '`'.$v['name'].'` = :'.$v['name'].',';
|
|
||||||
}
|
|
||||||
$out[] = '`json_data` = :data';
|
|
||||||
$out[] = 'WHERE `dso_id` = :dso_id';
|
|
||||||
return implode(PHP_EOL, $out).';';
|
|
||||||
}
|
|
||||||
|
|
||||||
public function insert(string $table, DSOInterface $dso) : bool
|
|
||||||
{
|
|
||||||
$s = $this->getStatement(
|
|
||||||
'insert',
|
|
||||||
['table'=>$table]
|
|
||||||
);
|
|
||||||
$params = $this->legacyParams($dso);
|
|
||||||
return $s->execute($params);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function legacyParams(DSOInterface $dso)
|
|
||||||
{
|
|
||||||
$params = [':data' => $this->json_encode($dso->get())];
|
|
||||||
foreach (Factory::CORE_VIRTUAL_COLUMNS as $vk => $vv) {
|
|
||||||
$params[':'.$vv['name']] = $dso->get($vk);
|
|
||||||
}
|
|
||||||
return $params;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function sql_insert($args)
|
|
||||||
{
|
|
||||||
$out = [];
|
|
||||||
$out[] = 'INSERT INTO `'.$args['table'].'`';
|
|
||||||
$out[] = '(`json_data`,`dso_id`,`dso_type`,`dso_deleted`)';
|
|
||||||
$out[] = 'VALUES (:data, :dso_id, :dso_type, :dso_deleted)';
|
|
||||||
return implode(PHP_EOL, $out).';';
|
|
||||||
}
|
|
||||||
|
|
||||||
public function delete(string $table, DSOInterface $dso) : bool
|
|
||||||
{
|
|
||||||
$s = $this->getStatement(
|
|
||||||
'delete',
|
|
||||||
['table'=>$table]
|
|
||||||
);
|
|
||||||
$out = $s->execute([
|
|
||||||
':dso_id' => $dso['dso.id']
|
|
||||||
]);
|
|
||||||
// if (!$out) {
|
|
||||||
// var_dump($s->errorInfo());
|
|
||||||
// }
|
|
||||||
return $out;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function sql_delete($args)
|
|
||||||
{
|
|
||||||
return 'DELETE FROM `'.$args['table'].'` WHERE `dso_id` = :dso_id;';
|
|
||||||
}
|
|
||||||
|
|
||||||
public function json_encode($a, array &$b = null, string $prefix = '')
|
|
||||||
{
|
|
||||||
if ($b === null) {
|
|
||||||
$b = [];
|
|
||||||
$this->json_encode($a, $b, '');
|
|
||||||
return json_encode($b);
|
|
||||||
} else {
|
|
||||||
if (is_array($a)) {
|
|
||||||
foreach ($a as $ak => $av) {
|
|
||||||
if ($prefix == '') {
|
|
||||||
$nprefix = $ak;
|
|
||||||
} else {
|
|
||||||
$nprefix = $prefix.'|'.$ak;
|
|
||||||
}
|
|
||||||
$this->json_encode($av, $b, $nprefix);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$b[$prefix] = $a;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,45 +0,0 @@
|
||||||
# Destructr legacy drivers
|
|
||||||
|
|
||||||
This folder holds what are called "legacy drivers."
|
|
||||||
These drivers attempt to extend Destructr support to as many databases as possible,
|
|
||||||
even in some cases at the expense of functionality.
|
|
||||||
|
|
||||||
All these drivers are designed to support the following features:
|
|
||||||
* Inserting objects (and disallowing duplicates at the *SQL* level)
|
|
||||||
* Updating existing objects
|
|
||||||
* Deleting objects
|
|
||||||
* Searching by exact text matches
|
|
||||||
|
|
||||||
The things many of this sort of driver will *not* ever support:
|
|
||||||
* Performance optimization via virtual columns. Many of them will have their
|
|
||||||
performance lightly optimized for common system queries, just by hard-coding
|
|
||||||
virtual columns for `dso.id`, `dso.deleted`, and `dso.type`
|
|
||||||
* Complex queries, like joins, REGEX, or LIKE queries.
|
|
||||||
|
|
||||||
## Current state of legacy drivers
|
|
||||||
|
|
||||||
### SQLite 3
|
|
||||||
|
|
||||||
**\Destructr\LegacyDrivers\SQLiteDriver**
|
|
||||||
|
|
||||||
**Overall support level: Highly functional, a touch slow**
|
|
||||||
|
|
||||||
Via LegacyDrivers\SQLiteDriver Destructr now has surprisingly good support for
|
|
||||||
SQLite 3. It accomplishes this by using SQLite's user-defined functions feature
|
|
||||||
to offload JSON parsing to PHP. This means all the JSON parsing is actually up
|
|
||||||
to spec, storage doesn't need to be flattened like the other legacy drivers,
|
|
||||||
and tables are dramatically more easily forward-compatible.
|
|
||||||
|
|
||||||
If you're considering using legacy MySQL, you should really seriously consider
|
|
||||||
just using SQLite instead. In benchmarks with 1000 records SQLite's performance
|
|
||||||
is actually BETTER than MySQL 5.6 for everything but insert/update operations.
|
|
||||||
In some cases it appears to even be *significantly* faster while also having the
|
|
||||||
distinct advantage of not using any goofy home-rolled JSON extraction funcitons.
|
|
||||||
|
|
||||||
So unless you have a good reason to use MySQL 5.6, you're probably best off
|
|
||||||
using SQLite if you don't have access to a fully supported database version.
|
|
||||||
|
|
||||||
### MySQL 5.6
|
|
||||||
|
|
||||||
** No longer supported. You should really use SQLite if you don't have access to
|
|
||||||
something better. Will most likely never be supported. **
|
|
|
@ -1,97 +0,0 @@
|
||||||
<?php
|
|
||||||
/* Destructr | https://gitlab.com/byjoby/destructr | MIT License */
|
|
||||||
namespace Destructr\LegacyDrivers;
|
|
||||||
|
|
||||||
use Destructr\DSOInterface;
|
|
||||||
use Destructr\Factory;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* What this driver supports: Version of SQLite3 in PHP environments that allow
|
|
||||||
* pdo::sqliteCreateFunction
|
|
||||||
*
|
|
||||||
* Overally this driver is quite safe and reliable. Definitely the most safe of
|
|
||||||
* all the legacy drivers. The performance isn't even that bad. It benchmarks
|
|
||||||
* close to the same speed as MySQL 5.7, even. The benchmarks are only operating
|
|
||||||
* on databases of 500 objects though, so ymmv.
|
|
||||||
*/
|
|
||||||
class SQLiteDriver extends AbstractLegacyDriver
|
|
||||||
{
|
|
||||||
public function __construct(string $dsn, string $username=null, string $password=null, array $options=null)
|
|
||||||
{
|
|
||||||
parent::__construct($dsn, $username, $password, $options);
|
|
||||||
/*
|
|
||||||
What we're doing here is adding a custom function to SQLite so that it
|
|
||||||
can extract JSON values. It's not fast, but it does let us use JSON
|
|
||||||
fairly seamlessly.
|
|
||||||
*/
|
|
||||||
$this->pdo->sqliteCreateFunction(
|
|
||||||
'DESTRUCTR_JSON_EXTRACT',
|
|
||||||
'\\Destructr\\LegacyDrivers\\SQLiteDriver::JSON_EXTRACT',
|
|
||||||
2
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function JSON_EXTRACT($json, $path)
|
|
||||||
{
|
|
||||||
$path = substr($path, 2);
|
|
||||||
$path = explode('.', $path);
|
|
||||||
$arr = json_decode($json, true);
|
|
||||||
$out = &$arr;
|
|
||||||
while ($key = array_shift($path)) {
|
|
||||||
if (isset($out[$key])) {
|
|
||||||
$out = &$out[$key];
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return @"$out";
|
|
||||||
}
|
|
||||||
|
|
||||||
public function createTable(string $table, array $virtualColumns) : bool
|
|
||||||
{
|
|
||||||
$sql = $this->sql_ddl([
|
|
||||||
'table'=>$table,
|
|
||||||
]);
|
|
||||||
$out = $this->pdo->exec($sql) !== false;
|
|
||||||
foreach (Factory::CORE_VIRTUAL_COLUMNS as $key => $vcol) {
|
|
||||||
$idxResult = true;
|
|
||||||
if (@$vcol['unique']) {
|
|
||||||
$idxResult = $this->pdo->exec('CREATE UNIQUE INDEX '.$table.'_'.$vcol['name'].'_idx on `'.$table.'`(`'.$vcol['name'].'`)') !== false;
|
|
||||||
} elseif (@$vcol['index']) {
|
|
||||||
$idxResult = $this->pdo->exec('CREATE INDEX '.$table.'_'.$vcol['name'].'_idx on `'.$table.'`(`'.$vcol['name'].'`)') !== false;
|
|
||||||
}
|
|
||||||
if (!$idxResult) {
|
|
||||||
$out = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $out;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function sql_ddl($args=array())
|
|
||||||
{
|
|
||||||
$out = [];
|
|
||||||
$out[] = "CREATE TABLE `{$args['table']}` (";
|
|
||||||
$lines = [];
|
|
||||||
$lines[] = "`json_data` TEXT DEFAULT NULL";
|
|
||||||
foreach (Factory::CORE_VIRTUAL_COLUMNS as $path => $col) {
|
|
||||||
$line = "`{$col['name']}` {$col['type']}";
|
|
||||||
if (@$col['primary']) {
|
|
||||||
$line .= ' PRIMARY KEY';
|
|
||||||
}
|
|
||||||
$lines[] = $line;
|
|
||||||
}
|
|
||||||
$out[] = implode(','.PHP_EOL, $lines);
|
|
||||||
$out[] = ");";
|
|
||||||
return implode(PHP_EOL, $out);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function expandPath(string $path) : string
|
|
||||||
{
|
|
||||||
return "DESTRUCTR_JSON_EXTRACT(`json_data`,'$.{$path}')";
|
|
||||||
}
|
|
||||||
|
|
||||||
public function json_encode($a, ?array &$b = null, string $prefix = '')
|
|
||||||
{
|
|
||||||
return json_encode($a);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,10 +1,7 @@
|
||||||
<?php
|
<?php
|
||||||
/* Destructr | https://gitlab.com/byjoby/destructr | MIT License */
|
/* Destructr | https://github.com/jobyone/destructr | MIT License */
|
||||||
namespace Destructr;
|
namespace Destructr;
|
||||||
|
|
||||||
use Destructr\DSOFactoryInterface;
|
|
||||||
use Destructr\Drivers\DSODriverInterface;
|
|
||||||
|
|
||||||
class Search implements \Serializable
|
class Search implements \Serializable
|
||||||
{
|
{
|
||||||
protected $factory;
|
protected $factory;
|
||||||
|
@ -13,17 +10,27 @@ class Search implements \Serializable
|
||||||
protected $limit;
|
protected $limit;
|
||||||
protected $offset;
|
protected $offset;
|
||||||
|
|
||||||
public function __construct(DSOFactoryInterface &$factory=null)
|
public function __construct(Factory $factory = null)
|
||||||
{
|
{
|
||||||
$this->factory = $factory;
|
$this->factory = $factory;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function quote(string $str): string
|
||||||
|
{
|
||||||
|
return $this->factory->quote($str);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function count(array $params = array(), $deleted = false)
|
||||||
|
{
|
||||||
|
return $this->factory->executeCount($this, $params, $deleted);
|
||||||
|
}
|
||||||
|
|
||||||
public function execute(array $params = array(), $deleted = false)
|
public function execute(array $params = array(), $deleted = false)
|
||||||
{
|
{
|
||||||
return $this->factory->executeSearch($this, $params, $deleted);
|
return $this->factory->executeSearch($this, $params, $deleted);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function where(string $set = null) : ?string
|
public function where(string $set = null): ?string
|
||||||
{
|
{
|
||||||
if ($set !== null) {
|
if ($set !== null) {
|
||||||
$this->where = $set;
|
$this->where = $set;
|
||||||
|
@ -31,7 +38,18 @@ class Search implements \Serializable
|
||||||
return $this->where;
|
return $this->where;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function order(string $set = null) : ?string
|
public function paginate(int $perPage, int $page = 1)
|
||||||
|
{
|
||||||
|
$this->limit($perPage);
|
||||||
|
$this->offset(($page - 1) * $perPage);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function pageCount(int $perPage)
|
||||||
|
{
|
||||||
|
return ceil($this->count() / $perPage);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function order(string $set = null): ?string
|
||||||
{
|
{
|
||||||
if ($set !== null) {
|
if ($set !== null) {
|
||||||
$this->order = $set;
|
$this->order = $set;
|
||||||
|
@ -39,7 +57,7 @@ class Search implements \Serializable
|
||||||
return $this->order;
|
return $this->order;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function limit(int $set = null) : ?int
|
public function limit(int $set = null): ?int
|
||||||
{
|
{
|
||||||
if ($set !== null) {
|
if ($set !== null) {
|
||||||
$this->limit = $set;
|
$this->limit = $set;
|
||||||
|
@ -47,7 +65,7 @@ class Search implements \Serializable
|
||||||
return $this->limit;
|
return $this->limit;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function offset(int $set = null) : ?int
|
public function offset(int $set = null): ?int
|
||||||
{
|
{
|
||||||
if ($set !== null) {
|
if ($set !== null) {
|
||||||
$this->offset = $set;
|
$this->offset = $set;
|
||||||
|
@ -58,7 +76,7 @@ class Search implements \Serializable
|
||||||
public function serialize()
|
public function serialize()
|
||||||
{
|
{
|
||||||
return json_encode(
|
return json_encode(
|
||||||
[$this->where(),$this->order(),$this->limit(),$this->offset()]
|
[$this->where(), $this->order(), $this->limit(), $this->offset()]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<?php
|
<?php
|
||||||
/* Destructr | https://gitlab.com/byjoby/destructr | MIT License */
|
/* Destructr | https://github.com/jobyone/destructr | MIT License */
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
namespace Destructr;
|
namespace Destructr;
|
||||||
|
|
||||||
|
|
|
@ -1,35 +1,42 @@
|
||||||
<?php
|
<?php
|
||||||
/* Destructr | https://gitlab.com/byjoby/destructr | MIT License */
|
/* Destructr | https://github.com/jobyone/destructr | MIT License */
|
||||||
declare(strict_types=1);
|
declare (strict_types = 1);
|
||||||
namespace Destructr\Drivers;
|
namespace Destructr\Drivers;
|
||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
|
||||||
use PHPUnit\DbUnit\TestCaseTrait;
|
|
||||||
use Destructr\DSO;
|
|
||||||
use Destructr\Search;
|
|
||||||
use Destructr\Factory;
|
use Destructr\Factory;
|
||||||
|
use PHPUnit\DbUnit\TestCaseTrait;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
abstract class AbstractDriverIntegrationTest extends TestCase
|
abstract class AbstractSQLDriverIntegrationTest extends TestCase
|
||||||
{
|
{
|
||||||
use TestCaseTrait;
|
use TestCaseTrait;
|
||||||
|
const TEST_TABLE = 'integrationtest';
|
||||||
|
|
||||||
|
protected static function DRIVER_USERNAME()
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static function DRIVER_PASSWORD()
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static function DRIVER_OPTIONS()
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
public static function setUpBeforeClass()
|
public static function setUpBeforeClass()
|
||||||
{
|
{
|
||||||
$pdo = static::createPDO();
|
$pdo = static::createPDO();
|
||||||
$pdo->exec('DROP TABLE '.static::TEST_TABLE);
|
$pdo->exec('DROP TABLE ' . static::TEST_TABLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testCreateTable()
|
public function testPrepareEnvironment()
|
||||||
{
|
{
|
||||||
$factory = $this->createFactory();
|
$factory = $this->createFactory();
|
||||||
//should work the first time
|
$factory->prepareEnvironment();
|
||||||
$out = $factory->createTable();
|
|
||||||
if (!$out) {
|
|
||||||
var_dump($factory->errorInfo());
|
|
||||||
}
|
|
||||||
$this->assertTrue($out);
|
|
||||||
//but not the second time, because it already exists
|
|
||||||
$this->assertFalse($factory->createTable());
|
|
||||||
//table should exist and have zero rows
|
//table should exist and have zero rows
|
||||||
$this->assertEquals(0, $this->getConnection()->getRowCount(static::TEST_TABLE));
|
$this->assertEquals(0, $this->getConnection()->getRowCount(static::TEST_TABLE));
|
||||||
}
|
}
|
||||||
|
@ -39,12 +46,12 @@ abstract class AbstractDriverIntegrationTest extends TestCase
|
||||||
$startRowCount = $this->getConnection()->getRowCount(static::TEST_TABLE);
|
$startRowCount = $this->getConnection()->getRowCount(static::TEST_TABLE);
|
||||||
$factory = $this->createFactory();
|
$factory = $this->createFactory();
|
||||||
//inserting a freshly created object should return true
|
//inserting a freshly created object should return true
|
||||||
$o = $factory->create(['dso.id'=>'object-one']);
|
$o = $factory->create(['dso.id' => 'object-one']);
|
||||||
$this->assertTrue($o->insert());
|
$this->assertTrue($o->insert());
|
||||||
//inserting it a second time should not
|
//inserting it a second time should not
|
||||||
$this->assertFalse($o->insert());
|
$this->assertFalse($o->insert());
|
||||||
//there should now be one more row
|
//there should now be one more row
|
||||||
$this->assertEquals($startRowCount+1, $this->getConnection()->getRowCount(static::TEST_TABLE));
|
$this->assertEquals($startRowCount + 1, $this->getConnection()->getRowCount(static::TEST_TABLE));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testReadAndUpdate()
|
public function testReadAndUpdate()
|
||||||
|
@ -52,9 +59,9 @@ abstract class AbstractDriverIntegrationTest extends TestCase
|
||||||
$startRowCount = $this->getConnection()->getRowCount(static::TEST_TABLE);
|
$startRowCount = $this->getConnection()->getRowCount(static::TEST_TABLE);
|
||||||
$factory = $this->createFactory();
|
$factory = $this->createFactory();
|
||||||
//insert some new objects
|
//insert some new objects
|
||||||
$a1 = $factory->create(['foo'=>'bar']);
|
$a1 = $factory->create(['foo' => 'bar']);
|
||||||
$a1->insert();
|
$a1->insert();
|
||||||
$b1 = $factory->create(['foo.bar'=>'baz']);
|
$b1 = $factory->create(['foo.bar' => 'baz']);
|
||||||
$b1->insert();
|
$b1->insert();
|
||||||
//read objects back out
|
//read objects back out
|
||||||
$a2 = $factory->read($a1['dso.id']);
|
$a2 = $factory->read($a1['dso.id']);
|
||||||
|
@ -77,7 +84,7 @@ abstract class AbstractDriverIntegrationTest extends TestCase
|
||||||
$this->assertNotEquals($a1->get(), $a3->get());
|
$this->assertNotEquals($a1->get(), $a3->get());
|
||||||
$this->assertNotEquals($b1->get(), $b3->get());
|
$this->assertNotEquals($b1->get(), $b3->get());
|
||||||
//there should now be two more rows
|
//there should now be two more rows
|
||||||
$this->assertEquals($startRowCount+2, $this->getConnection()->getRowCount(static::TEST_TABLE));
|
$this->assertEquals($startRowCount + 2, $this->getConnection()->getRowCount(static::TEST_TABLE));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testDelete()
|
public function testDelete()
|
||||||
|
@ -85,17 +92,17 @@ abstract class AbstractDriverIntegrationTest extends TestCase
|
||||||
$startRowCount = $this->getConnection()->getRowCount(static::TEST_TABLE);
|
$startRowCount = $this->getConnection()->getRowCount(static::TEST_TABLE);
|
||||||
$factory = $this->createFactory();
|
$factory = $this->createFactory();
|
||||||
//insert some new objects
|
//insert some new objects
|
||||||
$a1 = $factory->create(['testDelete'=>'undelete me']);
|
$a1 = $factory->create(['testDelete' => 'undelete me']);
|
||||||
$a1->insert();
|
$a1->insert();
|
||||||
$b1 = $factory->create(['testDelete'=>'should be permanently deleted']);
|
$b1 = $factory->create(['testDelete' => 'should be permanently deleted']);
|
||||||
$b1->insert();
|
$b1->insert();
|
||||||
//there should now be two more rows
|
//there should now be two more rows
|
||||||
$this->assertEquals($startRowCount+2, $this->getConnection()->getRowCount(static::TEST_TABLE));
|
$this->assertEquals($startRowCount + 2, $this->getConnection()->getRowCount(static::TEST_TABLE));
|
||||||
//delete one permanently and the other not, both shoudl take effect immediately
|
//delete one permanently and the other not, both shoudl take effect immediately
|
||||||
$a1->delete();
|
$a1->delete();
|
||||||
$b1->delete(true);
|
$b1->delete(true);
|
||||||
//there should now be only one more row
|
//there should now be only one more row
|
||||||
$this->assertEquals($startRowCount+1, $this->getConnection()->getRowCount(static::TEST_TABLE));
|
$this->assertEquals($startRowCount + 1, $this->getConnection()->getRowCount(static::TEST_TABLE));
|
||||||
//a should be possible to read a back out with the right flags
|
//a should be possible to read a back out with the right flags
|
||||||
$this->assertNull($factory->read($a1['dso.id']));
|
$this->assertNull($factory->read($a1['dso.id']));
|
||||||
$this->assertNotNull($factory->read($a1['dso.id'], 'dso.id', true));
|
$this->assertNotNull($factory->read($a1['dso.id'], 'dso.id', true));
|
||||||
|
@ -116,25 +123,25 @@ abstract class AbstractDriverIntegrationTest extends TestCase
|
||||||
$factory->create([
|
$factory->create([
|
||||||
'testSearch' => 'a',
|
'testSearch' => 'a',
|
||||||
'a' => '1',
|
'a' => '1',
|
||||||
'b' => '2'
|
'b' => '2',
|
||||||
])->insert();
|
])->insert();
|
||||||
$factory->create([
|
$factory->create([
|
||||||
'testSearch' => 'b',
|
'testSearch' => 'b',
|
||||||
'a' => '2',
|
'a' => '2',
|
||||||
'b' => '1'
|
'b' => '1',
|
||||||
])->insert();
|
])->insert();
|
||||||
$factory->create([
|
$factory->create([
|
||||||
'testSearch' => 'c',
|
'testSearch' => 'c',
|
||||||
'a' => '3',
|
'a' => '3',
|
||||||
'b' => '4'
|
'b' => '4',
|
||||||
])->insert();
|
])->insert();
|
||||||
$factory->create([
|
$factory->create([
|
||||||
'testSearch' => 'a',
|
'testSearch' => 'a',
|
||||||
'a' => '4',
|
'a' => '4',
|
||||||
'b' => '3'
|
'b' => '3',
|
||||||
])->insert();
|
])->insert();
|
||||||
//there should now be four more rows
|
//there should now be four more rows
|
||||||
$this->assertEquals($startRowCount+4, $this->getConnection()->getRowCount(static::TEST_TABLE));
|
$this->assertEquals($startRowCount + 4, $this->getConnection()->getRowCount(static::TEST_TABLE));
|
||||||
//TODO: test some searches
|
//TODO: test some searches
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -146,10 +153,10 @@ abstract class AbstractDriverIntegrationTest extends TestCase
|
||||||
{
|
{
|
||||||
$class = static::DRIVER_CLASS;
|
$class = static::DRIVER_CLASS;
|
||||||
return new $class(
|
return new $class(
|
||||||
static::DRIVER_DSN,
|
static::DRIVER_DSN(),
|
||||||
static::DRIVER_USERNAME,
|
static::DRIVER_USERNAME(),
|
||||||
static::DRIVER_PASSWORD,
|
static::DRIVER_PASSWORD(),
|
||||||
static::DRIVER_OPTIONS
|
static::DRIVER_OPTIONS()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -165,10 +172,10 @@ abstract class AbstractDriverIntegrationTest extends TestCase
|
||||||
protected static function createPDO()
|
protected static function createPDO()
|
||||||
{
|
{
|
||||||
return new \PDO(
|
return new \PDO(
|
||||||
static::DRIVER_DSN,
|
static::DRIVER_DSN(),
|
||||||
static::DRIVER_USERNAME,
|
static::DRIVER_USERNAME(),
|
||||||
static::DRIVER_PASSWORD,
|
static::DRIVER_PASSWORD(),
|
||||||
static::DRIVER_OPTIONS
|
static::DRIVER_OPTIONS()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
154
tests/Drivers/AbstractSQLDriverSchemaChangeTest.php
Normal file
154
tests/Drivers/AbstractSQLDriverSchemaChangeTest.php
Normal file
|
@ -0,0 +1,154 @@
|
||||||
|
<?php
|
||||||
|
/* Destructr | https://github.com/jobyone/destructr | MIT License */
|
||||||
|
declare (strict_types = 1);
|
||||||
|
namespace Destructr\Drivers;
|
||||||
|
|
||||||
|
use PDO;
|
||||||
|
use PHPUnit\DbUnit\TestCaseTrait;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class tests a Driver's ability to correctly change schemas.
|
||||||
|
*/
|
||||||
|
abstract class AbstractSQLDriverSchemaChangeTest extends TestCase
|
||||||
|
{
|
||||||
|
use TestCaseTrait;
|
||||||
|
const TEST_TABLE = 'schematest';
|
||||||
|
|
||||||
|
protected static function DRIVER_USERNAME()
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static function DRIVER_PASSWORD()
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static function DRIVER_OPTIONS()
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSchemaChanges()
|
||||||
|
{
|
||||||
|
// set up using schema A
|
||||||
|
$factory = $this->createFactoryA();
|
||||||
|
$this->assertFalse($factory->checkEnvironment());
|
||||||
|
$factory->prepareEnvironment();
|
||||||
|
$this->assertTrue($factory->checkEnvironment());
|
||||||
|
$factory->updateEnvironment();
|
||||||
|
// verify schema in database
|
||||||
|
$this->assertEquals(
|
||||||
|
$factory->schema,
|
||||||
|
$factory->driver()->getSchema('schematest')
|
||||||
|
);
|
||||||
|
// add some content
|
||||||
|
$new = $factory->create([
|
||||||
|
'dso.id' => 'dso1',
|
||||||
|
'test.a' => 'value a1',
|
||||||
|
'test.b' => 'value b1',
|
||||||
|
'test.c' => 'value c1',
|
||||||
|
]);
|
||||||
|
$new->insert();
|
||||||
|
$new = $factory->create([
|
||||||
|
'dso.id' => 'dso2',
|
||||||
|
'test.a' => 'value a2',
|
||||||
|
'test.b' => 'value b2',
|
||||||
|
'test.c' => 'value c2',
|
||||||
|
]);
|
||||||
|
$new->insert();
|
||||||
|
$new = $factory->create([
|
||||||
|
'dso.id' => 'dso3',
|
||||||
|
'test.a' => 'value a3',
|
||||||
|
'test.b' => 'value b3',
|
||||||
|
'test.c' => 'value c3',
|
||||||
|
]);
|
||||||
|
$new->insert();
|
||||||
|
// verify data in table matches
|
||||||
|
$pdo = $this->createPDO();
|
||||||
|
$this->assertEquals(3, $this->getConnection()->getRowCount('schematest'));
|
||||||
|
for ($i = 1; $i <= 3; $i++) {
|
||||||
|
$row = $pdo->query('select dso_id, test_a, test_b from schematest where dso_id = "dso' . $i . '"')->fetch(PDO::FETCH_ASSOC);
|
||||||
|
$this->assertEquals(['dso_id' => "dso$i", 'test_a' => "value a$i", 'test_b' => "value b$i"], $row);
|
||||||
|
}
|
||||||
|
// change to schema B
|
||||||
|
sleep(1); //a table can't have its schema updated faster than once per second
|
||||||
|
$factory = $this->createFactoryB();
|
||||||
|
$this->assertFalse($factory->checkEnvironment());
|
||||||
|
$factory->prepareEnvironment();
|
||||||
|
$this->assertFalse($factory->checkEnvironment());
|
||||||
|
$factory->updateEnvironment();
|
||||||
|
$this->assertTrue($factory->checkEnvironment());
|
||||||
|
// verify schema in database
|
||||||
|
$this->assertEquals(
|
||||||
|
$factory->schema,
|
||||||
|
$factory->driver()->getSchema('schematest')
|
||||||
|
);
|
||||||
|
// verify data in table matches
|
||||||
|
$pdo = $this->createPDO();
|
||||||
|
$this->assertEquals(3, $this->getConnection()->getRowCount('schematest'));
|
||||||
|
for ($i = 1; $i <= 3; $i++) {
|
||||||
|
$row = $pdo->query('select dso_id, test_a_2, test_c from schematest where dso_id = "dso' . $i . '"')->fetch(PDO::FETCH_ASSOC);
|
||||||
|
$this->assertEquals(['dso_id' => "dso$i", 'test_a_2' => "value a$i", 'test_c' => "value c$i"], $row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static function createFactoryA()
|
||||||
|
{
|
||||||
|
$driver = static::createDriver();
|
||||||
|
$factory = new FactorySchemaA(
|
||||||
|
$driver,
|
||||||
|
static::TEST_TABLE
|
||||||
|
);
|
||||||
|
return $factory;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static function createFactoryB()
|
||||||
|
{
|
||||||
|
$driver = static::createDriver();
|
||||||
|
$factory = new FactorySchemaB(
|
||||||
|
$driver,
|
||||||
|
static::TEST_TABLE
|
||||||
|
);
|
||||||
|
return $factory;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static function createDriver()
|
||||||
|
{
|
||||||
|
$class = static::DRIVER_CLASS;
|
||||||
|
return new $class(
|
||||||
|
static::DRIVER_DSN(),
|
||||||
|
static::DRIVER_USERNAME(),
|
||||||
|
static::DRIVER_PASSWORD(),
|
||||||
|
static::DRIVER_OPTIONS()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static function createPDO()
|
||||||
|
{
|
||||||
|
return new \PDO(
|
||||||
|
static::DRIVER_DSN(),
|
||||||
|
static::DRIVER_USERNAME(),
|
||||||
|
static::DRIVER_PASSWORD(),
|
||||||
|
static::DRIVER_OPTIONS()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getConnection()
|
||||||
|
{
|
||||||
|
return $this->createDefaultDBConnection($this->createPDO(), 'phpunit');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDataSet()
|
||||||
|
{
|
||||||
|
return new \PHPUnit\DbUnit\DataSet\DefaultDataSet();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function setUpBeforeClass()
|
||||||
|
{
|
||||||
|
$pdo = static::createPDO();
|
||||||
|
$pdo->exec('DROP TABLE schematest');
|
||||||
|
$pdo->exec('DROP TABLE destructr_schema');
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,78 +1,97 @@
|
||||||
<?php
|
<?php
|
||||||
/* Destructr | https://gitlab.com/byjoby/destructr | MIT License */
|
/* Destructr | https://github.com/jobyone/destructr | MIT License */
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace Destructr\Drivers;
|
namespace Destructr\Drivers;
|
||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
|
||||||
use PHPUnit\DbUnit\TestCaseTrait;
|
|
||||||
use Destructr\DSO;
|
use Destructr\DSO;
|
||||||
|
use Destructr\Factory;
|
||||||
use Destructr\Search;
|
use Destructr\Search;
|
||||||
|
use PHPUnit\DbUnit\TestCaseTrait;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class tests a factory in isolation. In the name of simplicity it's a bit
|
* This class tests a factory in isolation. In the name of simplicity it's a bit
|
||||||
* simplistic, because it doesn't get the help of the Factory.
|
* simplistic, because it doesn't get the help of the Factory.
|
||||||
*
|
*
|
||||||
* There is also a class called AbstractDriverIntegrationTest that tests drivers
|
* There is also a class called AbstractSQLDriverIntegrationTest that tests drivers
|
||||||
* through a Factory. The results of that are harder to interpret, but more
|
* through a Factory. The results of that are harder to interpret, but more
|
||||||
* properly and thoroughly test the Drivers in a real environment.
|
* properly and thoroughly test the Drivers in a real environment.
|
||||||
*/
|
*/
|
||||||
abstract class AbstractDriverTest extends TestCase
|
abstract class AbstractSQLDriverTest extends TestCase
|
||||||
{
|
{
|
||||||
use TestCaseTrait;
|
use TestCaseTrait;
|
||||||
|
|
||||||
|
abstract protected static function DRIVER_DSN();
|
||||||
|
|
||||||
|
protected static function DRIVER_USERNAME()
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static function DRIVER_PASSWORD()
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static function DRIVER_OPTIONS()
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
In actual practice, these would come from a Factory
|
In actual practice, these would come from a Factory
|
||||||
*/
|
*/
|
||||||
protected $virtualColumns = [
|
protected $schema = [
|
||||||
'dso.id' => [
|
'dso.id' => [
|
||||||
'name'=>'dso_id',
|
'name' => 'dso_id',
|
||||||
'type'=>'VARCHAR(16)',
|
'type' => 'VARCHAR(16)',
|
||||||
'index' => 'BTREE',
|
'index' => 'BTREE',
|
||||||
'unique' => true
|
'unique' => true,
|
||||||
],
|
],
|
||||||
'dso.type' => [
|
'dso.type' => [
|
||||||
'name'=>'dso_type',
|
'name' => 'dso_type',
|
||||||
'type'=>'VARCHAR(30)',
|
'type' => 'VARCHAR(30)',
|
||||||
'index'=>'BTREE'
|
'index' => 'BTREE',
|
||||||
],
|
],
|
||||||
'dso.deleted' => [
|
'dso.deleted' => [
|
||||||
'name'=>'dso_deleted',
|
'name' => 'dso_deleted',
|
||||||
'type'=>'BIGINT',
|
'type' => 'BIGINT',
|
||||||
'index'=>'BTREE'
|
'index' => 'BTREE',
|
||||||
]
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
public function testCreateTable()
|
public function testPrepareEnvironment()
|
||||||
{
|
{
|
||||||
$driver = $this->createDriver();
|
$driver = $this->createDriver();
|
||||||
$res = $driver->createTable('testCreateTable', $this->virtualColumns);
|
$this->assertFalse($driver->tableExists('testPrepareEnvironment'));
|
||||||
$this->assertTrue($res);
|
$this->assertFalse($driver->tableExists(AbstractDriver::SCHEMA_TABLE));
|
||||||
$this->assertEquals(0, $this->getConnection()->getRowCount('testCreateTable'));
|
$driver->prepareEnvironment('testPrepareEnvironment', $this->schema);
|
||||||
$this->assertFalse($driver->createTable('testCreateTable', $this->virtualColumns));
|
$this->assertTrue($driver->tableExists(AbstractDriver::SCHEMA_TABLE));
|
||||||
|
$this->assertTrue($driver->tableExists('testPrepareEnvironment'));
|
||||||
|
$this->assertEquals(1, $this->getConnection()->getRowCount(AbstractDriver::SCHEMA_TABLE));
|
||||||
|
$this->assertEquals(0, $this->getConnection()->getRowCount('testPrepareEnvironment'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testInsert()
|
public function testInsert()
|
||||||
{
|
{
|
||||||
$driver = $this->createDriver();
|
$driver = $this->createDriver();
|
||||||
$driver->createTable('testInsert', $this->virtualColumns);
|
$driver->prepareEnvironment('testInsert', $this->schema);
|
||||||
//test inserting an object
|
//test inserting an object
|
||||||
$o = new DSO(['dso.id'=>'first-inserted']);
|
$o = new DSO(['dso.id' => 'first-inserted'], new Factory($driver, 'no_table'));
|
||||||
$this->assertTrue($driver->insert('testInsert', $o));
|
$this->assertTrue($driver->insert('testInsert', $o));
|
||||||
$this->assertEquals(1, $this->getConnection()->getRowCount('testInsert'));
|
$this->assertEquals(1, $this->getConnection()->getRowCount('testInsert'));
|
||||||
//test inserting a second object
|
//test inserting a second object
|
||||||
$o = new DSO(['dso.id'=>'second-inserted']);
|
$o = new DSO(['dso.id' => 'second-inserted'], new Factory($driver, 'no_table'));
|
||||||
$this->assertTrue($driver->insert('testInsert', $o));
|
$this->assertTrue($driver->insert('testInsert', $o));
|
||||||
$this->assertEquals(2, $this->getConnection()->getRowCount('testInsert'));
|
$this->assertEquals(2, $this->getConnection()->getRowCount('testInsert'));
|
||||||
//test inserting a second object with an existing id, it shouldn't work
|
|
||||||
$o = new DSO(['dso.id'=>'first-inserted']);
|
|
||||||
$this->assertFalse($driver->insert('testInsert', $o));
|
|
||||||
$this->assertEquals(2, $this->getConnection()->getRowCount('testInsert'));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testSelect()
|
public function testSelect()
|
||||||
{
|
{
|
||||||
$driver = $this->createDriver();
|
$driver = $this->createDriver();
|
||||||
$driver->createTable('testSelect', $this->virtualColumns);
|
$driver->prepareEnvironment('testSelect', $this->schema);
|
||||||
//set up dummy data
|
//set up dummy data
|
||||||
$this->setup_testSelect();
|
$this->setup_testSelect();
|
||||||
//empty search
|
//empty search
|
||||||
|
@ -97,79 +116,38 @@ abstract class AbstractDriverTest extends TestCase
|
||||||
// search with no results, searching by virtual column
|
// search with no results, searching by virtual column
|
||||||
$search = new Search();
|
$search = new Search();
|
||||||
$search->where('`dso_type` = :param');
|
$search->where('`dso_type` = :param');
|
||||||
$results = $driver->select('testSelect', $search, [':param'=>'type-none']);
|
$results = $driver->select('testSelect', $search, [':param' => 'type-none']);
|
||||||
$this->assertSame(0, count($results));
|
$this->assertSame(0, count($results));
|
||||||
// search with no results, searching by json field
|
// search with no results, searching by json field
|
||||||
$search = new Search();
|
$search = new Search();
|
||||||
$search->where('${foo} = :param');
|
$search->where('${foo} = :param');
|
||||||
$results = $driver->select('testSelect', $search, [':param'=>'nonexistent foo value']);
|
$results = $driver->select('testSelect', $search, [':param' => 'nonexistent foo value']);
|
||||||
$this->assertSame(0, count($results));
|
$this->assertSame(0, count($results));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testDelete()
|
|
||||||
{
|
|
||||||
$driver = $this->createDriver();
|
|
||||||
$driver->createTable('testDelete', $this->virtualColumns);
|
|
||||||
//set up dummy data
|
|
||||||
$this->setup_testDelete();
|
|
||||||
//try deleting an item
|
|
||||||
$dso = new DSO(['dso.id'=>'item-a-1']);
|
|
||||||
$driver->delete('testDelete', $dso);
|
|
||||||
$this->assertEquals(3, $this->getConnection()->getRowCount('testDelete'));
|
|
||||||
//try deleting an item at the other end of the table
|
|
||||||
$dso = new DSO(['dso.id'=>'item-b-2']);
|
|
||||||
$driver->delete('testDelete', $dso);
|
|
||||||
$this->assertEquals(2, $this->getConnection()->getRowCount('testDelete'));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function setup_testDelete()
|
|
||||||
{
|
|
||||||
$driver = $this->createDriver();
|
|
||||||
$driver->insert('testDelete', new DSO([
|
|
||||||
'dso'=>['id'=>'item-a-1','type'=>'type-a'],
|
|
||||||
'foo'=>'bar',
|
|
||||||
'sort'=>'a'
|
|
||||||
]));
|
|
||||||
$driver->insert('testDelete', new DSO([
|
|
||||||
'dso'=>['id'=>'item-a-2','type'=>'type-a'],
|
|
||||||
'foo'=>'baz',
|
|
||||||
'sort'=>'c'
|
|
||||||
]));
|
|
||||||
$driver->insert('testDelete', new DSO([
|
|
||||||
'dso'=>['id'=>'item-b-1','type'=>'type-b'],
|
|
||||||
'foo'=>'buz',
|
|
||||||
'sort'=>'b'
|
|
||||||
]));
|
|
||||||
$driver->insert('testDelete', new DSO([
|
|
||||||
'dso'=>['id'=>'item-b-2','type'=>'type-b','deleted'=>100],
|
|
||||||
'foo'=>'quz',
|
|
||||||
'sort'=>'d'
|
|
||||||
]));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function setup_testSelect()
|
protected function setup_testSelect()
|
||||||
{
|
{
|
||||||
$driver = $this->createDriver();
|
$driver = $this->createDriver();
|
||||||
$driver->insert('testSelect', new DSO([
|
$driver->insert('testSelect', new DSO([
|
||||||
'dso'=>['id'=>'item-a-1','type'=>'type-a'],
|
'dso' => ['id' => 'item-a-1', 'type' => 'type-a'],
|
||||||
'foo'=>'bar',
|
'foo' => 'bar',
|
||||||
'sort'=>'a'
|
'sort' => 'a',
|
||||||
]));
|
], new Factory($driver, 'no_table')));
|
||||||
$driver->insert('testSelect', new DSO([
|
$driver->insert('testSelect', new DSO([
|
||||||
'dso'=>['id'=>'item-a-2','type'=>'type-a'],
|
'dso' => ['id' => 'item-a-2', 'type' => 'type-a'],
|
||||||
'foo'=>'baz',
|
'foo' => 'baz',
|
||||||
'sort'=>'c'
|
'sort' => 'c',
|
||||||
]));
|
], new Factory($driver, 'no_table')));
|
||||||
$driver->insert('testSelect', new DSO([
|
$driver->insert('testSelect', new DSO([
|
||||||
'dso'=>['id'=>'item-b-1','type'=>'type-b'],
|
'dso' => ['id' => 'item-b-1', 'type' => 'type-b'],
|
||||||
'foo'=>'buz',
|
'foo' => 'buz',
|
||||||
'sort'=>'b'
|
'sort' => 'b',
|
||||||
]));
|
], new Factory($driver, 'no_table')));
|
||||||
$driver->insert('testSelect', new DSO([
|
$driver->insert('testSelect', new DSO([
|
||||||
'dso'=>['id'=>'item-b-2','type'=>'type-b','deleted'=>100],
|
'dso' => ['id' => 'item-b-2', 'type' => 'type-b', 'deleted' => 100],
|
||||||
'foo'=>'quz',
|
'foo' => 'quz',
|
||||||
'sort'=>'d'
|
'sort' => 'd',
|
||||||
]));
|
], new Factory($driver, 'no_table')));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -180,29 +158,29 @@ abstract class AbstractDriverTest extends TestCase
|
||||||
{
|
{
|
||||||
$class = static::DRIVER_CLASS;
|
$class = static::DRIVER_CLASS;
|
||||||
return new $class(
|
return new $class(
|
||||||
static::DRIVER_DSN,
|
static::DRIVER_DSN(),
|
||||||
static::DRIVER_USERNAME,
|
static::DRIVER_USERNAME(),
|
||||||
static::DRIVER_PASSWORD,
|
static::DRIVER_PASSWORD(),
|
||||||
static::DRIVER_OPTIONS
|
static::DRIVER_OPTIONS()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function setUpBeforeClass()
|
public static function setUpBeforeClass()
|
||||||
{
|
{
|
||||||
$pdo = static::createPDO();
|
$pdo = static::createPDO();
|
||||||
$pdo->exec('DROP TABLE testCreateTable');
|
$pdo->exec('DROP TABLE testPrepareEnvironment');
|
||||||
$pdo->exec('DROP TABLE testInsert');
|
$pdo->exec('DROP TABLE testInsert');
|
||||||
$pdo->exec('DROP TABLE testSelect');
|
$pdo->exec('DROP TABLE testSelect');
|
||||||
$pdo->exec('DROP TABLE testDelete');
|
$pdo->exec('DROP TABLE destructr_schema');
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static function createPDO()
|
protected static function createPDO()
|
||||||
{
|
{
|
||||||
return new \PDO(
|
return new \PDO(
|
||||||
static::DRIVER_DSN,
|
static::DRIVER_DSN(),
|
||||||
static::DRIVER_USERNAME,
|
static::DRIVER_USERNAME(),
|
||||||
static::DRIVER_PASSWORD,
|
static::DRIVER_PASSWORD(),
|
||||||
static::DRIVER_OPTIONS
|
static::DRIVER_OPTIONS()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
30
tests/Drivers/FactorySchemaA.php
Normal file
30
tests/Drivers/FactorySchemaA.php
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
<?php
|
||||||
|
/* Destructr | https://github.com/jobyone/destructr | MIT License */
|
||||||
|
declare (strict_types = 1);
|
||||||
|
namespace Destructr\Drivers;
|
||||||
|
|
||||||
|
use Destructr\Factory;
|
||||||
|
|
||||||
|
class FactorySchemaA extends Factory
|
||||||
|
{
|
||||||
|
public $schema = [
|
||||||
|
'dso.id' => [
|
||||||
|
'name' => 'dso_id', //column name to be used
|
||||||
|
'type' => 'VARCHAR(16)', //column type
|
||||||
|
'index' => 'BTREE', //whether/how to index
|
||||||
|
'unique' => true, //whether column should be unique
|
||||||
|
'primary' => true, //whether column should be the primary key
|
||||||
|
],
|
||||||
|
'test.a' => [
|
||||||
|
'name' => 'test_a',
|
||||||
|
'type' => 'VARCHAR(100)',
|
||||||
|
'index' => 'BTREE',
|
||||||
|
]
|
||||||
|
,
|
||||||
|
'test.b' => [
|
||||||
|
'name' => 'test_b',
|
||||||
|
'type' => 'VARCHAR(100)',
|
||||||
|
'index' => 'BTREE',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
30
tests/Drivers/FactorySchemaB.php
Normal file
30
tests/Drivers/FactorySchemaB.php
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
<?php
|
||||||
|
/* Destructr | https://github.com/jobyone/destructr | MIT License */
|
||||||
|
declare (strict_types = 1);
|
||||||
|
namespace Destructr\Drivers;
|
||||||
|
|
||||||
|
use Destructr\Factory;
|
||||||
|
|
||||||
|
class FactorySchemaB extends Factory
|
||||||
|
{
|
||||||
|
public $schema = [
|
||||||
|
'dso.id' => [
|
||||||
|
'name' => 'dso_id', //column name to be used
|
||||||
|
'type' => 'VARCHAR(16)', //column type
|
||||||
|
'index' => 'BTREE', //whether/how to index
|
||||||
|
'unique' => true, //whether column should be unique
|
||||||
|
'primary' => true, //whether column should be the primary key
|
||||||
|
],
|
||||||
|
'test.a' => [
|
||||||
|
'name' => 'test_a_2',
|
||||||
|
'type' => 'VARCHAR(100)',
|
||||||
|
'index' => 'BTREE',
|
||||||
|
]
|
||||||
|
,
|
||||||
|
'test.c' => [
|
||||||
|
'name' => 'test_c',
|
||||||
|
'type' => 'VARCHAR(100)',
|
||||||
|
'index' => 'BTREE',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
39
tests/Drivers/MariaDB/MariaDBDriverIntegrationTest.php
Normal file
39
tests/Drivers/MariaDB/MariaDBDriverIntegrationTest.php
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
<?php
|
||||||
|
/* Destructr | https://github.com/jobyone/destructr | MIT License */
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Destructr\Drivers\MariaDB;
|
||||||
|
|
||||||
|
use Destructr\Drivers\AbstractSQLDriverIntegrationTest;
|
||||||
|
use Destructr\Drivers\MariaDBDriver;
|
||||||
|
|
||||||
|
class MariaDBDriverIntegrationTest extends AbstractSQLDriverIntegrationTest
|
||||||
|
{
|
||||||
|
const DRIVER_CLASS = MariaDBDriver::class;
|
||||||
|
|
||||||
|
protected static function DRIVER_DSN()
|
||||||
|
{
|
||||||
|
return sprintf(
|
||||||
|
'mysql:host=%s:%s;dbname=%s',
|
||||||
|
$_ENV['TEST_MYSQL_SERVER'],
|
||||||
|
$_ENV['TEST_MYSQL_PORT'],
|
||||||
|
static::DRIVER_DBNAME()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static function DRIVER_DBNAME()
|
||||||
|
{
|
||||||
|
return @$_ENV['TEST_MARIADB_DBNAME'] ?? 'destructr_test';
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static function DRIVER_USERNAME()
|
||||||
|
{
|
||||||
|
return @$_ENV['TEST_MARIADB_USER'] ?? 'root';
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static function DRIVER_PASSWORD()
|
||||||
|
{
|
||||||
|
return @$_ENV['TEST_MARIADB_PASSWORD'] ?? 'root';
|
||||||
|
}
|
||||||
|
}
|
39
tests/Drivers/MariaDB/MariaDBDriverSchemaChangeTest.php
Normal file
39
tests/Drivers/MariaDB/MariaDBDriverSchemaChangeTest.php
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
<?php
|
||||||
|
/* Destructr | https://github.com/jobyone/destructr | MIT License */
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Destructr\Drivers\MariaDB;
|
||||||
|
|
||||||
|
use Destructr\Drivers\AbstractSQLDriverSchemaChangeTest;
|
||||||
|
use Destructr\Drivers\MariaDBDriver;
|
||||||
|
|
||||||
|
class MariaDBDriverSchemaChangeTest extends AbstractSQLDriverSchemaChangeTest
|
||||||
|
{
|
||||||
|
const DRIVER_CLASS = MariaDBDriver::class;
|
||||||
|
|
||||||
|
protected static function DRIVER_DSN()
|
||||||
|
{
|
||||||
|
return sprintf(
|
||||||
|
'mysql:host=%s:%s;dbname=%s',
|
||||||
|
$_ENV['TEST_MYSQL_SERVER'],
|
||||||
|
$_ENV['TEST_MYSQL_PORT'],
|
||||||
|
static::DRIVER_DBNAME()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static function DRIVER_DBNAME()
|
||||||
|
{
|
||||||
|
return @$_ENV['TEST_MARIADB_DBNAME'] ?? 'destructr_test';
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static function DRIVER_USERNAME()
|
||||||
|
{
|
||||||
|
return @$_ENV['TEST_MARIADB_USER'] ?? 'root';
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static function DRIVER_PASSWORD()
|
||||||
|
{
|
||||||
|
return @$_ENV['TEST_MARIADB_PASSWORD'] ?? 'root';
|
||||||
|
}
|
||||||
|
}
|
39
tests/Drivers/MariaDB/MariaDBDriverTest.php
Normal file
39
tests/Drivers/MariaDB/MariaDBDriverTest.php
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
<?php
|
||||||
|
/* Destructr | https://github.com/jobyone/destructr | MIT License */
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Destructr\Drivers\MariaDB;
|
||||||
|
|
||||||
|
use Destructr\Drivers\AbstractSQLDriverTest;
|
||||||
|
use Destructr\Drivers\MariaDBDriver;
|
||||||
|
|
||||||
|
class MariaDBDriverTest extends AbstractSQLDriverTest
|
||||||
|
{
|
||||||
|
const DRIVER_CLASS = MariaDBDriver::class;
|
||||||
|
|
||||||
|
protected static function DRIVER_DSN()
|
||||||
|
{
|
||||||
|
return sprintf(
|
||||||
|
'mysql:host=%s:%s;dbname=%s',
|
||||||
|
$_ENV['TEST_MYSQL_SERVER'],
|
||||||
|
$_ENV['TEST_MYSQL_PORT'],
|
||||||
|
static::DRIVER_DBNAME()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static function DRIVER_DBNAME()
|
||||||
|
{
|
||||||
|
return @$_ENV['TEST_MARIADB_DBNAME'] ?? 'destructr_test';
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static function DRIVER_USERNAME()
|
||||||
|
{
|
||||||
|
return @$_ENV['TEST_MARIADB_USER'] ?? 'root';
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static function DRIVER_PASSWORD()
|
||||||
|
{
|
||||||
|
return @$_ENV['TEST_MARIADB_PASSWORD'] ?? 'root';
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,18 +1,39 @@
|
||||||
<?php
|
<?php
|
||||||
/* Destructr | https://gitlab.com/byjoby/destructr | MIT License */
|
/* Destructr | https://github.com/jobyone/destructr | MIT License */
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace Destructr\Drivers\MySQL;
|
namespace Destructr\Drivers\MySQL;
|
||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
use Destructr\Drivers\AbstractSQLDriverIntegrationTest;
|
||||||
use Destructr\Drivers\AbstractDriverIntegrationTest;
|
|
||||||
use Destructr\Drivers\MySQLDriver;
|
use Destructr\Drivers\MySQLDriver;
|
||||||
|
|
||||||
class MySQLDriverIntegrationTest extends AbstractDriverIntegrationTest
|
class MySQLDriverIntegrationTest extends AbstractSQLDriverIntegrationTest
|
||||||
{
|
{
|
||||||
const DRIVER_CLASS = \Destructr\Drivers\MySQLDriver::class;
|
const DRIVER_CLASS = MySQLDriver::class;
|
||||||
const DRIVER_DSN = 'mysql:host=mysql;dbname=destructr_test';
|
|
||||||
const DRIVER_USERNAME = 'root';
|
protected static function DRIVER_DSN()
|
||||||
const DRIVER_PASSWORD = 'badpassword';
|
{
|
||||||
const DRIVER_OPTIONS = null;
|
return sprintf(
|
||||||
const TEST_TABLE = 'integrationtest';
|
'mysql:host=%s:%s;dbname=%s',
|
||||||
|
$_ENV['TEST_MYSQL_SERVER'],
|
||||||
|
$_ENV['TEST_MYSQL_PORT'],
|
||||||
|
static::DRIVER_DBNAME()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static function DRIVER_DBNAME()
|
||||||
|
{
|
||||||
|
return @$_ENV['TEST_MYSQL_DBNAME'] ?? 'destructr_test';
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static function DRIVER_USERNAME()
|
||||||
|
{
|
||||||
|
return @$_ENV['TEST_MYSQL_USER'] ?? 'root';
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static function DRIVER_PASSWORD()
|
||||||
|
{
|
||||||
|
return @$_ENV['TEST_MYSQL_PASSWORD'] ?? 'root';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
39
tests/Drivers/MySQL/MySQLDriverSchemaChangeTest.php
Normal file
39
tests/Drivers/MySQL/MySQLDriverSchemaChangeTest.php
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
<?php
|
||||||
|
/* Destructr | https://github.com/jobyone/destructr | MIT License */
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Destructr\Drivers\MySQL;
|
||||||
|
|
||||||
|
use Destructr\Drivers\AbstractSQLDriverSchemaChangeTest;
|
||||||
|
use Destructr\Drivers\MySQLDriver;
|
||||||
|
|
||||||
|
class MySQLDriverSchemaChangeTest extends AbstractSQLDriverSchemaChangeTest
|
||||||
|
{
|
||||||
|
const DRIVER_CLASS = MySQLDriver::class;
|
||||||
|
|
||||||
|
protected static function DRIVER_DSN()
|
||||||
|
{
|
||||||
|
return sprintf(
|
||||||
|
'mysql:host=%s:%s;dbname=%s',
|
||||||
|
$_ENV['TEST_MYSQL_SERVER'],
|
||||||
|
$_ENV['TEST_MYSQL_PORT'],
|
||||||
|
static::DRIVER_DBNAME()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static function DRIVER_DBNAME()
|
||||||
|
{
|
||||||
|
return @$_ENV['TEST_MYSQL_DBNAME'] ?? 'destructr_test';
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static function DRIVER_USERNAME()
|
||||||
|
{
|
||||||
|
return @$_ENV['TEST_MYSQL_USER'] ?? 'root';
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static function DRIVER_PASSWORD()
|
||||||
|
{
|
||||||
|
return @$_ENV['TEST_MYSQL_PASSWORD'] ?? 'root';
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,17 +1,39 @@
|
||||||
<?php
|
<?php
|
||||||
/* Destructr | https://gitlab.com/byjoby/destructr | MIT License */
|
/* Destructr | https://github.com/jobyone/destructr | MIT License */
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace Destructr\Drivers\MySQL;
|
namespace Destructr\Drivers\MySQL;
|
||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
use Destructr\Drivers\AbstractSQLDriverTest;
|
||||||
use Destructr\Drivers\AbstractDriverTest;
|
|
||||||
use Destructr\Drivers\MySQLDriver;
|
use Destructr\Drivers\MySQLDriver;
|
||||||
|
|
||||||
class MySQLDriverTest extends AbstractDriverTest
|
class MySQLDriverTest extends AbstractSQLDriverTest
|
||||||
{
|
{
|
||||||
const DRIVER_CLASS = MySQLDriver::class;
|
const DRIVER_CLASS = MySQLDriver::class;
|
||||||
const DRIVER_DSN = 'mysql:host=mysql;dbname=destructr_test';
|
|
||||||
const DRIVER_USERNAME = 'root';
|
protected static function DRIVER_DSN()
|
||||||
const DRIVER_PASSWORD = 'badpassword';
|
{
|
||||||
const DRIVER_OPTIONS = null;
|
return sprintf(
|
||||||
|
'mysql:host=%s:%s;dbname=%s',
|
||||||
|
$_ENV['TEST_MYSQL_SERVER'],
|
||||||
|
$_ENV['TEST_MYSQL_PORT'],
|
||||||
|
static::DRIVER_DBNAME()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static function DRIVER_DBNAME()
|
||||||
|
{
|
||||||
|
return @$_ENV['TEST_MYSQL_DBNAME'] ?? 'destructr_test';
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static function DRIVER_USERNAME()
|
||||||
|
{
|
||||||
|
return @$_ENV['TEST_MYSQL_USER'] ?? 'root';
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static function DRIVER_PASSWORD()
|
||||||
|
{
|
||||||
|
return @$_ENV['TEST_MYSQL_PASSWORD'] ?? 'root';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
34
tests/Drivers/SQLite/SQLiteDriverIntegrationTest.php
Normal file
34
tests/Drivers/SQLite/SQLiteDriverIntegrationTest.php
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
<?php
|
||||||
|
/* Destructr | https://github.com/jobyone/destructr | MIT License */
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Destructr\Drivers\SQLite;
|
||||||
|
|
||||||
|
use Destructr\Drivers\AbstractSQLDriverIntegrationTest;
|
||||||
|
use Destructr\Drivers\SQLiteDriver;
|
||||||
|
|
||||||
|
class SQLiteDriverIntegrationTest extends AbstractSQLDriverIntegrationTest
|
||||||
|
{
|
||||||
|
const DRIVER_CLASS = SQLiteDriver::class;
|
||||||
|
|
||||||
|
public static function setUpBeforeClass()
|
||||||
|
{
|
||||||
|
@unlink(__DIR__ . '/integration.test.sqlite');
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function DRIVER_DSN()
|
||||||
|
{
|
||||||
|
return 'sqlite:' . __DIR__ . '/integration.test.sqlite';
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static function DRIVER_DBNAME()
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static function DRIVER_USERNAME()
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
34
tests/Drivers/SQLite/SQLiteDriverSchemaChangeTest.php
Normal file
34
tests/Drivers/SQLite/SQLiteDriverSchemaChangeTest.php
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
<?php
|
||||||
|
/* Destructr | https://github.com/jobyone/destructr | MIT License */
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Destructr\Drivers\SQLite;
|
||||||
|
|
||||||
|
use Destructr\Drivers\AbstractSQLDriverSchemaChangeTest;
|
||||||
|
use Destructr\Drivers\SQLiteDriver;
|
||||||
|
|
||||||
|
class SQLiteDriverSchemaChangeTest extends AbstractSQLDriverSchemaChangeTest
|
||||||
|
{
|
||||||
|
const DRIVER_CLASS = SQLiteDriver::class;
|
||||||
|
|
||||||
|
public static function setUpBeforeClass()
|
||||||
|
{
|
||||||
|
@unlink(__DIR__ . '/schema.test.sqlite');
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function DRIVER_DSN()
|
||||||
|
{
|
||||||
|
return 'sqlite:' . __DIR__ . '/schema.test.sqlite';
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static function DRIVER_DBNAME()
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static function DRIVER_USERNAME()
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
34
tests/Drivers/SQLite/SQLiteDriverTest.php
Normal file
34
tests/Drivers/SQLite/SQLiteDriverTest.php
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
<?php
|
||||||
|
/* Destructr | https://github.com/jobyone/destructr | MIT License */
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Destructr\Drivers\SQLite;
|
||||||
|
|
||||||
|
use Destructr\Drivers\AbstractSQLDriverTest;
|
||||||
|
use Destructr\Drivers\SQLiteDriver;
|
||||||
|
|
||||||
|
class SQLiteDriverTest extends AbstractSQLDriverTest
|
||||||
|
{
|
||||||
|
const DRIVER_CLASS = SQLiteDriver::class;
|
||||||
|
|
||||||
|
public static function setUpBeforeClass()
|
||||||
|
{
|
||||||
|
@unlink(__DIR__ . '/driver.test.sqlite');
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function DRIVER_DSN()
|
||||||
|
{
|
||||||
|
return 'sqlite:' . __DIR__ . '/driver.test.sqlite';
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static function DRIVER_DBNAME()
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static function DRIVER_USERNAME()
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
<?php
|
<?php
|
||||||
/* Destructr | https://gitlab.com/byjoby/destructr | MIT License */
|
/* Destructr | https://github.com/jobyone/destructr | MIT License */
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
namespace Destructr;
|
namespace Destructr;
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
<?php
|
<?php
|
||||||
/* Destructr | https://gitlab.com/byjoby/destructr | MIT License */
|
/* Destructr | https://github.com/jobyone/destructr | MIT License */
|
||||||
namespace Destructr;
|
namespace Destructr;
|
||||||
|
|
||||||
class HarnessDriver implements Drivers\DSODriverInterface
|
class HarnessDriver extends Drivers\AbstractDriver
|
||||||
{
|
{
|
||||||
const EXTENSIBLE_VIRTUAL_COLUMNS = true;
|
const EXTENSIBLE_VIRTUAL_COLUMNS = true;
|
||||||
public $last_select;
|
public $last_select;
|
||||||
|
@ -10,11 +10,15 @@ class HarnessDriver implements Drivers\DSODriverInterface
|
||||||
public $last_update;
|
public $last_update;
|
||||||
public $last_delete;
|
public $last_delete;
|
||||||
|
|
||||||
public function __construct(string $dsn, string $username=null, string $password=null, array $options=null)
|
public function __construct(string $dsn=null, string $username=null, string $password=null, array $options=null)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public function createTable(string $table, array $virtualColumns) : bool
|
public function pdo(\PDO $pdo=null) : ?\PDO {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function prepareEnvironment(string $table, array $schema) : bool
|
||||||
{
|
{
|
||||||
//TODO: add tests for this too
|
//TODO: add tests for this too
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -1,23 +0,0 @@
|
||||||
<?php
|
|
||||||
/* Destructr | https://gitlab.com/byjoby/destructr | MIT License */
|
|
||||||
declare(strict_types=1);
|
|
||||||
namespace Destructr\LegacyDrivers\SQLite;
|
|
||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
|
||||||
use Destructr\Drivers\AbstractDriverIntegrationTest;
|
|
||||||
use Destructr\LegacyDrivers\SQLiteDriver;
|
|
||||||
|
|
||||||
class MySQLDriverTest extends AbstractDriverIntegrationTest
|
|
||||||
{
|
|
||||||
const DRIVER_CLASS = SQLiteDriver::class;
|
|
||||||
const DRIVER_DSN = 'sqlite:'.__DIR__.'/integration.test.sqlite';
|
|
||||||
const DRIVER_USERNAME = null;
|
|
||||||
const DRIVER_PASSWORD = null;
|
|
||||||
const DRIVER_OPTIONS = null;
|
|
||||||
const TEST_TABLE = 'sqliteintegrationtest';
|
|
||||||
|
|
||||||
public static function setUpBeforeClass()
|
|
||||||
{
|
|
||||||
@unlink(__DIR__.'/integration.test.sqlite');
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,22 +0,0 @@
|
||||||
<?php
|
|
||||||
/* Destructr | https://gitlab.com/byjoby/destructr | MIT License */
|
|
||||||
declare(strict_types=1);
|
|
||||||
namespace Destructr\LegacyDrivers\SQLite;
|
|
||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
|
||||||
use Destructr\Drivers\AbstractDriverTest;
|
|
||||||
use Destructr\LegacyDrivers\SQLiteDriver;
|
|
||||||
|
|
||||||
class SQLiteDriverTest extends AbstractDriverTest
|
|
||||||
{
|
|
||||||
const DRIVER_CLASS = SQLiteDriver::class;
|
|
||||||
const DRIVER_DSN = 'sqlite:'.__DIR__.'/driver.test.sqlite';
|
|
||||||
const DRIVER_USERNAME = null;
|
|
||||||
const DRIVER_PASSWORD = null;
|
|
||||||
const DRIVER_OPTIONS = null;
|
|
||||||
|
|
||||||
public static function setUpBeforeClass()
|
|
||||||
{
|
|
||||||
@unlink(__DIR__.'/driver.test.sqlite');
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in a new issue