Begin Refactor

This commit is contained in:
color.diff=auto
2021-03-21 13:47:29 -06:00
parent 7622dc0d02
commit 018931d7ae
197 changed files with 47799 additions and 6 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,364 @@
<?php
namespace RedBeanPHP\QueryWriter;
use RedBeanPHP\QueryWriter\AQueryWriter as AQueryWriter;
use RedBeanPHP\QueryWriter as QueryWriter;
use RedBeanPHP\Adapter\DBAdapter as DBAdapter;
use RedBeanPHP\Adapter as Adapter;
use RedBeanPHP\RedException\SQL as SQLException;
/**
* RedBeanPHP CUBRID Writer.
* This is a QueryWriter class for RedBeanPHP.
* This QueryWriter provides support for the CUBRID database platform.
*
* @file RedBeanPHP/QueryWriter/CUBRID.php
* @author Gabor de Mooij and the RedBeanPHP Community
* @license BSD/GPLv2
*
* @copyright
* (c) copyright G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class CUBRID extends AQueryWriter implements QueryWriter
{
/**
* Data types
*/
const C_DATATYPE_INTEGER = 0;
const C_DATATYPE_DOUBLE = 1;
const C_DATATYPE_STRING = 2;
const C_DATATYPE_SPECIAL_DATE = 80;
const C_DATATYPE_SPECIAL_DATETIME = 81;
const C_DATATYPE_SPECIFIED = 99;
/**
* @var DBAdapter
*/
protected $adapter;
/**
* @var string
*/
protected $quoteCharacter = '`';
/**
* This method adds a foreign key from type and field to
* target type and target field.
* The foreign key is created without an action. On delete/update
* no action will be triggered. The FK is only used to allow database
* tools to generate pretty diagrams and to make it easy to add actions
* later on.
* This methods accepts a type and infers the corresponding table name.
*
* @param string $type type that will have a foreign key field
* @param string $targetType points to this type
* @param string $property field that contains the foreign key value
* @param string $targetProperty field where the fk points to
* @param boolean $isDep is dependent
*
* @return bool
*/
protected function buildFK( $type, $targetType, $property, $targetProperty, $isDep = FALSE )
{
$table = $this->esc( $type );
$tableNoQ = $this->esc( $type, TRUE );
$targetTable = $this->esc( $targetType );
$targetTableNoQ = $this->esc( $targetType, TRUE );
$column = $this->esc( $property );
$columnNoQ = $this->esc( $property, TRUE );
$targetColumn = $this->esc( $targetProperty );
if ( !is_null( $this->getForeignKeyForTypeProperty( $tableNoQ, $columnNoQ ) ) ) return FALSE;
$needsToDropFK = FALSE;
$casc = ( $isDep ? 'CASCADE' : 'SET NULL' );
$sql = "ALTER TABLE $table ADD CONSTRAINT FOREIGN KEY($column) REFERENCES $targetTable($targetColumn) ON DELETE $casc ";
try {
$this->adapter->exec( $sql );
} catch( SQLException $e ) {
return FALSE;
}
return TRUE;
}
/**
* @see AQueryWriter::getKeyMapForType
*/
protected function getKeyMapForType( $type )
{
$sqlCode = $this->adapter->get("SHOW CREATE TABLE `{$type}`");
if (!isset($sqlCode[0])) return array();
$matches = array();
preg_match_all( '/CONSTRAINT\s+\[([\w_]+)\]\s+FOREIGN\s+KEY\s+\(\[([\w_]+)\]\)\s+REFERENCES\s+\[([\w_]+)\](\s+ON\s+DELETE\s+(CASCADE|SET\sNULL|RESTRICT|NO\sACTION)\s+ON\s+UPDATE\s+(SET\sNULL|RESTRICT|NO\sACTION))?/', $sqlCode[0]['CREATE TABLE'], $matches );
$list = array();
if (!isset($matches[0])) return $list;
$max = count($matches[0]);
for($i = 0; $i < $max; $i++) {
$label = $this->makeFKLabel( $matches[2][$i], $matches[3][$i], 'id' );
$list[ $label ] = array(
'name' => $matches[1][$i],
'from' => $matches[2][$i],
'table' => $matches[3][$i],
'to' => 'id',
'on_update' => $matches[6][$i],
'on_delete' => $matches[5][$i]
);
}
return $list;
}
/**
* Constructor
* Most of the time, you do not need to use this constructor,
* since the facade takes care of constructing and wiring the
* RedBeanPHP core objects. However if you would like to
* assemble an OODB instance yourself, this is how it works:
*
* Usage:
*
* <code>
* $database = new RPDO( $dsn, $user, $pass );
* $adapter = new DBAdapter( $database );
* $writer = new PostgresWriter( $adapter );
* $oodb = new OODB( $writer, FALSE );
* $bean = $oodb->dispense( 'bean' );
* $bean->name = 'coffeeBean';
* $id = $oodb->store( $bean );
* $bean = $oodb->load( 'bean', $id );
* </code>
*
* The example above creates the 3 RedBeanPHP core objects:
* the Adapter, the Query Writer and the OODB instance and
* wires them together. The example also demonstrates some of
* the methods that can be used with OODB, as you see, they
* closely resemble their facade counterparts.
*
* The wiring process: create an RPDO instance using your database
* connection parameters. Create a database adapter from the RPDO
* object and pass that to the constructor of the writer. Next,
* create an OODB instance from the writer. Now you have an OODB
* object.
*
* @param Adapter $adapter Database Adapter
*/
public function __construct( Adapter $adapter )
{
$this->typeno_sqltype = array(
CUBRID::C_DATATYPE_INTEGER => ' INTEGER ',
CUBRID::C_DATATYPE_DOUBLE => ' DOUBLE ',
CUBRID::C_DATATYPE_STRING => ' STRING ',
CUBRID::C_DATATYPE_SPECIAL_DATE => ' DATE ',
CUBRID::C_DATATYPE_SPECIAL_DATETIME => ' DATETIME ',
);
$this->sqltype_typeno = array();
foreach ( $this->typeno_sqltype as $k => $v ) {
$this->sqltype_typeno[trim( ( $v ) )] = $k;
}
$this->sqltype_typeno['STRING(1073741823)'] = self::C_DATATYPE_STRING;
$this->adapter = $adapter;
}
/**
* This method returns the datatype to be used for primary key IDS and
* foreign keys. Returns one if the data type constants.
*
* @return integer
*/
public function getTypeForID()
{
return self::C_DATATYPE_INTEGER;
}
/**
* @see QueryWriter::getTables
*/
public function getTables()
{
$rows = $this->adapter->getCol( "SELECT class_name FROM db_class WHERE is_system_class = 'NO';" );
return $rows;
}
/**
* @see QueryWriter::createTable
*/
public function createTable( $table )
{
$sql = 'CREATE TABLE '
. $this->esc( $table )
. ' ("id" integer AUTO_INCREMENT, CONSTRAINT "pk_'
. $this->esc( $table, TRUE )
. '_id" PRIMARY KEY("id"))';
$this->adapter->exec( $sql );
}
/**
* @see QueryWriter::getColumns
*/
public function getColumns( $table )
{
$table = $this->esc( $table );
$columnsRaw = $this->adapter->get( "SHOW COLUMNS FROM $table" );
$columns = array();
foreach ( $columnsRaw as $r ) {
$columns[$r['Field']] = $r['Type'];
}
return $columns;
}
/**
* @see QueryWriter::scanType
*/
public function scanType( $value, $flagSpecial = FALSE )
{
$this->svalue = $value;
if ( is_null( $value ) ) {
return self::C_DATATYPE_INTEGER;
}
if ( $flagSpecial ) {
if ( preg_match( '/^\d{4}\-\d\d-\d\d$/', $value ) ) {
return self::C_DATATYPE_SPECIAL_DATE;
}
if ( preg_match( '/^\d{4}\-\d\d-\d\d\s\d\d:\d\d:\d\d$/', $value ) ) {
return self::C_DATATYPE_SPECIAL_DATETIME;
}
}
$value = strval( $value );
if ( !$this->startsWithZeros( $value ) ) {
if ( is_numeric( $value ) && ( floor( $value ) == $value ) && $value >= -2147483647 && $value <= 2147483647 ) {
return self::C_DATATYPE_INTEGER;
}
if ( is_numeric( $value ) ) {
return self::C_DATATYPE_DOUBLE;
}
}
return self::C_DATATYPE_STRING;
}
/**
* @see QueryWriter::code
*/
public function code( $typedescription, $includeSpecials = FALSE )
{
$r = ( ( isset( $this->sqltype_typeno[$typedescription] ) ) ? $this->sqltype_typeno[$typedescription] : self::C_DATATYPE_SPECIFIED );
if ( $includeSpecials ) {
return $r;
}
if ( $r >= QueryWriter::C_DATATYPE_RANGE_SPECIAL ) {
return self::C_DATATYPE_SPECIFIED;
}
return $r;
}
/**
* @see QueryWriter::addColumn
*/
public function addColumn( $type, $column, $field )
{
$table = $type;
$type = $field;
$table = $this->esc( $table );
$column = $this->esc( $column );
$type = array_key_exists( $type, $this->typeno_sqltype ) ? $this->typeno_sqltype[$type] : '';
$this->adapter->exec( "ALTER TABLE $table ADD COLUMN $column $type " );
}
/**
* @see QueryWriter::addUniqueIndex
*/
public function addUniqueConstraint( $type, $properties )
{
$tableNoQ = $this->esc( $type, TRUE );
$columns = array();
foreach( $properties as $key => $column ) $columns[$key] = $this->esc( $column );
$table = $this->esc( $type );
sort( $columns ); // else we get multiple indexes due to order-effects
$name = 'UQ_' . sha1( implode( ',', $columns ) );
$sql = "ALTER TABLE $table ADD CONSTRAINT UNIQUE $name (" . implode( ',', $columns ) . ")";
try {
$this->adapter->exec( $sql );
} catch( SQLException $e ) {
return FALSE;
}
return TRUE;
}
/**
* @see QueryWriter::sqlStateIn
*/
public function sqlStateIn( $state, $list, $extraDriverDetails = array() )
{
return ( $state == 'HY000' ) ? ( count( array_diff( array(
QueryWriter::C_SQLSTATE_INTEGRITY_CONSTRAINT_VIOLATION,
QueryWriter::C_SQLSTATE_NO_SUCH_COLUMN,
QueryWriter::C_SQLSTATE_NO_SUCH_TABLE
), $list ) ) !== 3 ) : FALSE;
}
/**
* @see QueryWriter::addIndex
*/
public function addIndex( $type, $name, $column )
{
try {
$table = $this->esc( $type );
$name = preg_replace( '/\W/', '', $name );
$column = $this->esc( $column );
$this->adapter->exec( "CREATE INDEX $name ON $table ($column) " );
return TRUE;
} catch ( SQLException $e ) {
return FALSE;
}
}
/**
* @see QueryWriter::addFK
*/
public function addFK( $type, $targetType, $property, $targetProperty, $isDependent = FALSE )
{
return $this->buildFK( $type, $targetType, $property, $targetProperty, $isDependent );
}
/**
* @see QueryWriter::wipeAll
*/
public function wipeAll()
{
if (AQueryWriter::$noNuke) throw new \Exception('The nuke() command has been disabled using noNuke() or R::feature(novice/...).');
foreach ( $this->getTables() as $t ) {
foreach ( $this->getKeyMapForType( $t ) as $k ) {
$this->adapter->exec( "ALTER TABLE \"$t\" DROP FOREIGN KEY \"{$k['name']}\"" );
}
}
foreach ( $this->getTables() as $t ) {
$this->adapter->exec( "DROP TABLE \"$t\"" );
}
}
/**
* @see QueryWriter::esc
*/
public function esc( $dbStructure, $noQuotes = FALSE )
{
return parent::esc( strtolower( $dbStructure ), $noQuotes );
}
}

View File

@@ -0,0 +1,352 @@
<?php
namespace RedBeanPHP\QueryWriter;
/* Experimental */
/**
* This driver has been created in 2015 but it has never been distributed
* because it was never finished. However, in the true spirit of open source
* it is now available in the source of RedBeanPHP.
*
* Consider this driver experimental or help me finish it to
* support the Firebird/Interbase database series.
*
*/
use RedBeanPHP\QueryWriter\AQueryWriter as AQueryWriter;
use RedBeanPHP\QueryWriter as QueryWriter;
use RedBeanPHP\Adapter\DBAdapter as DBAdapter;
use RedBeanPHP\Adapter as Adapter;
use RedBeanPHP\RedException\SQL as SQLException;
/**
* RedBeanPHP Firebird/Interbase QueryWriter.
* This is a QueryWriter class for RedBeanPHP.
* This QueryWriter provides support for the Firebird/Interbase database platform.
*
* *** Warning - Experimental Software | Not ready for Production ****
*
* @file RedBeanPHP/QueryWriter/Firebird.php
* @author Gabor de Mooij and the RedBeanPHP Community
* @license BSD/GPLv2
*
* @copyright
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Firebird extends AQueryWriter implements QueryWriter
{
/**
* Data types
*/
const C_DATATYPE_INTEGER = 2;
const C_DATATYPE_FLOAT = 3;
const C_DATATYPE_TEXT = 5;
/**
* @var DBAdapter
*/
protected $adapter;
/**
* @var string
*/
protected $quoteCharacter = '"';
/**
* Returns the insert suffix SQL Snippet
*
* @param string $table table
*
* @return string $sql SQL Snippet
*/
protected function getInsertSuffix( $table )
{
return 'RETURNING id ';
}
/**
* @see AQueryWriter::getKeyMapForType
*/
protected function getKeyMapForType( $type )
{
$table = $this->esc( $type, TRUE );
$keys = $this->adapter->get('
SELECT
information_schema.key_column_usage.constraint_name AS `name`,
information_schema.key_column_usage.referenced_table_name AS `table`,
information_schema.key_column_usage.column_name AS `from`,
information_schema.key_column_usage.referenced_column_name AS `to`,
information_schema.referential_constraints.update_rule AS `on_update`,
information_schema.referential_constraints.delete_rule AS `on_delete`
FROM information_schema.key_column_usage
INNER JOIN information_schema.referential_constraints
ON (
information_schema.referential_constraints.constraint_name = information_schema.key_column_usage.constraint_name
AND information_schema.referential_constraints.constraint_schema = information_schema.key_column_usage.constraint_schema
AND information_schema.referential_constraints.constraint_catalog = information_schema.key_column_usage.constraint_catalog
)
WHERE
information_schema.key_column_usage.table_schema IN ( SELECT DATABASE() )
AND information_schema.key_column_usage.table_name = ?
AND information_schema.key_column_usage.constraint_name != \'PRIMARY\'
AND information_schema.key_column_usage.referenced_table_name IS NOT NULL
', array($table));
$keyInfoList = array();
foreach ( $keys as $k ) {
$label = $this->makeFKLabel( $k['from'], $k['table'], $k['to'] );
$keyInfoList[$label] = array(
'name' => $k['name'],
'from' => $k['from'],
'table' => $k['table'],
'to' => $k['to'],
'on_update' => $k['on_update'],
'on_delete' => $k['on_delete']
);
}
return $keyInfoList;
}
/**
* Constructor
*
* @param Adapter $adapter Database Adapter
*/
public function __construct( Adapter $adapter )
{
$this->typeno_sqltype = array(
Firebird::C_DATATYPE_INTEGER => 'INTEGER',
Firebird::C_DATATYPE_FLOAT => 'FLOAT',
Firebird::C_DATATYPE_TEXT => 'VARCHAR(8190)',
);
$this->sqltype_typeno = array();
foreach ( $this->typeno_sqltype as $k => $v ) {
$this->sqltype_typeno[trim( strtoupper( $v ) )] = $k;
}
$this->adapter = $adapter;
$this->encoding = $this->adapter->getDatabase()->getMysqlEncoding();
}
/**
* This method returns the datatype to be used for primary key IDS and
* foreign keys. Returns one if the data type constants.
*
* @return integer $const data type to be used for IDS.
*/
public function getTypeForID()
{
return self::C_DATATYPE_INTEGER;
}
/**
* @see QueryWriter::getTables
*/
public function getTables()
{
return $this->adapter->getCol( 'SELECT RDB$RELATION_NAME FROM RDB$RELATIONS
WHERE RDB$VIEW_BLR IS NULL AND
(RDB$SYSTEM_FLAG IS NULL OR RDB$SYSTEM_FLAG = 0)');
}
/**
* @see QueryWriter::createTable
*/
public function createTable( $table )
{
$tableNoQ = $this->esc( $table );
$tableSQL = "CREATE TABLE \"{$table}\" ( id INT )";
$dropGeneratorSQL = "DROP GENERATOR gen{$table}";
$generatorSQL = "CREATE GENERATOR gen{$table}";
$generatorSQL2 = "SET GENERATOR gen{$table} TO 0";
$triggerSQL = "
CREATE TRIGGER ai{$table} FOR \"{$table}\"
ACTIVE BEFORE INSERT POSITION 0
AS
BEGIN
if (NEW.id is NULL) then NEW.id = GEN_ID(gen{$table}, 1);
END
";
try { $this->adapter->exec( $dropGeneratorSQL ); }catch( SQLException $e ) {};
$this->adapter->exec( $tableSQL );
$this->adapter->exec( $generatorSQL );
$this->adapter->exec( $generatorSQL2 );
$this->adapter->exec( $triggerSQL );
}
/**
* @see QueryWriter::widenColumn
*/
public function widenColumn( $type, $property, $dataType )
{
if ( !isset($this->typeno_sqltype[$dataType]) ) return FALSE;
$table = $this->esc( $type );
$column = $this->esc( $property );
$newType = $this->typeno_sqltype[$dataType];
$this->adapter->exec( "ALTER TABLE $table ALTER COLUMN $column TYPE $newType " );
return TRUE;
}
/**
* @see QueryWriter::getColumns
*/
public function getColumns( $table )
{
$columnsRaw = $this->adapter->getAssoc( '
SELECT
RDB$RELATION_FIELDS.RDB$FIELD_NAME,
CASE RDB$FIELDS.RDB$FIELD_TYPE
WHEN 10 THEN \'FLOAT\'
WHEN 8 THEN \'INTEGER\'
WHEN 37 THEN \'VARCHAR\'
ELSE RDB$FIELDS.RDB$FIELD_TYPE
END AS FTYPE
FROM RDB$RELATION_FIELDS
LEFT JOIN RDB$FIELDS ON RDB$RELATION_FIELDS.RDB$FIELD_SOURCE = RDB$FIELDS.RDB$FIELD_NAME
WHERE RDB$RELATION_FIELDS.RDB$RELATION_NAME = \''.($this->esc($table, true)).'\'
ORDER BY RDB$RELATION_FIELDS.RDB$FIELD_POSITION
');
$columns = array();
foreach( $columnsRaw as $rawKey => $columnRaw ) {
$columns[ trim( $rawKey ) ] = trim( $columnRaw );
}
return $columns;
}
/**
* @see QueryWriter::scanType
*/
public function scanType( $value, $flagSpecial = FALSE )
{
if ( AQueryWriter::canBeTreatedAsInt( $value ) ) {
return FireBird::C_DATATYPE_INTEGER;
}
if ( !$this->startsWithZeros( $value ) && is_numeric( $value ) ) {
return FireBird::C_DATATYPE_DOUBLE;
}
return FireBird::C_DATATYPE_TEXT;
}
/**
* @see QueryWriter::code
*/
public function code( $typedescription, $includeSpecials = FALSE )
{
if ( isset( $this->sqltype_typeno[$typedescription] ) ) {
return $this->sqltype_typeno[$typedescription];
} else {
return self::C_DATATYPE_SPECIFIED;
}
}
/**
* @see QueryWriter::addUniqueIndex
*/
public function addUniqueConstraint( $type, $properties )
{
$tableNoQ = $this->esc( $type, TRUE );
$columns = array();
foreach( $properties as $key => $column ) $columns[$key] = $this->esc( $column );
$table = $this->esc( $type );
sort( $columns ); // Else we get multiple indexes due to order-effects
$name = 'UQ_'.substr( sha1( implode( ',', $columns ) ), 0, 28);
try {
$sql = "ALTER TABLE $table
ADD CONSTRAINT $name UNIQUE (" . implode( ',', $columns ) . ")";
$this->adapter->exec( $sql );
} catch ( SQLException $e ) {
//do nothing, dont use alter table ignore, this will delete duplicate records in 3-ways!
return FALSE;
}
return TRUE;
}
/**
* @see QueryWriter::addIndex
*/
public function addIndex( $type, $name, $property )
{
try {
$table = $this->esc( $type );
$name = preg_replace( '/\W/', '', $name );
$column = $this->esc( $property );
$this->adapter->exec( "CREATE INDEX $name ON $table ($column) " );
return TRUE;
} catch ( SQLException $e ) {
return FALSE;
}
}
/**
* @see QueryWriter::addFK
*/
public function addFK( $type, $targetType, $property, $targetProperty, $isDependent = FALSE )
{
$table = $this->esc( $type );
$targetTable = $this->esc( $targetType );
$targetTableNoQ = $this->esc( $targetType, TRUE );
$field = $this->esc( $property );
$fieldNoQ = $this->esc( $property, TRUE );
$targetField = $this->esc( $targetProperty );
$targetFieldNoQ = $this->esc( $targetProperty, TRUE );
$tableNoQ = $this->esc( $type, TRUE );
$fieldNoQ = $this->esc( $property, TRUE );
if ( !is_null( $this->getForeignKeyForTypeProperty( $tableNoQ, $fieldNoQ ) ) ) return FALSE;
//Widen the column if it's incapable of representing a foreign key (at least INT).
$columns = $this->getColumns( $tableNoQ );
$idType = $this->getTypeForID();
if ( $this->code( $columns[$fieldNoQ] ) !== $idType ) {
$this->widenColumn( $type, $property, $idType );
}
$fkName = 'fk_'.($tableNoQ.'_'.$fieldNoQ);
$cName = 'c_'.$fkName;
try {
$this->adapter->exec( "
ALTER TABLE {$table}
ADD CONSTRAINT $cName
FOREIGN KEY $fkName ( {$fieldNoQ} ) REFERENCES {$targetTableNoQ}
({$targetFieldNoQ}) ON DELETE " . ( $isDependent ? 'CASCADE' : 'SET NULL' ) . ' ON UPDATE '.( $isDependent ? 'CASCADE' : 'SET NULL' ).';');
} catch ( SQLException $e ) {
// Failure of fk-constraints is not a problem
}
}
/**
* @see QueryWriter::sqlStateIn
*/
public function sqlStateIn( $state, $list, $extraDriverDetails = array() )
{
$stateMap = array(
'42S02' => QueryWriter::C_SQLSTATE_NO_SUCH_TABLE,
'42S22' => QueryWriter::C_SQLSTATE_NO_SUCH_COLUMN,
'23000' => QueryWriter::C_SQLSTATE_INTEGRITY_CONSTRAINT_VIOLATION
);
return in_array( ( isset( $stateMap[$state] ) ? $stateMap[$state] : '0' ), $list );
}
/**
* @see QueryWriter::wipeAll
*/
public function wipeAll()
{
if (AQueryWriter::$noNuke) throw new \Exception('The nuke() command has been disabled using noNuke() or R::feature(novice/...).');
$tables = $this->getTables();
foreach( $tables as $table ) {
$table = trim( $table );
$this->adapter->exec( "DROP TABLE \"{$table}\" " );
}
}
}

View File

@@ -0,0 +1,460 @@
<?php
namespace RedBeanPHP\QueryWriter;
use RedBeanPHP\QueryWriter\AQueryWriter as AQueryWriter;
use RedBeanPHP\QueryWriter as QueryWriter;
use RedBeanPHP\Adapter\DBAdapter as DBAdapter;
use RedBeanPHP\Adapter as Adapter;
use RedBeanPHP\RedException\SQL as SQLException;
/**
* RedBeanPHP MySQLWriter.
* This is a QueryWriter class for RedBeanPHP.
* This QueryWriter provides support for the MySQL/MariaDB database platform.
*
* @file RedBeanPHP/QueryWriter/MySQL.php
* @author Gabor de Mooij and the RedBeanPHP Community
* @license BSD/GPLv2
*
* @copyright
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class MySQL extends AQueryWriter implements QueryWriter
{
/**
* Data types
*/
const C_DATATYPE_BOOL = 0;
const C_DATATYPE_UINT32 = 2;
const C_DATATYPE_DOUBLE = 3;
const C_DATATYPE_TEXT7 = 4; //InnoDB cant index varchar(255) utf8mb4 - so keep 191 as long as possible
const C_DATATYPE_TEXT8 = 5;
const C_DATATYPE_TEXT16 = 6;
const C_DATATYPE_TEXT32 = 7;
const C_DATATYPE_SPECIAL_DATE = 80;
const C_DATATYPE_SPECIAL_DATETIME = 81;
const C_DATATYPE_SPECIAL_TIME = 83; //MySQL time column (only manual)
const C_DATATYPE_SPECIAL_POINT = 90;
const C_DATATYPE_SPECIAL_LINESTRING = 91;
const C_DATATYPE_SPECIAL_POLYGON = 92;
const C_DATATYPE_SPECIAL_MONEY = 93;
const C_DATATYPE_SPECIAL_JSON = 94; //JSON support (only manual)
const C_DATATYPE_SPECIFIED = 99;
/**
* @var DBAdapter
*/
protected $adapter;
/**
* @var string
*/
protected $quoteCharacter = '`';
/**
* @var array
*/
protected $DDLTemplates = array(
'addColumn' => array(
'*' => 'ALTER TABLE %s ADD %s %s '
),
'createTable' => array(
'*' => 'CREATE TABLE %s (id INT( 11 ) UNSIGNED NOT NULL AUTO_INCREMENT, PRIMARY KEY ( id )) ENGINE = InnoDB DEFAULT CHARSET=%s COLLATE=%s '
),
'widenColumn' => array(
'*' => 'ALTER TABLE `%s` CHANGE %s %s %s '
)
);
/**
* @see AQueryWriter::getKeyMapForType
*/
protected function getKeyMapForType( $type )
{
$databaseName = $this->adapter->getCell('SELECT DATABASE()');
$table = $this->esc( $type, TRUE );
$keys = $this->adapter->get('
SELECT
information_schema.key_column_usage.constraint_name AS `name`,
information_schema.key_column_usage.referenced_table_name AS `table`,
information_schema.key_column_usage.column_name AS `from`,
information_schema.key_column_usage.referenced_column_name AS `to`,
information_schema.referential_constraints.update_rule AS `on_update`,
information_schema.referential_constraints.delete_rule AS `on_delete`
FROM information_schema.key_column_usage
INNER JOIN information_schema.referential_constraints
ON information_schema.referential_constraints.constraint_name = information_schema.key_column_usage.constraint_name
WHERE
information_schema.key_column_usage.table_schema = :database
AND information_schema.referential_constraints.constraint_schema = :database
AND information_schema.key_column_usage.constraint_schema = :database
AND information_schema.key_column_usage.table_name = :table
AND information_schema.key_column_usage.constraint_name != \'PRIMARY\'
AND information_schema.key_column_usage.referenced_table_name IS NOT NULL
', array( ':database' => $databaseName, ':table' => $table ) );
$keyInfoList = array();
foreach ( $keys as $k ) {
$label = $this->makeFKLabel( $k['from'], $k['table'], $k['to'] );
$keyInfoList[$label] = array(
'name' => $k['name'],
'from' => $k['from'],
'table' => $k['table'],
'to' => $k['to'],
'on_update' => $k['on_update'],
'on_delete' => $k['on_delete']
);
}
return $keyInfoList;
}
/**
* Constructor
* Most of the time, you do not need to use this constructor,
* since the facade takes care of constructing and wiring the
* RedBeanPHP core objects. However if you would like to
* assemble an OODB instance yourself, this is how it works:
*
* Usage:
*
* <code>
* $database = new RPDO( $dsn, $user, $pass );
* $adapter = new DBAdapter( $database );
* $writer = new PostgresWriter( $adapter );
* $oodb = new OODB( $writer, FALSE );
* $bean = $oodb->dispense( 'bean' );
* $bean->name = 'coffeeBean';
* $id = $oodb->store( $bean );
* $bean = $oodb->load( 'bean', $id );
* </code>
*
* The example above creates the 3 RedBeanPHP core objects:
* the Adapter, the Query Writer and the OODB instance and
* wires them together. The example also demonstrates some of
* the methods that can be used with OODB, as you see, they
* closely resemble their facade counterparts.
*
* The wiring process: create an RPDO instance using your database
* connection parameters. Create a database adapter from the RPDO
* object and pass that to the constructor of the writer. Next,
* create an OODB instance from the writer. Now you have an OODB
* object.
*
* @param Adapter $adapter Database Adapter
* @param array $options options array
*/
public function __construct( Adapter $adapter, $options = array() )
{
$this->typeno_sqltype = array(
MySQL::C_DATATYPE_BOOL => ' TINYINT(1) UNSIGNED ',
MySQL::C_DATATYPE_UINT32 => ' INT(11) UNSIGNED ',
MySQL::C_DATATYPE_DOUBLE => ' DOUBLE ',
MySQL::C_DATATYPE_TEXT7 => ' VARCHAR(191) ',
MYSQL::C_DATATYPE_TEXT8 => ' VARCHAR(255) ',
MySQL::C_DATATYPE_TEXT16 => ' TEXT ',
MySQL::C_DATATYPE_TEXT32 => ' LONGTEXT ',
MySQL::C_DATATYPE_SPECIAL_DATE => ' DATE ',
MySQL::C_DATATYPE_SPECIAL_DATETIME => ' DATETIME ',
MySQL::C_DATATYPE_SPECIAL_TIME => ' TIME ',
MySQL::C_DATATYPE_SPECIAL_POINT => ' POINT ',
MySQL::C_DATATYPE_SPECIAL_LINESTRING => ' LINESTRING ',
MySQL::C_DATATYPE_SPECIAL_POLYGON => ' POLYGON ',
MySQL::C_DATATYPE_SPECIAL_MONEY => ' DECIMAL(10,2) ',
MYSQL::C_DATATYPE_SPECIAL_JSON => ' JSON '
);
$this->sqltype_typeno = array();
foreach ( $this->typeno_sqltype as $k => $v ) {
$this->sqltype_typeno[trim( strtolower( $v ) )] = $k;
}
$this->adapter = $adapter;
$this->encoding = $this->adapter->getDatabase()->getMysqlEncoding();
$me = $this;
if (!isset($options['noInitcode']))
$this->adapter->setInitCode(function($version) use(&$me) {
try {
if (strpos($version, 'maria')===FALSE && intval($version)>=8) {
$me->useFeature('ignoreDisplayWidth');
}
} catch( \Exception $e ){}
});
}
/**
* Enables certain features/dialects.
*
* - ignoreDisplayWidth required for MySQL8+
* (automatically set by setup() if you pass dsn instead of PDO object)
*
* @param string $name feature ID
*
* @return void
*/
public function useFeature($name) {
if ($name == 'ignoreDisplayWidth') {
$this->typeno_sqltype[MySQL::C_DATATYPE_BOOL] = ' TINYINT UNSIGNED ';
$this->typeno_sqltype[MySQL::C_DATATYPE_UINT32] = ' INT UNSIGNED ';
foreach ( $this->typeno_sqltype as $k => $v ) {
$this->sqltype_typeno[trim( strtolower( $v ) )] = $k;
}
}
}
/**
* This method returns the datatype to be used for primary key IDS and
* foreign keys. Returns one if the data type constants.
*
* @return integer
*/
public function getTypeForID()
{
return self::C_DATATYPE_UINT32;
}
/**
* @see QueryWriter::getTables
*/
public function getTables()
{
return $this->adapter->getCol( 'show tables' );
}
/**
* @see QueryWriter::createTable
*/
public function createTable( $type )
{
$table = $this->esc( $type );
$charset_collate = $this->adapter->getDatabase()->getMysqlEncoding( TRUE );
$charset = $charset_collate['charset'];
$collate = $charset_collate['collate'];
$sql = sprintf( $this->getDDLTemplate( 'createTable', $type ), $table, $charset, $collate );
$this->adapter->exec( $sql );
}
/**
* @see QueryWriter::getColumns
*/
public function getColumns( $table )
{
$columnsRaw = $this->adapter->get( "DESCRIBE " . $this->esc( $table ) );
$columns = array();
foreach ( $columnsRaw as $r ) {
$columns[$r['Field']] = $r['Type'];
}
return $columns;
}
/**
* @see QueryWriter::scanType
*/
public function scanType( $value, $flagSpecial = FALSE )
{
$this->svalue = $value;
if ( is_null( $value ) ) return MySQL::C_DATATYPE_BOOL;
if ( $value === INF ) return MySQL::C_DATATYPE_TEXT7;
if ( $flagSpecial ) {
if ( preg_match( '/^-?\d+\.\d{2}$/', $value ) ) {
return MySQL::C_DATATYPE_SPECIAL_MONEY;
}
if ( preg_match( '/^\d{4}\-\d\d-\d\d$/', $value ) ) {
return MySQL::C_DATATYPE_SPECIAL_DATE;
}
if ( preg_match( '/^\d{4}\-\d\d-\d\d\s\d\d:\d\d:\d\d$/', $value ) ) {
return MySQL::C_DATATYPE_SPECIAL_DATETIME;
}
if ( preg_match( '/^POINT\(/', $value ) ) {
return MySQL::C_DATATYPE_SPECIAL_POINT;
}
if ( preg_match( '/^LINESTRING\(/', $value ) ) {
return MySQL::C_DATATYPE_SPECIAL_LINESTRING;
}
if ( preg_match( '/^POLYGON\(/', $value ) ) {
return MySQL::C_DATATYPE_SPECIAL_POLYGON;
}
if ( self::$flagUseJSONColumns && $this->isJSON( $value ) ) {
return self::C_DATATYPE_SPECIAL_JSON;
}
}
//setter turns TRUE FALSE into 0 and 1 because database has no real bools (TRUE and FALSE only for test?).
if ( $value === FALSE || $value === TRUE || $value === '0' || $value === '1' || $value === 0 || $value === 1 ) {
return MySQL::C_DATATYPE_BOOL;
}
if ( is_float( $value ) ) return self::C_DATATYPE_DOUBLE;
if ( !$this->startsWithZeros( $value ) ) {
if ( is_numeric( $value ) && ( floor( $value ) == $value ) && $value >= 0 && $value <= 4294967295 ) {
return MySQL::C_DATATYPE_UINT32;
}
if ( is_numeric( $value ) ) {
return MySQL::C_DATATYPE_DOUBLE;
}
}
if ( mb_strlen( $value, 'UTF-8' ) <= 191 ) {
return MySQL::C_DATATYPE_TEXT7;
}
if ( mb_strlen( $value, 'UTF-8' ) <= 255 ) {
return MySQL::C_DATATYPE_TEXT8;
}
if ( mb_strlen( $value, 'UTF-8' ) <= 65535 ) {
return MySQL::C_DATATYPE_TEXT16;
}
return MySQL::C_DATATYPE_TEXT32;
}
/**
* @see QueryWriter::code
*/
public function code( $typedescription, $includeSpecials = FALSE )
{
if ( isset( $this->sqltype_typeno[$typedescription] ) ) {
$r = $this->sqltype_typeno[$typedescription];
} else {
$r = self::C_DATATYPE_SPECIFIED;
}
if ( $includeSpecials ) {
return $r;
}
if ( $r >= QueryWriter::C_DATATYPE_RANGE_SPECIAL ) {
return self::C_DATATYPE_SPECIFIED;
}
return $r;
}
/**
* @see QueryWriter::addUniqueIndex
*/
public function addUniqueConstraint( $type, $properties )
{
$tableNoQ = $this->esc( $type, TRUE );
$columns = array();
foreach( $properties as $key => $column ) $columns[$key] = $this->esc( $column );
$table = $this->esc( $type );
sort( $columns ); // Else we get multiple indexes due to order-effects
$name = 'UQ_' . sha1( implode( ',', $columns ) );
try {
$sql = "ALTER TABLE $table
ADD UNIQUE INDEX $name (" . implode( ',', $columns ) . ")";
$this->adapter->exec( $sql );
} catch ( SQLException $e ) {
//do nothing, dont use alter table ignore, this will delete duplicate records in 3-ways!
return FALSE;
}
return TRUE;
}
/**
* @see QueryWriter::addIndex
*/
public function addIndex( $type, $name, $property )
{
try {
$table = $this->esc( $type );
$name = preg_replace( '/\W/', '', $name );
$column = $this->esc( $property );
$this->adapter->exec( "CREATE INDEX $name ON $table ($column) " );
return TRUE;
} catch ( SQLException $e ) {
return FALSE;
}
}
/**
* @see QueryWriter::addFK
* @return bool
*/
public function addFK( $type, $targetType, $property, $targetProperty, $isDependent = FALSE )
{
$table = $this->esc( $type );
$targetTable = $this->esc( $targetType );
$targetTableNoQ = $this->esc( $targetType, TRUE );
$field = $this->esc( $property );
$fieldNoQ = $this->esc( $property, TRUE );
$targetField = $this->esc( $targetProperty );
$targetFieldNoQ = $this->esc( $targetProperty, TRUE );
$tableNoQ = $this->esc( $type, TRUE );
$fieldNoQ = $this->esc( $property, TRUE );
if ( !is_null( $this->getForeignKeyForTypeProperty( $tableNoQ, $fieldNoQ ) ) ) return FALSE;
//Widen the column if it's incapable of representing a foreign key (at least INT).
$columns = $this->getColumns( $tableNoQ );
$idType = $this->getTypeForID();
if ( $this->code( $columns[$fieldNoQ] ) !== $idType ) {
$this->widenColumn( $type, $property, $idType );
}
$fkName = 'fk_'.($tableNoQ.'_'.$fieldNoQ);
$cName = 'c_'.$fkName;
try {
$this->adapter->exec( "
ALTER TABLE {$table}
ADD CONSTRAINT $cName
FOREIGN KEY $fkName ( `{$fieldNoQ}` ) REFERENCES `{$targetTableNoQ}`
(`{$targetFieldNoQ}`) ON DELETE " . ( $isDependent ? 'CASCADE' : 'SET NULL' ) . ' ON UPDATE '.( $isDependent ? 'CASCADE' : 'SET NULL' ).';');
} catch ( SQLException $e ) {
// Failure of fk-constraints is not a problem
}
return TRUE;
}
/**
* @see QueryWriter::sqlStateIn
*/
public function sqlStateIn( $state, $list, $extraDriverDetails = array() )
{
$stateMap = array(
'42S02' => QueryWriter::C_SQLSTATE_NO_SUCH_TABLE,
'42S22' => QueryWriter::C_SQLSTATE_NO_SUCH_COLUMN,
'23000' => QueryWriter::C_SQLSTATE_INTEGRITY_CONSTRAINT_VIOLATION,
);
if ( $state == 'HY000' && !empty( $extraDriverDetails[1] ) ) {
$driverCode = $extraDriverDetails[1];
if ( $driverCode == '1205' && in_array( QueryWriter::C_SQLSTATE_LOCK_TIMEOUT, $list ) ) {
return TRUE;
}
}
return in_array( ( isset( $stateMap[$state] ) ? $stateMap[$state] : '0' ), $list );
}
/**
* @see QueryWriter::wipeAll
*/
public function wipeAll()
{
if (AQueryWriter::$noNuke) throw new \Exception('The nuke() command has been disabled using noNuke() or R::feature(novice/...).');
$this->adapter->exec( 'SET FOREIGN_KEY_CHECKS = 0;' );
foreach ( $this->getTables() as $t ) {
try { $this->adapter->exec( "DROP TABLE IF EXISTS `$t`" ); } catch ( SQLException $e ) { ; }
try { $this->adapter->exec( "DROP VIEW IF EXISTS `$t`" ); } catch ( SQLException $e ) { ; }
}
$this->adapter->exec( 'SET FOREIGN_KEY_CHECKS = 1;' );
}
}

View File

@@ -0,0 +1,436 @@
<?php
namespace RedBeanPHP\QueryWriter;
use RedBeanPHP\QueryWriter\AQueryWriter as AQueryWriter;
use RedBeanPHP\QueryWriter as QueryWriter;
use RedBeanPHP\Adapter\DBAdapter as DBAdapter;
use RedBeanPHP\Adapter as Adapter;
use RedBeanPHP\RedException\SQL as SQLException;
/**
* RedBeanPHP PostgreSQL Query Writer.
* This is a QueryWriter class for RedBeanPHP.
* This QueryWriter provides support for the PostgreSQL database platform.
*
* @file RedBeanPHP/QueryWriter/PostgreSQL.php
* @author Gabor de Mooij and the RedBeanPHP Community
* @license BSD/GPLv2
*
* @copyright
* (c) copyright G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class PostgreSQL extends AQueryWriter implements QueryWriter
{
/**
* Data types
*/
const C_DATATYPE_INTEGER = 0;
const C_DATATYPE_DOUBLE = 1;
const C_DATATYPE_TEXT = 3;
const C_DATATYPE_SPECIAL_DATE = 80;
const C_DATATYPE_SPECIAL_DATETIME = 81;
const C_DATATYPE_SPECIAL_TIME = 82; //TIME (no zone) only manual
const C_DATATYPE_SPECIAL_TIMEZ = 83; //TIME (plus zone) only manual
const C_DATATYPE_SPECIAL_POINT = 90;
const C_DATATYPE_SPECIAL_LSEG = 91;
const C_DATATYPE_SPECIAL_CIRCLE = 92;
const C_DATATYPE_SPECIAL_MONEY = 93;
const C_DATATYPE_SPECIAL_POLYGON = 94;
const C_DATATYPE_SPECIAL_MONEY2 = 95; //Numbers only money, i.e. fixed point numeric
const C_DATATYPE_SPECIAL_JSON = 96; //JSON support (only manual)
const C_DATATYPE_SPECIFIED = 99;
/**
* @var DBAdapter
*/
protected $adapter;
/**
* @var string
*/
protected $quoteCharacter = '"';
/**
* @var string
*/
protected $defaultValue = 'DEFAULT';
/**
* @var array
*/
protected $DDLTemplates = array(
'addColumn' => array(
'*' => 'ALTER TABLE %s ADD %s %s '
),
'createTable' => array(
'*' => 'CREATE TABLE %s (id SERIAL PRIMARY KEY) '
),
'widenColumn' => array(
'*' => 'ALTER TABLE %s ALTER COLUMN %s TYPE %s'
)
);
/**
* Returns the insert suffix SQL Snippet
*
* @param string $table table
*
* @return string $sql SQL Snippet
*/
protected function getInsertSuffix( $table )
{
return 'RETURNING id ';
}
/**
* @see AQueryWriter::getKeyMapForType
*/
protected function getKeyMapForType( $type )
{
$table = $this->esc( $type, TRUE );
$keys = $this->adapter->get( '
SELECT
information_schema.key_column_usage.constraint_name AS "name",
information_schema.key_column_usage.column_name AS "from",
information_schema.constraint_table_usage.table_name AS "table",
information_schema.constraint_column_usage.column_name AS "to",
information_schema.referential_constraints.update_rule AS "on_update",
information_schema.referential_constraints.delete_rule AS "on_delete"
FROM information_schema.key_column_usage
INNER JOIN information_schema.constraint_table_usage
ON (
information_schema.key_column_usage.constraint_name = information_schema.constraint_table_usage.constraint_name
AND information_schema.key_column_usage.constraint_schema = information_schema.constraint_table_usage.constraint_schema
AND information_schema.key_column_usage.constraint_catalog = information_schema.constraint_table_usage.constraint_catalog
)
INNER JOIN information_schema.constraint_column_usage
ON (
information_schema.key_column_usage.constraint_name = information_schema.constraint_column_usage.constraint_name
AND information_schema.key_column_usage.constraint_schema = information_schema.constraint_column_usage.constraint_schema
AND information_schema.key_column_usage.constraint_catalog = information_schema.constraint_column_usage.constraint_catalog
)
INNER JOIN information_schema.referential_constraints
ON (
information_schema.key_column_usage.constraint_name = information_schema.referential_constraints.constraint_name
AND information_schema.key_column_usage.constraint_schema = information_schema.referential_constraints.constraint_schema
AND information_schema.key_column_usage.constraint_catalog = information_schema.referential_constraints.constraint_catalog
)
WHERE
information_schema.key_column_usage.table_catalog = current_database()
AND information_schema.key_column_usage.table_schema = ANY( current_schemas( FALSE ) )
AND information_schema.key_column_usage.table_name = ?
', array( $type ) );
$keyInfoList = array();
foreach ( $keys as $k ) {
$label = $this->makeFKLabel( $k['from'], $k['table'], $k['to'] );
$keyInfoList[$label] = array(
'name' => $k['name'],
'from' => $k['from'],
'table' => $k['table'],
'to' => $k['to'],
'on_update' => $k['on_update'],
'on_delete' => $k['on_delete']
);
}
return $keyInfoList;
}
/**
* Constructor
* Most of the time, you do not need to use this constructor,
* since the facade takes care of constructing and wiring the
* RedBeanPHP core objects. However if you would like to
* assemble an OODB instance yourself, this is how it works:
*
* Usage:
*
* <code>
* $database = new RPDO( $dsn, $user, $pass );
* $adapter = new DBAdapter( $database );
* $writer = new PostgresWriter( $adapter );
* $oodb = new OODB( $writer, FALSE );
* $bean = $oodb->dispense( 'bean' );
* $bean->name = 'coffeeBean';
* $id = $oodb->store( $bean );
* $bean = $oodb->load( 'bean', $id );
* </code>
*
* The example above creates the 3 RedBeanPHP core objects:
* the Adapter, the Query Writer and the OODB instance and
* wires them together. The example also demonstrates some of
* the methods that can be used with OODB, as you see, they
* closely resemble their facade counterparts.
*
* The wiring process: create an RPDO instance using your database
* connection parameters. Create a database adapter from the RPDO
* object and pass that to the constructor of the writer. Next,
* create an OODB instance from the writer. Now you have an OODB
* object.
*
* @param Adapter $adapter Database Adapter
*/
public function __construct( Adapter $adapter )
{
$this->typeno_sqltype = array(
self::C_DATATYPE_INTEGER => ' integer ',
self::C_DATATYPE_DOUBLE => ' double precision ',
self::C_DATATYPE_TEXT => ' text ',
self::C_DATATYPE_SPECIAL_DATE => ' date ',
self::C_DATATYPE_SPECIAL_TIME => ' time ',
self::C_DATATYPE_SPECIAL_TIMEZ => ' time with time zone ',
self::C_DATATYPE_SPECIAL_DATETIME => ' timestamp without time zone ',
self::C_DATATYPE_SPECIAL_POINT => ' point ',
self::C_DATATYPE_SPECIAL_LSEG => ' lseg ',
self::C_DATATYPE_SPECIAL_CIRCLE => ' circle ',
self::C_DATATYPE_SPECIAL_MONEY => ' money ',
self::C_DATATYPE_SPECIAL_MONEY2 => ' numeric(10,2) ',
self::C_DATATYPE_SPECIAL_POLYGON => ' polygon ',
self::C_DATATYPE_SPECIAL_JSON => ' json ',
);
$this->sqltype_typeno = array();
foreach ( $this->typeno_sqltype as $k => $v ) {
$this->sqltype_typeno[trim( strtolower( $v ) )] = $k;
}
$this->adapter = $adapter;
}
/**
* This method returns the datatype to be used for primary key IDS and
* foreign keys. Returns one if the data type constants.
*
* @return integer
*/
public function getTypeForID()
{
return self::C_DATATYPE_INTEGER;
}
/**
* @see QueryWriter::getTables
*/
public function getTables()
{
return $this->adapter->getCol( 'SELECT table_name FROM information_schema.tables WHERE table_schema = ANY( current_schemas( FALSE ) )' );
}
/**
* @see QueryWriter::createTable
*/
public function createTable( $type )
{
$table = $this->esc( $type );
$this->adapter->exec( sprintf( $this->getDDLTemplate( 'createTable', $type ), $table ) );
}
/**
* @see QueryWriter::getColumns
*/
public function getColumns( $table )
{
$table = $this->esc( $table, TRUE );
$columnsRaw = $this->adapter->get( "SELECT column_name, data_type FROM information_schema.columns WHERE table_name='$table' AND table_schema = ANY( current_schemas( FALSE ) )" );
$columns = array();
foreach ( $columnsRaw as $r ) {
$columns[$r['column_name']] = $r['data_type'];
}
return $columns;
}
/**
* @see QueryWriter::scanType
*/
public function scanType( $value, $flagSpecial = FALSE )
{
$this->svalue = $value;
if ( $value === INF ) return self::C_DATATYPE_TEXT;
if ( $flagSpecial && $value ) {
if ( preg_match( '/^\d{4}\-\d\d-\d\d$/', $value ) ) {
return PostgreSQL::C_DATATYPE_SPECIAL_DATE;
}
if ( preg_match( '/^\d{4}\-\d\d-\d\d\s\d\d:\d\d:\d\d(\.\d{1,6})?$/', $value ) ) {
return PostgreSQL::C_DATATYPE_SPECIAL_DATETIME;
}
if ( preg_match( '/^\([\d\.]+,[\d\.]+\)$/', $value ) ) {
return PostgreSQL::C_DATATYPE_SPECIAL_POINT;
}
if ( preg_match( '/^\[\([\d\.]+,[\d\.]+\),\([\d\.]+,[\d\.]+\)\]$/', $value ) ) {
return PostgreSQL::C_DATATYPE_SPECIAL_LSEG;
}
if ( preg_match( '/^\<\([\d\.]+,[\d\.]+\),[\d\.]+\>$/', $value ) ) {
return PostgreSQL::C_DATATYPE_SPECIAL_CIRCLE;
}
if ( preg_match( '/^\((\([\d\.]+,[\d\.]+\),?)+\)$/', $value ) ) {
return PostgreSQL::C_DATATYPE_SPECIAL_POLYGON;
}
if ( preg_match( '/^\-?(\$|€|¥|£)[\d,\.]+$/', $value ) ) {
return PostgreSQL::C_DATATYPE_SPECIAL_MONEY;
}
if ( preg_match( '/^-?\d+\.\d{2}$/', $value ) ) {
return PostgreSQL::C_DATATYPE_SPECIAL_MONEY2;
}
if ( self::$flagUseJSONColumns && $this->isJSON( $value ) ) {
return self::C_DATATYPE_SPECIAL_JSON;
}
}
if ( is_float( $value ) ) return self::C_DATATYPE_DOUBLE;
if ( $this->startsWithZeros( $value ) ) return self::C_DATATYPE_TEXT;
if ( $value === FALSE || $value === TRUE || $value === NULL || ( is_numeric( $value )
&& AQueryWriter::canBeTreatedAsInt( $value )
&& $value < 2147483648
&& $value > -2147483648 )
) {
return self::C_DATATYPE_INTEGER;
} elseif ( is_numeric( $value ) ) {
return self::C_DATATYPE_DOUBLE;
} else {
return self::C_DATATYPE_TEXT;
}
}
/**
* @see QueryWriter::code
*/
public function code( $typedescription, $includeSpecials = FALSE )
{
$r = ( isset( $this->sqltype_typeno[$typedescription] ) ) ? $this->sqltype_typeno[$typedescription] : 99;
if ( $includeSpecials ) return $r;
if ( $r >= QueryWriter::C_DATATYPE_RANGE_SPECIAL ) {
return self::C_DATATYPE_SPECIFIED;
}
return $r;
}
/**
* @see QueryWriter::widenColumn
*/
public function widenColumn( $beanType, $column, $datatype )
{
$table = $beanType;
$type = $datatype;
$table = $this->esc( $table );
$column = $this->esc( $column );
$newtype = $this->typeno_sqltype[$type];
$this->adapter->exec( sprintf( $this->getDDLTemplate( 'widenColumn', $beanType, $column ), $table, $column, $newtype ) );
}
/**
* @see QueryWriter::addUniqueIndex
*/
public function addUniqueConstraint( $type, $properties )
{
$tableNoQ = $this->esc( $type, TRUE );
$columns = array();
foreach( $properties as $key => $column ) $columns[$key] = $this->esc( $column );
$table = $this->esc( $type );
sort( $columns ); //else we get multiple indexes due to order-effects
$name = "UQ_" . sha1( $table . implode( ',', $columns ) );
$sql = "ALTER TABLE {$table}
ADD CONSTRAINT $name UNIQUE (" . implode( ',', $columns ) . ")";
try {
$this->adapter->exec( $sql );
} catch( SQLException $e ) {
return FALSE;
}
return TRUE;
}
/**
* @see QueryWriter::sqlStateIn
*/
public function sqlStateIn( $state, $list, $extraDriverDetails = array() )
{
$stateMap = array(
'42P01' => QueryWriter::C_SQLSTATE_NO_SUCH_TABLE,
'42703' => QueryWriter::C_SQLSTATE_NO_SUCH_COLUMN,
'23505' => QueryWriter::C_SQLSTATE_INTEGRITY_CONSTRAINT_VIOLATION,
'55P03' => QueryWriter::C_SQLSTATE_LOCK_TIMEOUT
);
return in_array( ( isset( $stateMap[$state] ) ? $stateMap[$state] : '0' ), $list );
}
/**
* @see QueryWriter::addIndex
*/
public function addIndex( $type, $name, $property )
{
$table = $this->esc( $type );
$name = preg_replace( '/\W/', '', $name );
$column = $this->esc( $property );
try {
$this->adapter->exec( "CREATE INDEX {$name} ON $table ({$column}) " );
return TRUE;
} catch ( SQLException $e ) {
return FALSE;
}
}
/**
* @see QueryWriter::addFK
*/
public function addFK( $type, $targetType, $property, $targetProperty, $isDep = FALSE )
{
$table = $this->esc( $type );
$targetTable = $this->esc( $targetType );
$field = $this->esc( $property );
$targetField = $this->esc( $targetProperty );
$tableNoQ = $this->esc( $type, TRUE );
$fieldNoQ = $this->esc( $property, TRUE );
if ( !is_null( $this->getForeignKeyForTypeProperty( $tableNoQ, $fieldNoQ ) ) ) return FALSE;
try{
$delRule = ( $isDep ? 'CASCADE' : 'SET NULL' );
$this->adapter->exec( "ALTER TABLE {$table}
ADD FOREIGN KEY ( {$field} ) REFERENCES {$targetTable}
({$targetField}) ON DELETE {$delRule} ON UPDATE {$delRule} DEFERRABLE ;" );
return TRUE;
} catch ( SQLException $e ) {
return FALSE;
}
}
/**
* @see QueryWriter::wipeAll
*/
public function wipeAll()
{
if (AQueryWriter::$noNuke) throw new \Exception('The nuke() command has been disabled using noNuke() or R::feature(novice/...).');
$this->adapter->exec( 'SET CONSTRAINTS ALL DEFERRED' );
foreach ( $this->getTables() as $t ) {
$t = $this->esc( $t );
//Some plugins (PostGIS have unremovable tables/views), avoid exceptions.
try { $this->adapter->exec( "DROP TABLE IF EXISTS $t CASCADE " ); }catch( \Exception $e ) {}
}
$this->adapter->exec( 'SET CONSTRAINTS ALL IMMEDIATE' );
}
}

View File

@@ -0,0 +1,496 @@
<?php
namespace RedBeanPHP\QueryWriter;
use RedBeanPHP\QueryWriter\AQueryWriter as AQueryWriter;
use RedBeanPHP\QueryWriter as QueryWriter;
use RedBeanPHP\Adapter\DBAdapter as DBAdapter;
use RedBeanPHP\Adapter as Adapter;
use RedBeanPHP\RedException\SQL as SQLException;
/**
* RedBeanPHP SQLiteWriter with support for SQLite types
* This is a QueryWriter class for RedBeanPHP.
* This QueryWriter provides support for the SQLite database platform.
*
* @file RedBeanPHP/QueryWriter/SQLiteT.php
* @author Gabor de Mooij and the RedBeanPHP Community
* @license BSD/GPLv2
*
* @copyright
* (c) copyright G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class SQLiteT extends AQueryWriter implements QueryWriter
{
/**
* Data types
*/
const C_DATATYPE_INTEGER = 0;
const C_DATATYPE_NUMERIC = 1;
const C_DATATYPE_TEXT = 2;
const C_DATATYPE_SPECIFIED = 99;
/**
* @var DBAdapter
*/
protected $adapter;
/**
* @var string
*/
protected $quoteCharacter = '`';
/**
* @var array
*/
protected $DDLTemplates = array(
'addColumn' => array(
'*' => 'ALTER TABLE `%s` ADD `%s` %s'
),
'createTable' => array(
'*' => 'CREATE TABLE %s ( id INTEGER PRIMARY KEY AUTOINCREMENT )'
),
'widenColumn' => array(
'*' => ',`%s` %s '
)
);
/**
* Gets all information about a table (from a type).
*
* Format:
* array(
* name => name of the table
* columns => array( name => datatype )
* indexes => array() raw index information rows from PRAGMA query
* keys => array() raw key information rows from PRAGMA query
* )
*
* @param string $type type you want to get info of
*
* @return array
*/
protected function getTable( $type )
{
$tableName = $this->esc( $type, TRUE );
$columns = $this->getColumns( $type );
$indexes = $this->getIndexes( $type );
$keys = $this->getKeyMapForType( $type );
$table = array(
'columns' => $columns,
'indexes' => $indexes,
'keys' => $keys,
'name' => $tableName
);
$this->tableArchive[$tableName] = $table;
return $table;
}
/**
* Puts a table. Updates the table structure.
* In SQLite we can't change columns, drop columns, change or add foreign keys so we
* have a table-rebuild function. You simply load your table with getTable(), modify it and
* then store it with putTable()...
*
* @param array $tableMap information array
*
* @return void
*/
protected function putTable( $tableMap )
{
$table = $tableMap['name'];
$q = array();
$q[] = "DROP TABLE IF EXISTS tmp_backup;";
$oldColumnNames = array_keys( $this->getColumns( $table ) );
foreach ( $oldColumnNames as $k => $v ) $oldColumnNames[$k] = "`$v`";
$q[] = "CREATE TEMPORARY TABLE tmp_backup(" . implode( ",", $oldColumnNames ) . ");";
$q[] = "INSERT INTO tmp_backup SELECT * FROM `$table`;";
$q[] = "PRAGMA foreign_keys = 0 ";
$q[] = "DROP TABLE `$table`;";
$newTableDefStr = '';
foreach ( $tableMap['columns'] as $column => $type ) {
if ( $column != 'id' ) {
$newTableDefStr .= sprintf( $this->getDDLTemplate( 'widenColumn', $table, $column ), $column, $type );
}
}
$fkDef = '';
foreach ( $tableMap['keys'] as $key ) {
$fkDef .= ", FOREIGN KEY(`{$key['from']}`)
REFERENCES `{$key['table']}`(`{$key['to']}`)
ON DELETE {$key['on_delete']} ON UPDATE {$key['on_update']}";
}
$q[] = "CREATE TABLE `$table` ( `id` INTEGER PRIMARY KEY AUTOINCREMENT $newTableDefStr $fkDef );";
foreach ( $tableMap['indexes'] as $name => $index ) {
if ( strpos( $name, 'UQ_' ) === 0 ) {
$cols = explode( '__', substr( $name, strlen( 'UQ_' . $table ) ) );
foreach ( $cols as $k => $v ) $cols[$k] = "`$v`";
$q[] = "CREATE UNIQUE INDEX $name ON `$table` (" . implode( ',', $cols ) . ")";
} else $q[] = "CREATE INDEX $name ON `$table` ({$index['name']}) ";
}
$q[] = "INSERT INTO `$table` SELECT * FROM tmp_backup;";
$q[] = "DROP TABLE tmp_backup;";
$q[] = "PRAGMA foreign_keys = 1 ";
foreach ( $q as $sq ) $this->adapter->exec( $sq );
}
/**
* Returns the an array describing the indexes for type $type.
*
* @param string $type type to describe indexes of
*
* @return array
*/
protected function getIndexes( $type )
{
$table = $this->esc( $type, TRUE );
$indexes = $this->adapter->get( "PRAGMA index_list('$table')" );
$indexInfoList = array();
foreach ( $indexes as $i ) {
$indexInfoList[$i['name']] = $this->adapter->getRow( "PRAGMA index_info('{$i['name']}') " );
$indexInfoList[$i['name']]['unique'] = $i['unique'];
}
return $indexInfoList;
}
/**
* Adds a foreign key to a type.
* Note: cant put this in try-catch because that can hide the fact
* that database has been damaged.
*
* @param string $type type you want to modify table of
* @param string $targetType target type
* @param string $field field of the type that needs to get the fk
* @param string $targetField field where the fk needs to point to
* @param integer $buildopt 0 = NO ACTION, 1 = ON DELETE CASCADE
*
* @return boolean
*/
protected function buildFK( $type, $targetType, $property, $targetProperty, $constraint = FALSE )
{
$table = $this->esc( $type, TRUE );
$targetTable = $this->esc( $targetType, TRUE );
$column = $this->esc( $property, TRUE );
$targetColumn = $this->esc( $targetProperty, TRUE );
$tables = $this->getTables();
if ( !in_array( $targetTable, $tables ) ) return FALSE;
if ( !is_null( $this->getForeignKeyForTypeProperty( $table, $column ) ) ) return FALSE;
$t = $this->getTable( $table );
$consSQL = ( $constraint ? 'CASCADE' : 'SET NULL' );
$label = 'from_' . $column . '_to_table_' . $targetTable . '_col_' . $targetColumn;
$t['keys'][$label] = array(
'table' => $targetTable,
'from' => $column,
'to' => $targetColumn,
'on_update' => $consSQL,
'on_delete' => $consSQL
);
$this->putTable( $t );
return TRUE;
}
/**
* @see AQueryWriter::getKeyMapForType
*/
protected function getKeyMapForType( $type )
{
$table = $this->esc( $type, TRUE );
$keys = $this->adapter->get( "PRAGMA foreign_key_list('$table')" );
$keyInfoList = array();
foreach ( $keys as $k ) {
$label = $this->makeFKLabel( $k['from'], $k['table'], $k['to'] );
$keyInfoList[$label] = array(
'name' => $label,
'from' => $k['from'],
'table' => $k['table'],
'to' => $k['to'],
'on_update' => $k['on_update'],
'on_delete' => $k['on_delete']
);
}
return $keyInfoList;
}
/**
* Constructor
* Most of the time, you do not need to use this constructor,
* since the facade takes care of constructing and wiring the
* RedBeanPHP core objects. However if you would like to
* assemble an OODB instance yourself, this is how it works:
*
* Usage:
*
* <code>
* $database = new RPDO( $dsn, $user, $pass );
* $adapter = new DBAdapter( $database );
* $writer = new PostgresWriter( $adapter );
* $oodb = new OODB( $writer, FALSE );
* $bean = $oodb->dispense( 'bean' );
* $bean->name = 'coffeeBean';
* $id = $oodb->store( $bean );
* $bean = $oodb->load( 'bean', $id );
* </code>
*
* The example above creates the 3 RedBeanPHP core objects:
* the Adapter, the Query Writer and the OODB instance and
* wires them together. The example also demonstrates some of
* the methods that can be used with OODB, as you see, they
* closely resemble their facade counterparts.
*
* The wiring process: create an RPDO instance using your database
* connection parameters. Create a database adapter from the RPDO
* object and pass that to the constructor of the writer. Next,
* create an OODB instance from the writer. Now you have an OODB
* object.
*
* @param Adapter $adapter Database Adapter
*/
public function __construct( Adapter $adapter )
{
$this->typeno_sqltype = array(
SQLiteT::C_DATATYPE_INTEGER => 'INTEGER',
SQLiteT::C_DATATYPE_NUMERIC => 'NUMERIC',
SQLiteT::C_DATATYPE_TEXT => 'TEXT',
);
$this->sqltype_typeno = array();
foreach ( $this->typeno_sqltype as $k => $v ) {
$this->sqltype_typeno[$v] = $k;
}
$this->adapter = $adapter;
$this->adapter->setOption( 'setInitQuery', ' PRAGMA foreign_keys = 1 ' );
}
/**
* This method returns the datatype to be used for primary key IDS and
* foreign keys. Returns one if the data type constants.
*
* @return integer $const data type to be used for IDS.
*/
public function getTypeForID()
{
return self::C_DATATYPE_INTEGER;
}
/**
* @see QueryWriter::scanType
*/
public function scanType( $value, $flagSpecial = FALSE )
{
$this->svalue = $value;
if ( $value === NULL ) return self::C_DATATYPE_INTEGER;
if ( $value === INF ) return self::C_DATATYPE_TEXT;
if ( $this->startsWithZeros( $value ) ) return self::C_DATATYPE_TEXT;
if ( $value === TRUE || $value === FALSE ) return self::C_DATATYPE_INTEGER;
if ( is_numeric( $value ) && ( intval( $value ) == $value ) && $value < 2147483648 && $value > -2147483648 ) return self::C_DATATYPE_INTEGER;
if ( ( is_numeric( $value ) && $value < 2147483648 && $value > -2147483648)
|| preg_match( '/\d{4}\-\d\d\-\d\d/', $value )
|| preg_match( '/\d{4}\-\d\d\-\d\d\s\d\d:\d\d:\d\d/', $value )
) {
return self::C_DATATYPE_NUMERIC;
}
return self::C_DATATYPE_TEXT;
}
/**
* @see QueryWriter::addColumn
*/
public function addColumn( $table, $column, $type )
{
$column = $this->check( $column );
$table = $this->check( $table );
$type = $this->typeno_sqltype[$type];
$this->adapter->exec( sprintf( $this->getDDLTemplate( 'addColumn', $table, $column ), $table, $column, $type ) );
}
/**
* @see QueryWriter::code
*/
public function code( $typedescription, $includeSpecials = FALSE )
{
$r = ( ( isset( $this->sqltype_typeno[$typedescription] ) ) ? $this->sqltype_typeno[$typedescription] : 99 );
return $r;
}
/**
* @see QueryWriter::widenColumn
*/
public function widenColumn( $type, $column, $datatype )
{
$t = $this->getTable( $type );
$t['columns'][$column] = $this->typeno_sqltype[$datatype];
$this->putTable( $t );
}
/**
* @see QueryWriter::getTables();
*/
public function getTables()
{
return $this->adapter->getCol( "SELECT name FROM sqlite_master
WHERE type='table' AND name!='sqlite_sequence';" );
}
/**
* @see QueryWriter::createTable
*/
public function createTable( $type )
{
$table = $this->esc( $type );
$sql = sprintf( $this->getDDLTemplate( 'createTable', $type ), $table );
$this->adapter->exec( $sql );
}
/**
* @see QueryWriter::getColumns
*/
public function getColumns( $table )
{
$table = $this->esc( $table, TRUE );
$columnsRaw = $this->adapter->get( "PRAGMA table_info('$table')" );
$columns = array();
foreach ( $columnsRaw as $r ) $columns[$r['name']] = $r['type'];
return $columns;
}
/**
* @see QueryWriter::addUniqueIndex
*/
public function addUniqueConstraint( $type, $properties )
{
$tableNoQ = $this->esc( $type, TRUE );
$name = 'UQ_' . $this->esc( $type, TRUE ) . implode( '__', $properties );
$t = $this->getTable( $type );
$t['indexes'][$name] = array( 'name' => $name );
try {
$this->putTable( $t );
} catch( SQLException $e ) {
return FALSE;
}
return TRUE;
}
/**
* @see QueryWriter::sqlStateIn
*/
public function sqlStateIn( $state, $list, $extraDriverDetails = array() )
{
$stateMap = array(
'23000' => QueryWriter::C_SQLSTATE_INTEGRITY_CONSTRAINT_VIOLATION
);
if ( $state == 'HY000'
&& isset($extraDriverDetails[1])
&& $extraDriverDetails[1] == 1
&& ( in_array( QueryWriter::C_SQLSTATE_NO_SUCH_TABLE, $list )
|| in_array( QueryWriter::C_SQLSTATE_NO_SUCH_COLUMN, $list )
)) {
return TRUE;
}
return in_array( ( isset( $stateMap[$state] ) ? $stateMap[$state] : '0' ), $list );
}
/**
* Sets an SQL snippet to be used for the next queryRecord() operation.
* SQLite has no SELECT-FOR-UPDATE and filters this.
*
* @param string $sql SQL snippet to use in SELECT statement.
*
* return self
*/
public function setSQLSelectSnippet( $sqlSelectSnippet = '' ) {
if ( $sqlSelectSnippet === AQueryWriter::C_SELECT_SNIPPET_FOR_UPDATE) $sqlSelectSnippet = '';
$this->sqlSelectSnippet = $sqlSelectSnippet;
return $this;
}
/**
* @see QueryWriter::addIndex
*/
public function addIndex( $type, $name, $column )
{
$columns = $this->getColumns( $type );
if ( !isset( $columns[$column] ) ) return FALSE;
$table = $this->esc( $type );
$name = preg_replace( '/\W/', '', $name );
$column = $this->esc( $column, TRUE );
try {
$t = $this->getTable( $type );
$t['indexes'][$name] = array( 'name' => $column );
$this->putTable( $t );
return TRUE;
} catch( SQLException $exception ) {
return FALSE;
}
}
/**
* @see QueryWriter::wipe
*/
public function wipe( $type )
{
$table = $this->esc( $type );
$this->adapter->exec( "DELETE FROM $table " );
}
/**
* @see QueryWriter::addFK
*/
public function addFK( $type, $targetType, $property, $targetProperty, $isDep = FALSE )
{
return $this->buildFK( $type, $targetType, $property, $targetProperty, $isDep );
}
/**
* @see QueryWriter::wipeAll
*/
public function wipeAll()
{
if (AQueryWriter::$noNuke) throw new \Exception('The nuke() command has been disabled using noNuke() or R::feature(novice/...).');
$this->adapter->exec( 'PRAGMA foreign_keys = 0 ' );
foreach ( $this->getTables() as $t ) {
try { $this->adapter->exec( "DROP TABLE IF EXISTS `$t`" ); } catch ( SQLException $e ) { ; }
try { $this->adapter->exec( "DROP TABLE IF EXISTS `$t`" ); } catch ( SQLException $e ) { ; }
}
$this->adapter->exec( 'PRAGMA foreign_keys = 1 ' );
}
}