Рефакторинг
This commit is contained in:
parent
1b5852cc43
commit
981a1d0f0f
11 changed files with 626 additions and 270 deletions
310
src/Database.php
310
src/Database.php
|
|
@ -1,23 +1,24 @@
|
||||||
<?php
|
<?php
|
||||||
|
///<reference path="database/pdostatement.php" />
|
||||||
|
require_once "database/pdostatement.php";
|
||||||
/**
|
/**
|
||||||
* @package system.db
|
|
||||||
* Класс оболочка для PDO для замены Creole
|
* Класс оболочка для PDO для замены Creole
|
||||||
*/
|
*/
|
||||||
class Database extends PDO
|
class Database extends PDO
|
||||||
{
|
{
|
||||||
public function __construct($dsn, $username = false, $password = false)
|
|
||||||
|
public $dsn;
|
||||||
|
public function __construct($dsn, $username = null, $password = null)
|
||||||
{
|
{
|
||||||
parent::__construct($dsn, $username, $password);
|
parent::__construct($dsn, $username, $password);
|
||||||
$this->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
$this->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||||
$this->setAttribute(PDO::ATTR_STATEMENT_CLASS, array('PDODatabaseStatement', array()));
|
$this->setAttribute(PDO::ATTR_STATEMENT_CLASS, array('Database_PDOStatement', array()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getDSN()
|
public function getDSN()
|
||||||
{
|
{
|
||||||
return $this->dsn;
|
return $this->dsn;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function isPostgres(){
|
public function isPostgres(){
|
||||||
return ($this->dsn["phptype"] == "pgsql");
|
return ($this->dsn["phptype"] == "pgsql");
|
||||||
}
|
}
|
||||||
|
|
@ -26,30 +27,43 @@ class Database extends PDO
|
||||||
*/
|
*/
|
||||||
static function getConnection(array $dsn)
|
static function getConnection(array $dsn)
|
||||||
{
|
{
|
||||||
|
|
||||||
if ($dsn['phptype'] == 'pgsql' || $dsn['phptype'] == 'mysql') {
|
if ($dsn['phptype'] == 'pgsql' || $dsn['phptype'] == 'mysql') {
|
||||||
$port = (isset($dsn['port'])) ? "port={$dsn['port']};" : "";
|
$port = (isset($dsn['port'])) ? "port={$dsn['port']};" : "";
|
||||||
$connection = new Database("{$dsn['phptype']}:host={$dsn['hostspec']}; $port dbname={$dsn['database']}", $dsn['username'], $dsn['password']);
|
/*.Database.*/$connection = new static("{$dsn['phptype']}:host={$dsn['hostspec']}; $port dbname={$dsn['database']}", $dsn['username'], $dsn['password']);
|
||||||
$connection->query('SET client_encoding = "UTF-8"');
|
if ($dsn['phptype'] == 'pgsql') {
|
||||||
|
$connection->query('SET client_encoding="UTF-8"');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if ($dsn['phptype'] == 'sqlite') {
|
if ($dsn['phptype'] == 'sqlite') {
|
||||||
$connection = new Database("{$dsn['phptype']}:{$dsn['database']}");
|
/*.Database.*/$connection = new static("{$dsn['phptype']}:{$dsn['database']}");
|
||||||
|
$connection->setAttribute(PDO::ATTR_TIMEOUT, 5);
|
||||||
|
$mode = defined('SQLITE_JOURNAL_MODE') ? SQLITE_JOURNAL_MODE : 'WAL';
|
||||||
|
$connection->query("PRAGMA journal_mode=$mode");
|
||||||
|
|
||||||
|
if(!function_exists('sqliteLower')){
|
||||||
|
function sqliteLower($str) {
|
||||||
|
return mb_strtolower($str, 'UTF-8');
|
||||||
|
}
|
||||||
|
$connection->sqliteCreateFunction('LOWER', 'sqliteLower', 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
$connection->dsn = $dsn;
|
$connection->dsn = $dsn;
|
||||||
return $connection;
|
return $connection;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function executeQuery($query)
|
public function executeQuery($query, $values=null)
|
||||||
{
|
{
|
||||||
$stmt = $this->prepare($query);
|
/*.Database_PDOStatement.*/$stmt = $this->prepare($query);
|
||||||
$stmt->setFetchMode(PDO::FETCH_ASSOC);
|
|
||||||
$stmt->execute();
|
$stmt->execute($values);
|
||||||
$stmt->cache = $stmt->fetchAll();
|
$stmt->cache = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
return $stmt;//$sth->fetchAll();
|
return $stmt;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function prepareStatement($query)
|
public function prepareStatement($query)
|
||||||
{
|
{
|
||||||
return new DatabaseStatement($query, $this);
|
return new Database_Statement($query, $this);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Для совместимости со старым представлением баз данных CIS
|
// Для совместимости со старым представлением баз данных CIS
|
||||||
|
|
@ -58,7 +72,7 @@ class Database extends PDO
|
||||||
*/
|
*/
|
||||||
public function fetchAllArray($query,$values=null)
|
public function fetchAllArray($query,$values=null)
|
||||||
{
|
{
|
||||||
$sth = $this->prepare($query);
|
/*.Database_PDOStatement.*/$sth = $this->prepare($query);
|
||||||
$prep = $this->prepareValues($values);
|
$prep = $this->prepareValues($values);
|
||||||
$sth->execute($prep);
|
$sth->execute($prep);
|
||||||
return $sth->fetchAll(PDO::FETCH_ASSOC);
|
return $sth->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
@ -69,13 +83,13 @@ class Database extends PDO
|
||||||
*/
|
*/
|
||||||
public function fetchOneArray($query,$values=null)
|
public function fetchOneArray($query,$values=null)
|
||||||
{
|
{
|
||||||
$sth = $this->prepare($query);
|
/*.Database_PDOStatement.*/$sth = $this->prepare($query);
|
||||||
$prep = $this->prepareValues($values);
|
$prep = $this->prepareValues($values);
|
||||||
$sth->execute($prep);
|
$sth->execute($prep);
|
||||||
return $sth->fetch(PDO::FETCH_ASSOC);
|
return $sth->fetch(PDO::FETCH_ASSOC);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function assignQuote($x, $y)
|
private static function assignQuote($x, $y)
|
||||||
{
|
{
|
||||||
return $x . "=" . $this->quote($y);
|
return $x . "=" . $this->quote($y);
|
||||||
}
|
}
|
||||||
|
|
@ -136,12 +150,17 @@ class Database extends PDO
|
||||||
*/
|
*/
|
||||||
function updateQuery($table, array $values, $cond)
|
function updateQuery($table, array $values, $cond)
|
||||||
{
|
{
|
||||||
return $this->query("UPDATE $table SET " . implode(",",
|
$prep = $this->prepareValues($values);
|
||||||
array_map(array($this, 'assignQuote'), array_keys($values), array_values($values))) . " WHERE $cond");
|
$sql = "UPDATE $table SET " . implode(",",
|
||||||
|
array_map(function($k,$v){return $k."=".$v;}, array_keys($values), array_keys($prep))) . " WHERE $cond";
|
||||||
|
|
||||||
|
$stmt = $this->prepare($sql);
|
||||||
|
$stmt->setFetchMode(PDO::FETCH_ASSOC);
|
||||||
|
$stmt->execute($prep);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getIdGenerator() {
|
function getIdGenerator() {
|
||||||
return new IdGenerator($this);
|
return new Database_IdGenerator($this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -149,8 +168,7 @@ class Database extends PDO
|
||||||
* @param string $seq Имя последовательности для ключа таблицы
|
* @param string $seq Имя последовательности для ключа таблицы
|
||||||
* @return int Идентефикатор следующей записи
|
* @return int Идентефикатор следующей записи
|
||||||
*/
|
*/
|
||||||
function getNextId($seq)
|
function getNextId($seq) {
|
||||||
{
|
|
||||||
$result = $this->fetchOneArray("SELECT nextval('$seq')");
|
$result = $this->fetchOneArray("SELECT nextval('$seq')");
|
||||||
return $result['nextval'];
|
return $result['nextval'];
|
||||||
}
|
}
|
||||||
|
|
@ -160,249 +178,3 @@ class Database extends PDO
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class IdGenerator {
|
|
||||||
private $db;
|
|
||||||
|
|
||||||
function __construct($db) {
|
|
||||||
$this->db = $db;
|
|
||||||
}
|
|
||||||
|
|
||||||
function isBeforeInsert() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function isAfterInsert() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getId($seq) {
|
|
||||||
$result = $this->db->fetchOneArray("SELECT nextval('$seq')");
|
|
||||||
return $result['nextval'];
|
|
||||||
// $result = $this->db->fetchOneArray("SELECT last_insert_rowid() AS nextval");
|
|
||||||
// return $result['nextval'];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class PDODatabaseStatementIterator implements Iterator
|
|
||||||
{
|
|
||||||
|
|
||||||
private $result;
|
|
||||||
private $pos = 0;
|
|
||||||
private $fetchmode;
|
|
||||||
private $row_count;
|
|
||||||
private $rs;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Construct the iterator.
|
|
||||||
* @param PgSQLResultSet $rs
|
|
||||||
*/
|
|
||||||
public function __construct($rs)
|
|
||||||
{
|
|
||||||
$this->result = $rs;
|
|
||||||
$this->row_count = $rs->getRecordCount();
|
|
||||||
}
|
|
||||||
|
|
||||||
function rewind()
|
|
||||||
{
|
|
||||||
$this->pos = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
function valid()
|
|
||||||
{
|
|
||||||
return ($this->pos < $this->row_count);
|
|
||||||
}
|
|
||||||
|
|
||||||
function key()
|
|
||||||
{
|
|
||||||
return $this->pos;
|
|
||||||
}
|
|
||||||
|
|
||||||
function current()
|
|
||||||
{
|
|
||||||
if (!isset($this->result->cache[$this->pos])) {
|
|
||||||
$this->result->cache[$this->pos] = $this->result->fetch(PDO::FETCH_ASSOC);
|
|
||||||
}
|
|
||||||
return $this->result->cache[$this->pos];
|
|
||||||
}
|
|
||||||
|
|
||||||
function next()
|
|
||||||
{
|
|
||||||
$this->pos++;
|
|
||||||
}
|
|
||||||
|
|
||||||
function seek ( $index )
|
|
||||||
{
|
|
||||||
$this->pos = $index;
|
|
||||||
}
|
|
||||||
|
|
||||||
function count ( ) {
|
|
||||||
return $this->row_count;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class PDODatabaseStatement extends PDOStatement implements IteratorAggregate
|
|
||||||
{
|
|
||||||
protected $cursorPos = 0;
|
|
||||||
public $cache = array();
|
|
||||||
public $fields;
|
|
||||||
|
|
||||||
function getIterator()
|
|
||||||
{
|
|
||||||
return new PDODatabaseStatementIterator($this);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function __construct() {
|
|
||||||
}
|
|
||||||
|
|
||||||
function rewind()
|
|
||||||
{
|
|
||||||
$this->cursorPos = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function seek($rownum)
|
|
||||||
{
|
|
||||||
if ($rownum < 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// PostgreSQL rows start w/ 0, but this works, because we are
|
|
||||||
// looking to move the position _before_ the next desired position
|
|
||||||
$this->cursorPos = $rownum;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
function valid()
|
|
||||||
{
|
|
||||||
return ( true );
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public function first()
|
|
||||||
{
|
|
||||||
if($this->cursorPos !== 0) { $this->seek(0); }
|
|
||||||
return $this->next();
|
|
||||||
}
|
|
||||||
|
|
||||||
function next()
|
|
||||||
{
|
|
||||||
if ($this->getRecordCount() > $this->cursorPos) {
|
|
||||||
if (!isset($this->cache[$this->cursorPos])) {
|
|
||||||
$this->cache[$this->cursorPos] = $this->fetch(PDO::FETCH_ASSOC);
|
|
||||||
}
|
|
||||||
$this->fields = $this->cache[$this->cursorPos];
|
|
||||||
|
|
||||||
$this->cursorPos++;
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
$this->fields = null;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function key() {
|
|
||||||
return $this->cursorPos;
|
|
||||||
}
|
|
||||||
|
|
||||||
function current()
|
|
||||||
{
|
|
||||||
return $this->result->fetch(PDO::FETCH_ASSOC);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getRow()
|
|
||||||
{
|
|
||||||
return $this->fields;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getInt($name)
|
|
||||||
{
|
|
||||||
return intval($this->fields[$name]);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getBlob($name)
|
|
||||||
{
|
|
||||||
return $this->fields[$name];
|
|
||||||
}
|
|
||||||
|
|
||||||
function getString($name)
|
|
||||||
{
|
|
||||||
return $this->fields[$name];
|
|
||||||
}
|
|
||||||
|
|
||||||
function getBoolean($name)
|
|
||||||
{
|
|
||||||
return (bool)$this->fields[$name];
|
|
||||||
}
|
|
||||||
|
|
||||||
function get($name)
|
|
||||||
{
|
|
||||||
return $this->fields[$name];
|
|
||||||
}
|
|
||||||
|
|
||||||
function getRecordCount()
|
|
||||||
{
|
|
||||||
return count($this->cache);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Класс оболочка для PDOStatement для замены Creole
|
|
||||||
*/
|
|
||||||
class DatabaseStatement
|
|
||||||
{
|
|
||||||
protected $limit = null;
|
|
||||||
protected $offset = null;
|
|
||||||
protected $statement = null;
|
|
||||||
protected $binds = array();
|
|
||||||
protected $conn;
|
|
||||||
protected $query;
|
|
||||||
|
|
||||||
function __construct($query, $conn) {
|
|
||||||
$this->query = $query;
|
|
||||||
$this->conn = $conn;
|
|
||||||
}
|
|
||||||
|
|
||||||
function setInt($n, $value)
|
|
||||||
{
|
|
||||||
$this->binds [] = array($n, $value, PDO::PARAM_INT);
|
|
||||||
}
|
|
||||||
|
|
||||||
function setString($n, $value)
|
|
||||||
{
|
|
||||||
$this->binds [] = array($n, $value, PDO::PARAM_STR);
|
|
||||||
}
|
|
||||||
|
|
||||||
function setBlob($n, $value)
|
|
||||||
{
|
|
||||||
$this->binds [] = array($n, $value, PDO::PARAM_LOB);
|
|
||||||
}
|
|
||||||
|
|
||||||
function setLimit($limit)
|
|
||||||
{
|
|
||||||
$this->limit = $limit;
|
|
||||||
}
|
|
||||||
|
|
||||||
function setOffset($offset)
|
|
||||||
{
|
|
||||||
$this->offset = $offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
function executeQuery()
|
|
||||||
{
|
|
||||||
if ($this->limit) {
|
|
||||||
$this->query .= " LIMIT {$this->limit} OFFSET {$this->offset}";
|
|
||||||
}
|
|
||||||
$stmt = $this->conn->prepare($this->query);
|
|
||||||
foreach ($this->binds as $bind) {
|
|
||||||
list($n, $value, $type) = $bind;
|
|
||||||
$stmt->bindValue($n, $value, $type);
|
|
||||||
}
|
|
||||||
$stmt->setFetchMode(PDO::FETCH_ASSOC);
|
|
||||||
$stmt->execute();
|
|
||||||
$stmt->cache = $stmt->fetchAll();
|
|
||||||
|
|
||||||
return $stmt;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
26
src/Database/IdGenerator.php
Normal file
26
src/Database/IdGenerator.php
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
class Database_IdGenerator {
|
||||||
|
private $db;
|
||||||
|
|
||||||
|
function __construct(Database $db) {
|
||||||
|
$this->db = $db;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isBeforeInsert() {
|
||||||
|
return $this->db->isPostgres();
|
||||||
|
}
|
||||||
|
|
||||||
|
function isAfterInsert() {
|
||||||
|
return !$this->db->isPostgres();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getId($seq) {
|
||||||
|
if ($this->db->isPostgres()) {
|
||||||
|
$result = $this->db->fetchOneArray("SELECT nextval('$seq') AS nextval");
|
||||||
|
} else {
|
||||||
|
$result = $this->db->fetchOneArray("SELECT last_insert_rowid() AS nextval");
|
||||||
|
}
|
||||||
|
return intval($result['nextval']);
|
||||||
|
}
|
||||||
|
}
|
||||||
143
src/Database/JsonInstall.php
Normal file
143
src/Database/JsonInstall.php
Normal file
|
|
@ -0,0 +1,143 @@
|
||||||
|
<?php
|
||||||
|
//Действия с базой данных согласно json файлу.
|
||||||
|
|
||||||
|
class Database_JsonInstall {
|
||||||
|
public $db_manager;
|
||||||
|
public $serialColumns;
|
||||||
|
|
||||||
|
public function __construct(Database_Manager $db_manager) {
|
||||||
|
$this->db_manager = $db_manager;
|
||||||
|
}
|
||||||
|
|
||||||
|
function install($dbinit_path, $dbfill_path = null) {
|
||||||
|
$dbinit_file = file_get_contents($dbinit_path);
|
||||||
|
if (is_string($dbinit_file)) {
|
||||||
|
$initActions = json_decode($dbinit_file, true);
|
||||||
|
if (!$initActions) {
|
||||||
|
echo "Invalid dbinit.json ".$dbinit_file;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
echo "No dbinit.json";
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->initDataBase($initActions, $dbinit_path);
|
||||||
|
if ($dbfill_path) {
|
||||||
|
$this->fillDataBase($dbfill_path);
|
||||||
|
}
|
||||||
|
$this->makeConstraints($initActions);
|
||||||
|
}
|
||||||
|
|
||||||
|
function missingTables($tables) {
|
||||||
|
$actual_tables = $this->db_manager->GetAllTableNames();
|
||||||
|
$missingTables = [];
|
||||||
|
foreach ($tables as $table) {
|
||||||
|
if (!in_array($table, $actual_tables))
|
||||||
|
$missingTables[] = $table;
|
||||||
|
}
|
||||||
|
return $missingTables;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Создать таблицы
|
||||||
|
function initDataBase(/*.array.*/$initActions, $dbinit_path) {
|
||||||
|
$pg = $this->db_manager->db->isPostgres();
|
||||||
|
if (!$pg) {
|
||||||
|
$refs = [];
|
||||||
|
//В sqlite нет alter reference. Референсы надо создавать при создании таблицы.
|
||||||
|
foreach ($initActions as $action) {
|
||||||
|
if ($action["type"] == "alterReference") {
|
||||||
|
if (!isset($refs[$action["table"]]))
|
||||||
|
$refs[$action["table"]] = [];
|
||||||
|
$refs[$action["table"]][]=$action;//добавить к списку референсов для таблицы
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($initActions as $action) {
|
||||||
|
if (!$pg) {
|
||||||
|
if ($action["type"] == "createTable") {
|
||||||
|
$table_name = $action["table_name"];
|
||||||
|
if (isset($refs[$table_name])) {
|
||||||
|
foreach ($refs[$table_name] as $value) {
|
||||||
|
$action['fields'][$value['column']]['references'] =
|
||||||
|
$value['refTable']."(".$value['refColumn'].")";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($action["type"] != "alterReference") {
|
||||||
|
$this->db_manager->ExecuteAction($action, $dbinit_path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Запомнить все колонки serial
|
||||||
|
$this->serialColumns = [];
|
||||||
|
if ($pg) {
|
||||||
|
foreach ($initActions as $action) {
|
||||||
|
if ($action["type"] == "createTable") {
|
||||||
|
foreach ($action["fields"] as $name => $field) {
|
||||||
|
if ($field["type"]=="serial") {
|
||||||
|
$this->serialColumns[] = [
|
||||||
|
"table"=>$action["table_name"],
|
||||||
|
"column"=>$name
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Заполнить данными
|
||||||
|
function fillDataBase($dbfill_file_path) {
|
||||||
|
$dbfill_file = file_get_contents($dbfill_file_path);
|
||||||
|
if (is_string($dbfill_file)) {
|
||||||
|
$actions = json_decode($dbfill_file,true);
|
||||||
|
if ($actions) {
|
||||||
|
|
||||||
|
//Проверка что упоминаемые в списке действий таблицы уже есть в базе
|
||||||
|
$affected_tables = [];
|
||||||
|
foreach ($actions as $action) {
|
||||||
|
if ($action["table_name"]) {
|
||||||
|
$affected_tables[$action["table_name"]] = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$missing = $this->missingTables(array_keys($affected_tables));
|
||||||
|
if (!empty($missing)) {
|
||||||
|
echo "dbfill error. Missing tables: ".implode(" ", $missing);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Выполнение действий
|
||||||
|
foreach ($actions as $action) {
|
||||||
|
$this->db_manager->ExecuteAction($action, $dbfill_file_path);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
echo "Invalid dbfill.json";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
echo "No dbfill.json";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Обновить ключи serial и создать ограничения
|
||||||
|
function makeConstraints($initActions) {
|
||||||
|
$pg = $this->db_manager->db->isPostgres();
|
||||||
|
if ($pg) {
|
||||||
|
foreach ($this->serialColumns as $serialColumn) {
|
||||||
|
$this->db_manager->UpdateSerial($serialColumn["table"], $serialColumn["column"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
foreach ($initActions as $action) {
|
||||||
|
if ($action["type"] == "alterReference") {
|
||||||
|
$this->db_manager->ExecuteAction($action);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
211
src/Database/Manager.php
Normal file
211
src/Database/Manager.php
Normal file
|
|
@ -0,0 +1,211 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
class Database_Manager
|
||||||
|
{
|
||||||
|
public /*.Database.*/$db;
|
||||||
|
|
||||||
|
function __construct(Database $db) {
|
||||||
|
$this->db = $db;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function ExecuteAction(/*.array.*/$action, $db_file = "") {
|
||||||
|
switch($action["type"]) {
|
||||||
|
case "dropTable":
|
||||||
|
$this->DropTableQuery($action["table_name"], true);
|
||||||
|
break;
|
||||||
|
case "createTable":
|
||||||
|
$constraints = isset($action["constraints"]) ? $action["constraints"] : NULL;
|
||||||
|
$this->CreateTableQuery($action["table_name"], $action["fields"], $constraints);
|
||||||
|
break;
|
||||||
|
case "addColumn":
|
||||||
|
$this->AddColumn($action["table_name"], $action["column_name"], $action["field"]);
|
||||||
|
break;
|
||||||
|
case "insert":
|
||||||
|
$this->db->insertQuery($action["table_name"], $action["values"]);
|
||||||
|
break;
|
||||||
|
case "alterReference":
|
||||||
|
$this->AlterReference($action["table"], $action["column"], $action["refTable"], $action["refColumn"]);
|
||||||
|
break;
|
||||||
|
case "renameColumn":
|
||||||
|
$this->RenameColumn($action["table"], $action["old_name"], $action["new_name"]);
|
||||||
|
break;
|
||||||
|
case "executeFile":
|
||||||
|
if ($this->db->isPostgres() && isset($action["pgsql"])) {
|
||||||
|
$file = $action["pgsql"];
|
||||||
|
} else {
|
||||||
|
$file = $action["source"];
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmtList = Tools_SQLStatementExtractor::extractFile(Path::join(dirname($db_file), $file));
|
||||||
|
foreach($stmtList as $stmt) {
|
||||||
|
$this->db->executeQuery($stmt);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Exception("unknown action ". $action["type"] . PHP_EOL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function DropTableQuery($table, $cascade=false) {
|
||||||
|
$statement = "DROP TABLE IF EXISTS ".$table;
|
||||||
|
if ($this->db->isPostgres()&&$cascade) {
|
||||||
|
$statement = $statement." CASCADE";
|
||||||
|
}
|
||||||
|
$this->db->query($statement);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function AlterReference($table,$column,$refTable,$refColumn) {
|
||||||
|
$this->db->query("ALTER TABLE ".$table." ADD CONSTRAINT ".$table."_".$column."fk"." FOREIGN KEY (".$column.") REFERENCES ".$refTable." (".$refColumn.")");
|
||||||
|
}
|
||||||
|
|
||||||
|
//Извлечение информации о полях таблицы
|
||||||
|
public function TableInfo($table) {
|
||||||
|
$pg = $this->db->isPostgres();
|
||||||
|
if ($pg) {
|
||||||
|
throw new Exception("Not implemented for postgres");
|
||||||
|
} else {
|
||||||
|
$results = $this->db->fetchAllArray("PRAGMA table_info(".$table.");");
|
||||||
|
if (empty($results)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
$fields = [];
|
||||||
|
foreach ($results as $result) {
|
||||||
|
$fields[$result["name"]] = [
|
||||||
|
"type"=> $result["type"],
|
||||||
|
"not_null"=> boolval($result["notnull"]),
|
||||||
|
"constraint"=> ((boolean) $result["pk"]) ? "PRIMARY KEY" : null
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return $fields;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function RenameColumn($table, $old_name, $new_name) {
|
||||||
|
$pg = $this->db->isPostgres();
|
||||||
|
if ($pg) {
|
||||||
|
$this->db->query("ALTER TABLE ".$table." RENAME COLUMN ".$old_name." TO ".$new_name);
|
||||||
|
} else {
|
||||||
|
$tmp_table = "tmp_" . $table;
|
||||||
|
$this->DropTableQuery($tmp_table);
|
||||||
|
$table_info = $this->TableInfo($table);
|
||||||
|
|
||||||
|
if (isset($table_info[$new_name])) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*.array.*/$data = $this->DumpTable($table);
|
||||||
|
|
||||||
|
$this->db->query("ALTER TABLE ".$table." RENAME TO ".$tmp_table.";");
|
||||||
|
$table_info[$new_name] = $table_info[$old_name];
|
||||||
|
unset($table_info[$old_name]);
|
||||||
|
$this->CreateTableQuery($table,$table_info,null);
|
||||||
|
|
||||||
|
foreach ($data as $row) {
|
||||||
|
$values = $row['values'];
|
||||||
|
$values[$new_name] = $values[$old_name];
|
||||||
|
unset($values[$old_name]);
|
||||||
|
$this->db->insertQuery($table, $values);
|
||||||
|
}
|
||||||
|
$this->DropTableQuery($tmp_table);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Обновление ключа serial после ручной вставки
|
||||||
|
public function UpdateSerial($table,$column) {
|
||||||
|
$this->db->query("SELECT setval(pg_get_serial_sequence('".$table."', '".$column."'), coalesce(max(".$column."),0) + 1, false) FROM ".$table);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function Column_Definition($name,$data,$pg){
|
||||||
|
$constraint = isset($data['constraint'])?" ".$data['constraint']:"";
|
||||||
|
$references = "";
|
||||||
|
if (isset($data['references'])) {
|
||||||
|
$references = " REFERENCES ".$data['references'];
|
||||||
|
}
|
||||||
|
if (isset($data["not_null"])&&$data["not_null"])
|
||||||
|
$constraint .=" NOT NULL";
|
||||||
|
$type = $data['type'];
|
||||||
|
if (!$pg) {
|
||||||
|
if (strtolower($type)=="serial")
|
||||||
|
$type = "integer";
|
||||||
|
//if (strtolower($type)=="boolean")
|
||||||
|
// $type = "integer";
|
||||||
|
}
|
||||||
|
return $name." ".$type.$references.$constraint;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function AddColumn($table_name,$column_name,$field){
|
||||||
|
$pg = $this->db->isPostgres();
|
||||||
|
$q = "ALTER TABLE ".$table_name." ADD COLUMN ".
|
||||||
|
$this->Column_Definition($column_name, $field, $pg);
|
||||||
|
$this->db->query($q);
|
||||||
|
}
|
||||||
|
|
||||||
|
//CreateTableQuery('users',['id'=>['type'=>'integer','constraint'=>'PRIMARY KEY']])
|
||||||
|
public function CreateTableQuery($table, $fields, $constraints) {
|
||||||
|
$pg = $this->db->isPostgres();
|
||||||
|
if ($constraints) {
|
||||||
|
$constraints = ", " . $constraints;
|
||||||
|
}
|
||||||
|
|
||||||
|
$statement = "CREATE TABLE $table (" . implode(",",
|
||||||
|
array_map(function($name,$data) use ($pg) {
|
||||||
|
return $this->Column_Definition($name,$data,$pg);
|
||||||
|
}, array_keys($fields), array_values($fields))
|
||||||
|
) . " " . $constraints . ")";
|
||||||
|
$this->db->query($statement);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function DumpTable($table_name) {
|
||||||
|
$pg = $this->db->isPostgres();
|
||||||
|
|
||||||
|
/*.array.*/$result = array();
|
||||||
|
/*.array.*/$data = $this->db->fetchAllArray("SELECT * FROM ".$table_name.";");
|
||||||
|
|
||||||
|
if (!$pg) {
|
||||||
|
$table_fields = $this->TableInfo($table_name);
|
||||||
|
foreach ($table_fields as $name => $value) {
|
||||||
|
$type = strtolower($value['type']);
|
||||||
|
if ($type == "boolean") {
|
||||||
|
foreach ($data as &$row) {
|
||||||
|
/*.array.*/$row = $row;
|
||||||
|
if (isset($row[$name])) {
|
||||||
|
$row[$name] = boolval($row[$name]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
foreach ($data as $r) {
|
||||||
|
$result[] = array(
|
||||||
|
"type" => "insert",
|
||||||
|
"table_name" => $table_name,
|
||||||
|
"values" => $r
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function GetAllTableNames() {
|
||||||
|
$result = [];
|
||||||
|
if ($this->db->isPostgres()) {
|
||||||
|
$query = "SELECT table_name as name FROM information_schema.tables WHERE table_schema='public'";
|
||||||
|
} else {
|
||||||
|
$query = "SELECT * FROM sqlite_master WHERE type='table'";
|
||||||
|
}
|
||||||
|
$tables = $this->db->fetchAllArray($query);
|
||||||
|
foreach ($tables as $table) {
|
||||||
|
$result[] = $table['name'];
|
||||||
|
}
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function DumpInserts() {
|
||||||
|
$table_names = $this->GetAllTableNames();
|
||||||
|
$result = array();
|
||||||
|
foreach ($table_names as $table_name) {
|
||||||
|
$result = array_merge($result, $this->DumpTable($table_name));
|
||||||
|
}
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
}
|
||||||
97
src/Database/PDOStatement.php
Normal file
97
src/Database/PDOStatement.php
Normal file
|
|
@ -0,0 +1,97 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
require_once 'tools/string.php';
|
||||||
|
|
||||||
|
class Database_PDOStatement extends PDOStatement implements IteratorAggregate
|
||||||
|
{
|
||||||
|
protected $cursorPos = 0;
|
||||||
|
public $cache = array();
|
||||||
|
public $fields;
|
||||||
|
|
||||||
|
function getIterator() {
|
||||||
|
return new Database_StatementIterator($this);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function __construct() {
|
||||||
|
}
|
||||||
|
|
||||||
|
function rewind() {
|
||||||
|
$this->cursorPos = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function seek($rownum) {
|
||||||
|
if ($rownum < 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// PostgreSQL rows start w/ 0, but this works, because we are
|
||||||
|
// looking to move the position _before_ the next desired position
|
||||||
|
$this->cursorPos = $rownum;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function valid() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function first() {
|
||||||
|
if($this->cursorPos !== 0) { $this->seek(0); }
|
||||||
|
return $this->next();
|
||||||
|
}
|
||||||
|
|
||||||
|
function next() {
|
||||||
|
if ($this->getRecordCount() > $this->cursorPos) {
|
||||||
|
if (!isset($this->cache[$this->cursorPos])) {
|
||||||
|
$this->cache[$this->cursorPos] = $this->fetch(PDO::FETCH_ASSOC);
|
||||||
|
}
|
||||||
|
$this->fields = $this->cache[$this->cursorPos];
|
||||||
|
|
||||||
|
$this->cursorPos++;
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
$this->fields = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function key() {
|
||||||
|
return $this->cursorPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
function current() {
|
||||||
|
return $this->cache[$this->cursorPos];
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRow() {
|
||||||
|
return $this->fields;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getInt($name) {
|
||||||
|
return intval($this->fields[$name]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getBlob($name) {
|
||||||
|
return $this->fields[$name];
|
||||||
|
}
|
||||||
|
|
||||||
|
function getString($name) {
|
||||||
|
return $this->fields[$name];
|
||||||
|
}
|
||||||
|
|
||||||
|
function getBoolean($name) {
|
||||||
|
return (bool)$this->fields[$name];
|
||||||
|
}
|
||||||
|
|
||||||
|
function get($name) {
|
||||||
|
return $this->fields[$name];
|
||||||
|
}
|
||||||
|
|
||||||
|
function getArray($name) {
|
||||||
|
return strToArray($this->fields[$name]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRecordCount() {
|
||||||
|
return count($this->cache);
|
||||||
|
}
|
||||||
|
}
|
||||||
61
src/Database/Statement.php
Normal file
61
src/Database/Statement.php
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Класс оболочка для PDOStatement для замены Creole
|
||||||
|
*/
|
||||||
|
class Database_Statement
|
||||||
|
{
|
||||||
|
protected $limit = null;
|
||||||
|
protected $offset = null;
|
||||||
|
protected $statement = null;
|
||||||
|
protected $binds = array();
|
||||||
|
protected $conn;
|
||||||
|
protected $query;
|
||||||
|
|
||||||
|
function __construct($query, /*.Database.*/ $conn) {
|
||||||
|
$this->query = $query;
|
||||||
|
$this->conn = $conn;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setInt($n, $value)
|
||||||
|
{
|
||||||
|
$this->binds [] = array($n, $value, PDO::PARAM_INT);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setString($n, $value)
|
||||||
|
{
|
||||||
|
$this->binds [] = array($n, $value, PDO::PARAM_STR);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setBlob($n, $value)
|
||||||
|
{
|
||||||
|
$this->binds [] = array($n, $value, PDO::PARAM_LOB);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setLimit($limit)
|
||||||
|
{
|
||||||
|
$this->limit = $limit;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setOffset($offset)
|
||||||
|
{
|
||||||
|
$this->offset = $offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
function executeQuery()
|
||||||
|
{
|
||||||
|
if ($this->limit) {
|
||||||
|
$this->query .= " LIMIT {$this->limit} OFFSET {$this->offset}";
|
||||||
|
}
|
||||||
|
/*.Database_PDOStatement.*/$stmt = $this->conn->prepare($this->query);
|
||||||
|
foreach ($this->binds as $bind) {
|
||||||
|
list($n, $value, $type) = $bind;
|
||||||
|
$stmt->bindValue($n, $value, (int) $type);
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt->execute();
|
||||||
|
$stmt->cache = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
return $stmt;
|
||||||
|
}
|
||||||
|
}
|
||||||
46
src/Database/StatementIterator.php
Normal file
46
src/Database/StatementIterator.php
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
class Database_StatementIterator implements Iterator
|
||||||
|
{
|
||||||
|
|
||||||
|
private $result;
|
||||||
|
private $pos = 0;
|
||||||
|
private $fetchmode;
|
||||||
|
private $row_count;
|
||||||
|
|
||||||
|
public function __construct(/*.Database_PDOStatement.*/ $rs) {
|
||||||
|
$this->result = $rs;
|
||||||
|
$this->row_count = $rs->getRecordCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
function rewind() {
|
||||||
|
$this->pos = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function valid() {
|
||||||
|
return ($this->pos < $this->row_count);
|
||||||
|
}
|
||||||
|
|
||||||
|
function key() {
|
||||||
|
return $this->pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
function current() {
|
||||||
|
if (!isset($this->result->cache[$this->pos])) {
|
||||||
|
$this->result->cache[$this->pos] = $this->result->fetch(PDO::FETCH_ASSOC);
|
||||||
|
}
|
||||||
|
return $this->result->cache[$this->pos];
|
||||||
|
}
|
||||||
|
|
||||||
|
function next() {
|
||||||
|
$this->pos++;
|
||||||
|
}
|
||||||
|
|
||||||
|
function seek($index) {
|
||||||
|
$this->pos = $index;
|
||||||
|
}
|
||||||
|
|
||||||
|
function count() {
|
||||||
|
return $this->row_count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class Validator_Rule_Notnull extends Rule_Abstract
|
class Validator_Rule_Notnull extends Validator_Rule_Abstract
|
||||||
{
|
{
|
||||||
function skipEmpty() {
|
function skipEmpty() {
|
||||||
return false;
|
return false;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue