git-svn-id: https://svn.fournier38.fr/svn/ProgSVN/trunk@4289 bf3deb0d-5f1a-0410-827f-c0cc1f45334c
3054 lines
102 KiB
PHP
3054 lines
102 KiB
PHP
<?php
|
|
/** DomFramework
|
|
@package domframework
|
|
@author Dominique Fournier <dominique@fournier38.fr> */
|
|
|
|
// dblayeroo.php
|
|
|
|
/** Permit abstraction on the differents SQL databases available */
|
|
class dblayeroo
|
|
{
|
|
/** The table name to use
|
|
*/
|
|
private $table = null;
|
|
/** The tableprefix text to prepend to table name (Should finish by _)
|
|
* Just allow chars !
|
|
*/
|
|
private $tableprefix = "";
|
|
/** The fields with the definition of type, and special parameters
|
|
*/
|
|
private $fields = array ();
|
|
/** The primary field
|
|
*/
|
|
private $primary = null;
|
|
/** An array to define the unique fields (or array of unique fields)
|
|
*/
|
|
private $unique = null;
|
|
/** An array to define the foreign keys of the field
|
|
*/
|
|
private $foreign = array ();
|
|
/** Debug of the SQL
|
|
*/
|
|
private $debug = FALSE;
|
|
/** The connecting DSN
|
|
*/
|
|
private $dsn = null;
|
|
/** The driver to use
|
|
*/
|
|
private $driver = null;
|
|
/** The field group delimiter
|
|
*/
|
|
private $sep = "";
|
|
/** Titles
|
|
*/
|
|
private $titles = array ();
|
|
|
|
/** Limit to one instance of the connection to the same database
|
|
*/
|
|
// Based on an idea of http://tonylandis.com/php/php5-pdo-singleton-class/
|
|
private static $instance = array ();
|
|
|
|
/** Connection to the database engine
|
|
* See http://fr2.php.net/manual/en/pdo.construct.php for the $dsn format
|
|
* @param string $dsn PDO Data Source Name
|
|
* @param string|null $username Username to connect
|
|
* @param string|null $password Password to connect
|
|
* @param string|null $driver_options Driver options to the database
|
|
*/
|
|
public function __construct ($dsn, $username=null, $password=null,
|
|
$driver_options=null)
|
|
{
|
|
$this->connect ($dsn, $username, $password, $driver_options);
|
|
}
|
|
|
|
/** Connection to the database engine
|
|
* See http://fr2.php.net/manual/en/pdo.construct.php for the $dsn format
|
|
* @param string $dsn PDO Data Source Name
|
|
* @param string|null $username Username to connect
|
|
* @param string|null $password Password to connect
|
|
* @param string|null $driver_options Driver options to the database
|
|
*/
|
|
public function connect ($dsn, $username=null, $password=null,
|
|
$driver_options=null)
|
|
/* {{{ */
|
|
{
|
|
if (! function_exists ("mb_strlen"))
|
|
throw new \Exception ("PHP don't have the MB Support. Please add it !",
|
|
500);
|
|
$driver = @explode (":", $dsn);
|
|
if (! isset ($driver[0]))
|
|
$this->DBException (dgettext ("domframework", "No valid DSN provided"));
|
|
$driver[0] = strtolower ($driver[0]);
|
|
if (! in_array ($driver[0], pdo_drivers ()))
|
|
$this->DBException (sprintf (dgettext ("domframework",
|
|
"Driver PDO '%s' not available in PHP"),
|
|
$driver[0]));
|
|
$this->driver = $driver[0];
|
|
// Force specifics initialisations
|
|
$this->dsn = $dsn;
|
|
switch ($driver[0])
|
|
{
|
|
case "sqlite":
|
|
// Look at the right to write in database and in the directory
|
|
$file = substr ($dsn, 7);
|
|
if (! is_writeable (dirname ($file)))
|
|
$this->DBException (dgettext ("domframework",
|
|
"The directory for SQLite database is write protected"));
|
|
if (file_exists ($file) && ! is_writeable ($file))
|
|
$this->DBException (dgettext ("domframework",
|
|
"The SQLite database file is write protected"));
|
|
if (function_exists ("posix_getuid") &&
|
|
file_exists ($file) &&
|
|
fileowner ($file) === posix_getuid ())
|
|
chmod ($file, 0666);
|
|
// Print the instances of PDO objects stored :
|
|
// var_dump (self::$instance);
|
|
if (! array_key_exists ($this->dsn, self::$instance))
|
|
{
|
|
$this->debugLog ("CONNECT TO SQLite DATABASE");
|
|
try
|
|
{
|
|
self::$instance[$this->dsn] = new \PDO ($dsn, $username, $password,
|
|
$driver_options);
|
|
self::$instance[$this->dsn]->setAttribute (\PDO::ATTR_ERRMODE,
|
|
\PDO::ERRMODE_EXCEPTION);
|
|
}
|
|
catch (Exception $e)
|
|
{
|
|
$this->DBException ("PDO error : ".$e->getMessage ());
|
|
}
|
|
}
|
|
// Force ForeignKeys support (disabled by default)
|
|
self::$instance[$this->dsn]->exec ("PRAGMA foreign_keys = ON");
|
|
$this->sep = "`";
|
|
if ($this->databasename () === null)
|
|
$this->DBException ("No Database provided in DSN");
|
|
break;
|
|
case "mysql":
|
|
if (! array_key_exists ($this->dsn, self::$instance))
|
|
{
|
|
$this->debugLog ("CONNECT TO MySQL DATABASE");
|
|
try
|
|
{
|
|
$driver_options[\PDO::MYSQL_ATTR_FOUND_ROWS] = 1;
|
|
self::$instance[$this->dsn] = new \PDO ($dsn, $username, $password,
|
|
$driver_options);
|
|
self::$instance[$this->dsn]->setAttribute (\PDO::ATTR_ERRMODE,
|
|
\PDO::ERRMODE_EXCEPTION);
|
|
}
|
|
catch (Exception $e)
|
|
{
|
|
$this->DBException ("PDO error : ".$e->getMessage ());
|
|
}
|
|
}
|
|
|
|
// Set the coding to UTF8
|
|
self::$instance[$this->dsn]->exec ("SET CHARACTER SET utf8");
|
|
$this->sep = "`";
|
|
if ($this->databasename () === null)
|
|
$this->DBException ("No Database provided in DSN");
|
|
// Force the GROUP_CONCAT value max to the max allowed from the server
|
|
$st = self::$instance[$this->dsn]->query (
|
|
"SHOW VARIABLES LIKE 'max_allowed_packet'", PDO::FETCH_COLUMN, 1);
|
|
$rows = $st->fetchAll ();
|
|
if (! isset ($rows[0]))
|
|
throw new \Exception (
|
|
"Can't read the max_allowed_packet from the MySQL server", 500);
|
|
$max_allowed_packet = $rows[0];
|
|
self::$instance[$this->dsn]->exec (
|
|
"SET SESSION group_concat_max_len = $max_allowed_packet");
|
|
break;
|
|
case "pgsql":
|
|
if (! array_key_exists ($this->dsn, self::$instance))
|
|
{
|
|
$this->debugLog ("CONNECT TO PGSQL DATABASE");
|
|
try
|
|
{
|
|
self::$instance[$this->dsn] = new \PDO ($dsn, $username, $password,
|
|
$driver_options);
|
|
self::$instance[$this->dsn]->setAttribute (\PDO::ATTR_ERRMODE,
|
|
\PDO::ERRMODE_EXCEPTION);
|
|
}
|
|
catch (Exception $e)
|
|
{
|
|
$this->DBException ("PDO error : ".$e->getMessage ());
|
|
}
|
|
}
|
|
|
|
// Set the coding to UTF8
|
|
self::$instance[$this->dsn]->exec ("SET NAMES 'utf8'");
|
|
$this->sep = "\"";
|
|
if ($this->databasename () === null)
|
|
$this->DBException ("No Database provided in DSN");
|
|
break;
|
|
default:
|
|
$this->DBException (dgettext ("domframework",
|
|
"Unknown PDO driver provided"));
|
|
}
|
|
return self::$instance[$this->dsn];
|
|
}
|
|
/* }}} */
|
|
|
|
/** This function disconnect the database. It is normally only used in phpunit
|
|
* unit tests
|
|
*/
|
|
public function disconnect ()
|
|
/* {{{ */
|
|
{
|
|
unset (self::$instance[$this->dsn]);
|
|
self::$sortOrder = 0;
|
|
}
|
|
/* }}} */
|
|
|
|
/** Start a new Transaction
|
|
*/
|
|
public function beginTransaction ()
|
|
/* {{{ */
|
|
{
|
|
return self::$instance[$this->dsn]->beginTransaction ();
|
|
}
|
|
/* }}} */
|
|
|
|
/** Commit (validate) a transaction
|
|
*/
|
|
public function commit ()
|
|
/* {{{ */
|
|
{
|
|
return self::$instance[$this->dsn]->commit ();
|
|
}
|
|
/* }}} */
|
|
|
|
/** RollBack a transaction
|
|
*/
|
|
public function rollback ()
|
|
/* {{{ */
|
|
{
|
|
return self::$instance[$this->dsn]->rollback ();
|
|
}
|
|
/* }}} */
|
|
|
|
/** Return the connected database name from DSN used to connect
|
|
*/
|
|
public function databasename ()
|
|
/* {{{ */
|
|
{
|
|
if ($this->sep === "")
|
|
$this->DBException (dgettext ("domframework", "Database not connected"));
|
|
if ($this->driver === "sqlite")
|
|
{
|
|
$dbFile = substr (strstr ($this->dsn, ":"), 1);
|
|
if (trim ($dbFile) !== "")
|
|
return $dbFile;
|
|
return null;
|
|
}
|
|
$vals = explode (";", substr (strstr ($this->dsn, ":"), 1));
|
|
$dsnExplode = array ();
|
|
foreach ($vals as $val)
|
|
{
|
|
@list ($k, $v) = explode ("=", $val);
|
|
$dsnExplode[$k] = $v;
|
|
}
|
|
if (isset ($dsnExplode["dbname"]))
|
|
return $dsnExplode["dbname"];
|
|
return NULL;
|
|
}
|
|
/* }}} */
|
|
|
|
/** Return all the tables available in the database
|
|
*/
|
|
public function listTables ()
|
|
/* {{{ */
|
|
{
|
|
if ($this->sep === "")
|
|
$this->DBException (dgettext ("domframework", "Database not connected"));
|
|
switch (self::$instance[$this->dsn]->getAttribute (\PDO::ATTR_DRIVER_NAME))
|
|
{
|
|
case "sqlite":
|
|
$req = "SELECT name FROM sqlite_master WHERE type='table'";
|
|
$st = self::$instance[$this->dsn]->prepare ($req);
|
|
$st->execute ();
|
|
$res = array ();
|
|
while ($d = $st->fetch (\PDO::FETCH_ASSOC))
|
|
$res[] = $d["name"];
|
|
break;
|
|
case "mysql":
|
|
$req = "SELECT TABLE_NAME
|
|
FROM information_schema.tables
|
|
WHERE TABLE_SCHEMA='".$this->databasename ()."'";
|
|
$st = self::$instance[$this->dsn]->prepare ($req);
|
|
$st->execute ();
|
|
$res = array ();
|
|
while ($d = $st->fetch (\PDO::FETCH_ASSOC))
|
|
$res[] = $d["TABLE_NAME"];
|
|
break;
|
|
case "pgsql":
|
|
$req = "SELECT *
|
|
FROM pg_tables
|
|
WHERE schemaname = 'public'";
|
|
$st = self::$instance[$this->dsn]->prepare ($req);
|
|
$st->execute ();
|
|
$res = array ();
|
|
while ($d = $st->fetch (\PDO::FETCH_ASSOC))
|
|
$res[] = $d["tablename"];
|
|
break;
|
|
default:
|
|
$this->DBException (dgettext ("domframework",
|
|
"Unknown database driver in listTables"));
|
|
}
|
|
return $res;
|
|
}
|
|
/* }}} */
|
|
|
|
/** Create the table defined by the differents fields.
|
|
* Define the SQL syntax based on SQL engines
|
|
* $table = "dns zones";
|
|
* $fields = array (
|
|
* "id"=>array ("integer", "not null", "autoincrement"),
|
|
* "zo ne"=>array ("varchar(255)", "not null"),
|
|
* "vie wname"=>array ("varchar(255)"),
|
|
* "view clients"=>array ("varchar(255)"),
|
|
* "comme nt"=>array ("varchar(1024)"),
|
|
* "opendate"=>array ("datetime", "not null"),
|
|
* "closedate"=>array ("datetime"),
|
|
* );
|
|
* $primary = "id";
|
|
* $unique = array ("id", array ("zo ne", "vie wname"));
|
|
* $foreign = array ("zone"=>"table.field",...);
|
|
*/
|
|
public function createTable ()
|
|
/* {{{ */
|
|
{
|
|
$this->debugLog ("Entering createTable");
|
|
if ($this->sep === "")
|
|
$this->DBException (dgettext("domframework", "Database not connected"),
|
|
500);
|
|
if (count ($this->fields) === 0)
|
|
$this->DBException (dgettext("domframework", "No Field defined"), 500);
|
|
if ($this->table === null)
|
|
throw new \Exception (dgettext("domframework",
|
|
"No table name defined to create the table"), 500);
|
|
switch (self::$instance[$this->dsn]->getAttribute(\PDO::ATTR_DRIVER_NAME))
|
|
{
|
|
case "sqlite":
|
|
$sql = "CREATE TABLE IF NOT EXISTS ".
|
|
"$this->sep$this->tableprefix$this->table$this->sep ".
|
|
"(\n";
|
|
$i = 0;
|
|
foreach ($this->fields as $field=>$params)
|
|
{
|
|
if ($i > 0)
|
|
$sql .= ",\n";
|
|
// Name of field
|
|
$sql .= "$this->sep$field$this->sep ";
|
|
switch ($this->fieldTypeLight ($field))
|
|
{
|
|
case "datetime":
|
|
$sql .= "DATETIME";
|
|
$params = array_slice ($params, 1);
|
|
break;
|
|
case "date":
|
|
$sql .= "DATE";
|
|
$params = array_slice ($params, 1);
|
|
break;
|
|
case "integer":
|
|
$sql .= "INTEGER";
|
|
$params = array_slice ($params, 1);
|
|
break;
|
|
case "varchar":
|
|
$length = $this->fieldLength ($field);
|
|
$sql .= "VARCHAR($length)";
|
|
$params = array_slice ($params, 1);
|
|
break;
|
|
default:
|
|
$this->DBException (sprintf (
|
|
dgettext("domframework",
|
|
"Unknown type '%s' provided for field '%s'"),
|
|
$this->fieldTypeLight ($field), $field), 500);
|
|
}
|
|
// Primary key
|
|
if ($this->primary === $field)
|
|
$sql .= " PRIMARY KEY";
|
|
// Others parameters for field
|
|
// Sort to put the autoincrement field in front of params, if it is
|
|
// present
|
|
sort ($params);
|
|
foreach ($params as $p)
|
|
{
|
|
switch ($p)
|
|
{
|
|
case "not null": $sql .= " NOT NULL"; break;
|
|
case "autoincrement":
|
|
if ($this->primary !== $field)
|
|
throw new \Exception (sprintf (
|
|
dgettext("domframework",
|
|
"Field '%s' is autoincrement but not primary"),
|
|
$field), 500);
|
|
$sql .= " AUTOINCREMENT";
|
|
break;
|
|
default:
|
|
$this->DBException (sprintf (dgettext("domframework",
|
|
"Unknown additionnal parameter '%s' for field '%s'"),
|
|
$p, $field), 500);
|
|
}
|
|
}
|
|
$i ++;
|
|
}
|
|
// Unique fields
|
|
if ($this->unique !== null)
|
|
{
|
|
if (!is_array ($this->unique))
|
|
$this->DBException (dgettext("domframework",
|
|
"The Unique field definition is not an array"),
|
|
500);
|
|
foreach ($this->unique as $u)
|
|
{
|
|
$sql .= ",\n UNIQUE ($this->sep";
|
|
if (is_array ($u))
|
|
$sql .=implode ("$this->sep,$this->sep", $u);
|
|
else
|
|
$sql .= $u;
|
|
$sql .="$this->sep)";
|
|
}
|
|
}
|
|
// Foreign keys
|
|
$i = 0;
|
|
foreach ($this->foreign as $field=>$k)
|
|
{
|
|
$field = explode (",", $field);
|
|
$field = implode ($this->sep.",".$this->sep, $field);
|
|
$k[1] = explode (",", $k[1]);
|
|
$k[1] = implode ($this->sep.",".$this->sep, $k[1]);
|
|
$sql .= ",\n FOREIGN KEY($this->sep$field$this->sep) ".
|
|
"REFERENCES $this->sep".$k[0]."$this->sep($this->sep".
|
|
$k[1]."$this->sep)";
|
|
if (isset ($k[2]))
|
|
$sql .= " ".$k[2];
|
|
$i++;
|
|
}
|
|
$sql .=")";
|
|
break;
|
|
case "mysql":
|
|
$sql = "CREATE TABLE IF NOT EXISTS ".
|
|
"$this->sep$this->tableprefix$this->table$this->sep ".
|
|
"(\n";
|
|
$i = 0;
|
|
foreach ($this->fields as $field=>$params)
|
|
{
|
|
if ($i > 0)
|
|
$sql .= ",\n";
|
|
// Name of field
|
|
$sql .= "$this->sep$field$this->sep ";
|
|
switch ($this->fieldTypeLight ($field))
|
|
{
|
|
case "integer":
|
|
$sql .= "INTEGER";
|
|
$params = array_slice ($params, 1);
|
|
break;
|
|
case "varchar":
|
|
$length = $this->fieldLength ($field);
|
|
$sql .= "VARCHAR($length)";
|
|
$params = array_slice ($params, 1);
|
|
break;
|
|
case "datetime":
|
|
$sql .= "DATETIME";
|
|
$params = array_slice ($params, 1);
|
|
break;
|
|
case "date":
|
|
$sql .= "DATE";
|
|
$params = array_slice ($params, 1);
|
|
break;
|
|
default:
|
|
$this->DBException (sprintf (
|
|
dgettext("domframework",
|
|
"Unknown type provided for field '%s'"),
|
|
$field), 500);
|
|
}
|
|
// Primary key
|
|
if ($this->primary === $field)
|
|
$sql .= " PRIMARY KEY";
|
|
// Others parameters for field
|
|
// Sort to put the autoincrement field in front of params, if it is
|
|
// present
|
|
sort ($params);
|
|
foreach ($params as $p)
|
|
{
|
|
switch ($p)
|
|
{
|
|
case "not null": $sql .= " NOT NULL"; break;
|
|
case "autoincrement":
|
|
if ($this->primary !== $field)
|
|
throw new \Exception (sprintf (
|
|
dgettext("domframework",
|
|
"Field '%s' is autoincrement but not primary"),
|
|
$field), 500);
|
|
$sql .= " AUTO_INCREMENT";
|
|
break;
|
|
default:
|
|
$this->DBException (sprintf (
|
|
dgettext("domframework",
|
|
"Unknown additionnal '%s' parameter for field '%s'"),
|
|
$p, $field), 500);
|
|
}
|
|
}
|
|
$i ++;
|
|
}
|
|
// Unique fields
|
|
if ($this->unique !== null)
|
|
{
|
|
foreach ($this->unique as $u)
|
|
{
|
|
$sql .= ",\n UNIQUE ($this->sep";
|
|
if (is_array ($u))
|
|
$sql .=implode ("$this->sep,$this->sep", $u);
|
|
else
|
|
$sql .= $u;
|
|
$sql .="$this->sep)";
|
|
}
|
|
}
|
|
// Foreign keys
|
|
$i = 0;
|
|
foreach ($this->foreign as $field=>$k)
|
|
{
|
|
$field = explode (",", $field);
|
|
$field = implode ($this->sep.",".$this->sep, $field);
|
|
$k[1] = explode (",", $k[1]);
|
|
$k[1] = implode ($this->sep.",".$this->sep, $k[1]);
|
|
$sql .= ",\n FOREIGN KEY($this->sep$field$this->sep) ".
|
|
"REFERENCES $this->sep".$k[0]."$this->sep($this->sep".
|
|
$k[1]."$this->sep)";
|
|
if (isset ($k[2]))
|
|
$sql .= " ".$k[2];
|
|
$i++;
|
|
}
|
|
$sql .=") ENGINE=InnoDB DEFAULT CHARSET=utf8;";
|
|
break;
|
|
case "pgsql":
|
|
$sql = "CREATE TABLE IF NOT EXISTS ".
|
|
"\"$this->tableprefix$this->table\" (\n";
|
|
$i = 0;
|
|
foreach ($this->fields as $field=>$params)
|
|
{
|
|
if ($i > 0)
|
|
$sql .= ",\n";
|
|
// Name of field
|
|
$sql .= "\"$field\" ";
|
|
if (in_array ("autoincrement", $params))
|
|
{
|
|
if ($this->primary !== $field)
|
|
throw new \Exception (sprintf (
|
|
dgettext("domframework",
|
|
"Field '%s' is autoincrement but not primary"),
|
|
$field), 500);
|
|
$sql .= "SERIAL";
|
|
}
|
|
else
|
|
{
|
|
switch ($this->fieldTypeLight ($field))
|
|
{
|
|
case "integer":
|
|
$sql .= "INTEGER";
|
|
$params = array_slice ($params, 1);
|
|
break;
|
|
case "varchar":
|
|
$length = $this->fieldLength ($field);
|
|
$sql .= "VARCHAR($length)";
|
|
$params = array_slice ($params, 1);
|
|
break;
|
|
case "datetime":
|
|
$sql .= "timestamp with time zone";
|
|
$params = array_slice ($params, 1);
|
|
break;
|
|
case "date":
|
|
$sql .= "DATE";
|
|
$params = array_slice ($params, 1);
|
|
break;
|
|
default:
|
|
$this->DBException (sprintf (
|
|
dgettext("domframework",
|
|
"Unknown type provided for field '%s'"),
|
|
$field), 500);
|
|
}
|
|
// Primary key
|
|
if ($this->primary === $field)
|
|
$sql .= " PRIMARY KEY";
|
|
// Others parameters for field
|
|
// Sort to put the autoincrement field in front of params, if it is
|
|
// present
|
|
sort ($params);
|
|
foreach ($params as $p)
|
|
{
|
|
switch ($p)
|
|
{
|
|
case "not null": $sql .= " NOT NULL"; break;
|
|
default:
|
|
$this->DBException (sprintf (
|
|
dgettext("domframework",
|
|
"Unknown additionnal parameter '%s' for field '%s'"),
|
|
$p, $field), 500);
|
|
}
|
|
}
|
|
}
|
|
$i ++;
|
|
}
|
|
// Unique fields
|
|
if ($this->unique !== null)
|
|
{
|
|
foreach ($this->unique as $u)
|
|
{
|
|
$sql .= ",\n UNIQUE (\"";
|
|
if (is_array ($u))
|
|
$sql .=implode ("\",\"", $u);
|
|
else
|
|
$sql .= $u;
|
|
$sql .="\")";
|
|
}
|
|
}
|
|
// Foreign keys
|
|
$i = 0;
|
|
foreach ($this->foreign as $field=>$k)
|
|
{
|
|
$field = explode (",", $field);
|
|
$field = implode ($this->sep.",".$this->sep, $field);
|
|
$k[1] = explode (",", $k[1]);
|
|
$k[1] = implode ($this->sep.",".$this->sep, $k[1]);
|
|
$sql .= ",\n FOREIGN KEY(\"$field\") REFERENCES \"".$k[0]."\"(\"".
|
|
$k[1]."\")";
|
|
if (isset ($k[2]))
|
|
$sql .= " ".$k[2];
|
|
$i++;
|
|
}
|
|
$sql .=")";
|
|
break;
|
|
default:
|
|
$this->DBException (dgettext("domframework",
|
|
"PDO Engine not supported in dbLayeroo"), 500);
|
|
}
|
|
|
|
$this->debugLog ($sql);
|
|
return self::$instance[$this->dsn]->exec ($sql);
|
|
}
|
|
/* }}} */
|
|
|
|
/** Drop the table
|
|
*/
|
|
public function dropTable ()
|
|
/* {{{ */
|
|
{
|
|
$this->debugLog ("Entering dropTable ()");
|
|
if ($this->sep === "")
|
|
$this->DBException (dgettext("domframework", "Database not connected"));
|
|
if ($this->table === null)
|
|
throw new \Exception (dgettext("domframework",
|
|
"No table name defined to drop the table"), 500);
|
|
$sql = "DROP TABLE $this->sep$this->tableprefix$this->table$this->sep";
|
|
$this->debugLog ($sql);
|
|
return self::$instance[$this->dsn]->exec ($sql);
|
|
}
|
|
/* }}} */
|
|
|
|
/** Get the informations about a table
|
|
* @param string $tableName The table to examine
|
|
*/
|
|
public function getTableSchema ($tableName)
|
|
/* {{{ */
|
|
{
|
|
$this->debugLog ("Entering getTableSchema (",$tableName,")");
|
|
switch (self::$instance[$this->dsn]->getAttribute(\PDO::ATTR_DRIVER_NAME))
|
|
{
|
|
case "sqlite":
|
|
$st = self::$instance[$this->dsn]->prepare (
|
|
"PRAGMA table_info($tableName)");
|
|
$st->execute ();
|
|
$content = $st->fetchAll (\PDO::FETCH_ASSOC);
|
|
$fields = array ();
|
|
$unique = array ();
|
|
$foreign = array ();
|
|
$primary = "";
|
|
foreach ($content as $row)
|
|
{
|
|
$type = str_replace (" ", "", strtolower ($row["type"]));
|
|
$fields[$row["name"]][] = $type;
|
|
if ($row["notnull"] === "1")
|
|
$fields[$row["name"]][] = "not null";
|
|
if ($row["pk"] === "1")
|
|
$primary = $row["name"];
|
|
}
|
|
|
|
$st = self::$instance[$this->dsn]->prepare (
|
|
"PRAGMA index_list($tableName)");
|
|
$st->execute ();
|
|
$content = $st->fetchAll (\PDO::FETCH_ASSOC);
|
|
foreach ($content as $c)
|
|
{
|
|
$st = self::$instance[$this->dsn]->prepare (
|
|
"PRAGMA index_info(".$c["name"].")");
|
|
$st->execute ();
|
|
$content2 = $st->fetchAll (\PDO::FETCH_ASSOC);
|
|
if (count ($content2) > 1)
|
|
{
|
|
$index = array ();
|
|
foreach ($content2 as $c2)
|
|
{
|
|
$index[] = $c2["name"];
|
|
}
|
|
$unique[$content2[0]["cid"]-1] = $index;
|
|
}
|
|
elseif (count ($content2) === 1)
|
|
{
|
|
$index = $content2[0]["name"];
|
|
$unique[$content2[0]["cid"]-1] = $index;
|
|
}
|
|
}
|
|
ksort ($unique);
|
|
|
|
try
|
|
{
|
|
$st = self::$instance[$this->dsn]->prepare (
|
|
"SELECT * FROM sqlite_sequence WHERE name='$tableName'");
|
|
$st->execute ();
|
|
$content = $st->fetchAll (\PDO::FETCH_ASSOC);
|
|
if (count ($content) > 0 && $primary !== "")
|
|
$fields[$primary][] = "autoincrement";
|
|
}
|
|
catch (\Exception $e)
|
|
{
|
|
// If no autoincrement key, the sqlite_sequence table doesn't exists
|
|
}
|
|
|
|
$st = self::$instance[$this->dsn]->prepare (
|
|
"PRAGMA foreign_key_list('$tableName')");
|
|
$st->execute ();
|
|
$content = $st->fetchAll (\PDO::FETCH_ASSOC);
|
|
foreach ($content as $for)
|
|
{
|
|
$tmp = array ($for["table"], $for["to"]);
|
|
$cascade = "";
|
|
if ($for["on_update"] !== "NO ACTION")
|
|
$cascade .= "ON UPDATE ".$for["on_update"];
|
|
if ($for["on_delete"] !== "NO ACTION")
|
|
$cascade .= "ON DELETE ".$for["on_delete"];
|
|
if ($cascade !== "")
|
|
$tmp[] = $cascade;
|
|
$foreign[$for["from"]] = $tmp;
|
|
}
|
|
return array ("fields" => $fields,
|
|
"primary" => $primary,
|
|
"unique" => $unique,
|
|
"foreign" => $foreign);
|
|
break;
|
|
case "mysql":
|
|
$st = self::$instance[$this->dsn]->prepare (
|
|
"SHOW COLUMNS FROM `$tableName`");
|
|
$st->execute ();
|
|
$content = $st->fetchAll (\PDO::FETCH_ASSOC);
|
|
$fields = array ();
|
|
$unique = array ();
|
|
$foreign = array ("TBD");
|
|
$primary = "";
|
|
foreach ($content as $col)
|
|
{
|
|
$tmp = array ();
|
|
if ($col["Type"] === "int(11)")
|
|
$tmp[] = "integer";
|
|
else
|
|
$tmp[] = $col["Type"];
|
|
if ($col["Null"] === "NO")
|
|
$tmp[] = "not null";
|
|
if ($col["Extra"] === "auto_increment")
|
|
$tmp[] = "autoincrement";
|
|
$fields[$col["Field"]] = $tmp;
|
|
}
|
|
$st = self::$instance[$this->dsn]->prepare (
|
|
"SHOW INDEX FROM `$tableName`");
|
|
$st->execute ();
|
|
$content = $st->fetchAll (\PDO::FETCH_ASSOC);
|
|
foreach ($content as $col)
|
|
{
|
|
if ($col["Key_name"] === "PRIMARY")
|
|
$primary = $col["Column_name"];
|
|
else
|
|
{
|
|
if ($col["Non_unique"] === "1")
|
|
continue;
|
|
if (array_key_exists ($col["Key_name"], $unique))
|
|
{
|
|
if (!is_array ($unique[$col["Key_name"]]))
|
|
$unique[$col["Key_name"]] = array ($unique[$col["Key_name"]]);
|
|
$unique[$col["Key_name"]][] = $col["Column_name"];
|
|
}
|
|
else
|
|
{
|
|
$unique[$col["Key_name"]] = $col["Column_name"];
|
|
}
|
|
}
|
|
}
|
|
$unique = array_values ($unique);
|
|
$st = self::$instance[$this->dsn]->prepare ("
|
|
SELECT UPDATE_RULE,DELETE_RULE,COLUMN_NAME,
|
|
kColUsage.REFERENCED_TABLE_NAME,REFERENCED_COLUMN_NAME
|
|
FROM information_schema.REFERENTIAL_CONSTRAINTS AS rCons,
|
|
information_schema.KEY_COLUMN_USAGE as kColUsage
|
|
WHERE rCons.CONSTRAINT_SCHEMA=:dbname AND rCons.TABLE_NAME=:table
|
|
AND rCons.CONSTRAINT_NAME=kColUsage.CONSTRAINT_NAME");
|
|
$st->execute (array(':dbname' => $this->databasename (),
|
|
':table' => $tableName));
|
|
$foreignTmp = $st->fetchAll (\PDO::FETCH_ASSOC);
|
|
$foreign = array ();
|
|
foreach ($foreignTmp as $f)
|
|
{
|
|
$tmp = array ();
|
|
$tmp[] = $f["REFERENCED_TABLE_NAME"];
|
|
$tmp[] = $f["REFERENCED_COLUMN_NAME"];
|
|
$tmp[2] = "";
|
|
if ($f["UPDATE_RULE"] !== "NO ACTION" &&
|
|
$f["UPDATE_RULE"] !== "RESTRICT")
|
|
$tmp[2] .= "ON UPDATE ".$f["UPDATE_RULE"]." ";
|
|
if ($f["DELETE_RULE"] !== "NO ACTION" &&
|
|
$f["DELETE_RULE"] !== "RESTRICT")
|
|
$tmp[2] .= "ON DELETE ".$f["DELETE_RULE"];
|
|
if ($tmp[2] !== "")
|
|
$tmp[2] = trim ($tmp[2]);
|
|
else
|
|
unset ($tmp[2]);
|
|
$foreign[$f["COLUMN_NAME"]] = $tmp;
|
|
}
|
|
return array ("fields" => $fields,
|
|
"primary" => $primary,
|
|
"unique" => $unique,
|
|
"foreign" => $foreign);
|
|
break;
|
|
case "pgsql":
|
|
$st = self::$instance[$this->dsn]->prepare (
|
|
"SELECT *
|
|
FROM information_schema.columns
|
|
WHERE table_schema='public' and table_name='$tableName'");
|
|
$st->execute ();
|
|
$content = $st->fetchAll (\PDO::FETCH_ASSOC);
|
|
$fields = array ();
|
|
$unique = array ();
|
|
$foreign = array ();
|
|
$primary = "";
|
|
foreach ($content as $col)
|
|
{
|
|
$tmp = array ();
|
|
if ($col["data_type"] === "character varying")
|
|
$tmp[] = "varchar(".$col["character_maximum_length"].")";
|
|
else
|
|
$tmp[] = $col["data_type"];
|
|
if ($col["is_nullable"] === "NO")
|
|
$tmp[] = "not null";
|
|
if (substr ($col["column_default"], 0, 7) === "nextval")
|
|
{
|
|
$tmp[] = "autoincrement";
|
|
$primary = $col["column_name"];
|
|
}
|
|
$fields[$col["column_name"]] = $tmp;
|
|
}
|
|
|
|
$st = self::$instance[$this->dsn]->prepare (
|
|
"select *
|
|
from information_schema.constraint_column_usage
|
|
where table_name='$tableName'");
|
|
$st->execute ();
|
|
$content = $st->fetchAll (\PDO::FETCH_ASSOC);
|
|
foreach ($content as $col)
|
|
{
|
|
if (array_key_exists ($col["constraint_name"], $unique))
|
|
{
|
|
if (! is_array ($unique[$col["constraint_name"]]))
|
|
$unique[$col["constraint_name"]] =
|
|
array ($unique[$col["constraint_name"]]);
|
|
$unique[$col["constraint_name"]][] = $col["column_name"];
|
|
}
|
|
else
|
|
{
|
|
$unique[$col["constraint_name"]] = $col["column_name"];
|
|
}
|
|
}
|
|
$unique = array_values ($unique);
|
|
$st = self::$instance[$this->dsn]->prepare ("
|
|
SELECT kColUsage1.column_name COLUMN_NAME,
|
|
kColUsage2.table_name REFERENCED_TABLE_NAME,
|
|
kColUsage2.column_name REFERENCED_COLUMN_NAME,
|
|
update_rule,delete_rule
|
|
FROM information_schema.KEY_COLUMN_USAGE AS kColUsage1,
|
|
information_schema.KEY_COLUMN_USAGE AS kColUsage2,
|
|
information_schema.REFERENTIAL_CONSTRAINTS AS rCons
|
|
WHERE kColUsage1.table_catalog=:dbname AND kColUsage1.table_name=:table
|
|
AND rCons.constraint_name=kColUsage1.constraint_name
|
|
AND rCons.unique_constraint_name=kColUsage2.constraint_name
|
|
");
|
|
$st->execute (array(':dbname' => $this->databasename (),
|
|
':table' => $tableName));
|
|
$foreignTmp = $st->fetchAll (\PDO::FETCH_ASSOC);
|
|
$foreign = array ();
|
|
foreach ($foreignTmp as $f)
|
|
{
|
|
$tmp = array ();
|
|
$tmp[] = $f["referenced_table_name"];
|
|
$tmp[] = $f["referenced_column_name"];
|
|
$tmp[2] = "";
|
|
if ($f["update_rule"] !== "NO ACTION" &&
|
|
$f["update_rule"] !== "RESTRICT")
|
|
$tmp[2] .= "ON UPDATE ".$f["update_rule"]." ";
|
|
if ($f["delete_rule"] !== "NO ACTION" &&
|
|
$f["delete_rule"] !== "RESTRICT")
|
|
$tmp[2] .= "ON DELETE ".$f["delete_rule"];
|
|
if ($tmp[2] !== "")
|
|
$tmp[2] = trim ($tmp[2]);
|
|
else
|
|
unset ($tmp[2]);
|
|
$foreign[$f["column_name"]] = $tmp;
|
|
}
|
|
|
|
return array ("fields" => $fields,
|
|
"primary" => $primary,
|
|
"unique" => $unique,
|
|
"foreign" => $foreign);
|
|
break;
|
|
default:
|
|
$this->DBException (dgettext("domframework",
|
|
"PDO Engine not supported in dbLayeroo"), 500);
|
|
}
|
|
}
|
|
/* }}} */
|
|
|
|
/** Return the type of the provided field
|
|
* @param string $field The field to get the type
|
|
*/
|
|
private function fieldTypeComplete ($field)
|
|
/* {{{ */
|
|
{
|
|
// $this->debugLog ("Entering fieldTypeComplete (",$field,")");
|
|
if (! array_key_exists ($field, $this->fields))
|
|
$this->DBException (sprintf (
|
|
"fieldType : can't find the definition for field '%s'",
|
|
$field));
|
|
if (! array_key_exists (0, $this->fields[$field]))
|
|
$this->DBException (sprintf (
|
|
"fieldType : can't find the type for field '%s'",
|
|
$field));
|
|
if (! is_string ($this->fields[$field][0]))
|
|
$this->DBException (sprintf (
|
|
"fieldType : The type of field '%s' is not a string",
|
|
$field));
|
|
$type = strtolower ($this->fields[$field][0]);
|
|
$type = str_replace (" ", "", $type);
|
|
return $type;
|
|
}
|
|
/* }}} */
|
|
|
|
/** Return the type of the provided field. For varchar(255), return only
|
|
* varchar
|
|
* @param string $field The field to get the type
|
|
*/
|
|
private function fieldTypeLight ($field)
|
|
/* {{{ */
|
|
{
|
|
$type = $this->fieldTypeComplete ($field);
|
|
list ($type, ) = explode ("(", $type);
|
|
return $type;
|
|
}
|
|
/* }}} */
|
|
|
|
/** Return the length of a field (generally a varchar)
|
|
* @param string $field The field to get the type
|
|
*/
|
|
private function fieldLength ($field)
|
|
/* {{{ */
|
|
{
|
|
$type = $this->fieldTypeComplete ($field);
|
|
$pos = strpos ($type, "(");
|
|
if ($pos === false)
|
|
$this->DBException (sprintf (
|
|
"fieldLength : no length defined for field '%s'", $field));
|
|
$length = intval (substr ($type, 1+$pos, -1));
|
|
if ($length === 0)
|
|
$this->DBException (sprintf (
|
|
"fieldLength : Length equal to Zero for field '%s'", $field));
|
|
return $length;
|
|
}
|
|
/* }}} */
|
|
|
|
/////////////////////////////
|
|
/// GETTERS / SETTERS ///
|
|
/////////////////////////////
|
|
/** Get/Set the table property
|
|
* @param string|null $table The table to use
|
|
*/
|
|
public function table ($table=null)
|
|
/* {{{ */
|
|
{
|
|
$this->debugLog ("Entering table (",$table,")");
|
|
if ($table === null)
|
|
return $this->table;
|
|
if (! is_string ($table))
|
|
$this->DBException ("Parameter table invalid: not a string");
|
|
if (mb_strlen ($table) > 63)
|
|
$this->DBException ("Parameter table invalid: too long");
|
|
$this->table = $table;
|
|
return $this;
|
|
}
|
|
/* }}} */
|
|
|
|
/** Get/Set the tableprefix property
|
|
* @param string|null $tableprefix The prefix to append
|
|
*/
|
|
public function tableprefix ($tableprefix=null)
|
|
/* {{{ */
|
|
{
|
|
$this->debugLog ("Entering tableprefix (",$tableprefix,")");
|
|
if ($tableprefix === null)
|
|
return $this->tableprefix;
|
|
if (! is_string ($tableprefix))
|
|
$this->DBException ("Parameter tableprefix invalid: not a string");
|
|
// 64 - at least one char for the table name
|
|
if (mb_strlen ($tableprefix) > 63)
|
|
$this->DBException ("Parameter tableprefix invalid: too long");
|
|
$this->tableprefix = $tableprefix;
|
|
return $this;
|
|
}
|
|
/* }}} */
|
|
|
|
/** Get/Set the fields property
|
|
* The fields to define are in the format:
|
|
* array ("fieldName"=>array ("type"[, "not null"[, "autoincrement"]]))
|
|
* @param array|null $fields The fields to define
|
|
*/
|
|
public function fields ($fields=null)
|
|
/* {{{ */
|
|
{
|
|
$this->debugLog ("Entering fields (VALUE)");
|
|
if ($fields === null)
|
|
return $this->fields;
|
|
if (! is_array ($fields))
|
|
$this->DBException ("Parameter fields invalid: not an array");
|
|
foreach ($fields as $field=>$params)
|
|
{
|
|
if (mb_strlen ($field) > 64)
|
|
$this->DBException ("Parameter fields invalid: column name too long");
|
|
if (! is_array ($params))
|
|
$this->DBException ("Parameter fields invalid: ".
|
|
"param not an array for '$field'");
|
|
if (array_key_exists (0, $params))
|
|
$fields[$field][0] = strtolower ($fields[$field][0]);
|
|
if (array_key_exists (1, $params))
|
|
$fields[$field][1] = strtolower ($fields[$field][1]);
|
|
if (array_key_exists (2, $params))
|
|
$fields[$field][2] = strtolower ($fields[$field][2]);
|
|
}
|
|
foreach ($fields as $field=>$params)
|
|
{
|
|
if (! array_key_exists (0, $params))
|
|
$this->DBException ("Parameter fields invalid: ".
|
|
"No type of column provided for '$field'");
|
|
if (preg_match ("#^(date|datetime|integer|time|".
|
|
"varchar\(\d+\))$#i",
|
|
$params[0]) !== 1)
|
|
$this->DBException ("Parameter fields invalid: ".
|
|
"Unknown column type provided for '$field'");
|
|
if (array_key_exists (1, $params) &&
|
|
$params[1] !== "not null" && $params[1] !== "autoincrement")
|
|
$this->DBException ("Parameter fields invalid: ".
|
|
"Second parameter invalid for '$field'");
|
|
if (array_key_exists (2, $params) &&
|
|
$params[2] !== "autoincrement")
|
|
$this->DBException ("Parameter fields invalid: ".
|
|
"Third parameter invalid for '$field'");
|
|
if ($params[0] !== "integer" && (
|
|
isset ($params[1]) && $params[1] === "autoincrement" ||
|
|
isset ($params[2]) && $params[2] === "autoincrement"))
|
|
$this->DBException ("Parameter fields invalid: ".
|
|
"Field '$field' can not be autoincrement and not integer");
|
|
if (array_key_exists (0, $params))
|
|
$fields[$field][0] = strtolower ($fields[$field][0]);
|
|
if (array_key_exists (1, $params))
|
|
$fields[$field][1] = strtolower ($fields[$field][1]);
|
|
if (array_key_exists (2, $params))
|
|
$fields[$field][2] = strtolower ($fields[$field][2]);
|
|
}
|
|
$this->fields = $fields;
|
|
return $this;
|
|
}
|
|
/* }}} */
|
|
|
|
/** Get all the fields with the table name if needed.
|
|
* If the objectJoin is set, return the fields name too
|
|
* @param boolean|null $full Add the table name if the $full is set
|
|
*/
|
|
public function fieldsAll ($full = false)
|
|
/* {{{ */
|
|
{
|
|
$fields = array ();
|
|
if ($this->joinObject)
|
|
{
|
|
foreach ($this->joinObject as $obj)
|
|
$fields = array_merge ($fields, $obj->fieldsAll (true));
|
|
$full = true;
|
|
}
|
|
foreach ($this->fields as $f=>$val)
|
|
{
|
|
if ($full !== false)
|
|
$fields[$this->sep.$this->tableprefix.$this->table.$this->sep.".".
|
|
$this->sep.$f.$this->sep] = $val;
|
|
else
|
|
$fields[$f] = $val;
|
|
}
|
|
return $fields;
|
|
}
|
|
/* }}} */
|
|
|
|
/** Get/Set the primary property
|
|
* @param string|null $primary The primary key to use
|
|
*/
|
|
public function primary ($primary=null)
|
|
/* {{{ */
|
|
{
|
|
$this->debugLog ("Entering primary (",$primary,")");
|
|
if ($primary === null)
|
|
return $this->primary;
|
|
if (! is_string ($primary))
|
|
$this->DBException ("Parameter primary invalid: not a string");
|
|
if (mb_strlen ($primary) > 64)
|
|
$this->DBException ("Parameter primary invalid: too long");
|
|
if (! array_key_exists ($primary, $this->fields))
|
|
$this->DBException ("Parameter primary invalid: column doesn't exists");
|
|
$this->primary = $primary;
|
|
return $this;
|
|
}
|
|
/* }}} */
|
|
|
|
/** Get/Set the unique property
|
|
* @param array|null $unique The unique fields constraint to add
|
|
*/
|
|
public function unique ($unique=null)
|
|
/* {{{ */
|
|
{
|
|
$this->debugLog ("Entering unique (VALUE)");
|
|
if ($unique === null)
|
|
return $this->unique;
|
|
if (! is_array ($unique))
|
|
$this->DBException ("Parameter unique invalid: not an array");
|
|
foreach ($unique as $u1)
|
|
{
|
|
if (is_string ($u1))
|
|
{
|
|
if (mb_strlen ($u1) > 64)
|
|
$this->DBException ("Parameter unique invalid: too long '$u1'");
|
|
}
|
|
elseif (is_array ($u1))
|
|
{
|
|
foreach ($u1 as $u2)
|
|
{
|
|
if (is_string ($u2))
|
|
{
|
|
if (mb_strlen ($u2) > 64)
|
|
$this->DBException ("Parameter unique invalid: too long '$u2'");
|
|
}
|
|
else
|
|
$this->DBException ("Parameter unique invalid: Not string ".
|
|
"in array");
|
|
}
|
|
}
|
|
else
|
|
$this->DBException ("Parameter unique invalid: Not string nor array: ".
|
|
gettype ($u1));
|
|
}
|
|
$this->unique = $unique;
|
|
return $this;
|
|
}
|
|
/* }}} */
|
|
|
|
/** Get/Set the foreign property
|
|
* @param array|null $foreign The definition of the foreign constraint
|
|
* The format is :
|
|
* array (
|
|
* "field" => array ("parentTable", "parentField", "options if needed"),
|
|
* )
|
|
* Multiple field and parnentField can be provided, separated by comma
|
|
*/
|
|
public function foreign ($foreign=null)
|
|
/* {{{ */
|
|
{
|
|
$this->debugLog ("Entering foreign (VALUE)");
|
|
if ($foreign === null)
|
|
return $this->foreign;
|
|
if (! is_array ($foreign))
|
|
$this->DBException ("Parameter foreign invalid: not an array");
|
|
foreach ($foreign as $cols=>$params)
|
|
{
|
|
foreach (explode (",", $cols) as $col)
|
|
{
|
|
if (! array_key_exists ($col, $this->fields))
|
|
$this->DBException (
|
|
"Parameter foreign invalid: column doesn't exists");
|
|
if (! is_array ($params))
|
|
$this->DBException ("Parameter foreign invalid: ".
|
|
"parameters not in array");
|
|
if (! array_key_exists (0, $params))
|
|
$this->DBException ("Parameter foreign invalid: ".
|
|
"ParentTable is not provided");
|
|
if (! array_key_exists (1, $params))
|
|
$this->DBException ("Parameter foreign invalid: ".
|
|
"ParentField is not provided");
|
|
if (! is_string ($params[0]))
|
|
$this->DBException ("Parameter foreign invalid: ".
|
|
"parameter 0 is not a string");
|
|
if (! is_string ($params[1]))
|
|
$this->DBException ("Parameter foreign invalid: ".
|
|
"parameter 1 is not a string");
|
|
if (mb_strlen ($params[0] > 64))
|
|
$this->DBException ("Parameter foreign invalid: ".
|
|
"parameter 0 is too long");
|
|
if (mb_strlen ($params[1] > 64))
|
|
$this->DBException ("Parameter foreign invalid: ".
|
|
"parameter 1 is too long");
|
|
}
|
|
if (substr_count ($cols, ",") !== substr_count ($params[1], ","))
|
|
$this->DBException ("Parameter foreign invalid: ".
|
|
"Not the same number of comma between the local ".
|
|
"field and the parent field");
|
|
if (array_key_exists (2, $params))
|
|
{
|
|
preg_match_all ("#^(ON UPDATE ".
|
|
"(CASCADE|RESTRICT|NO ACTION|SET DEFAULT|SET NULL) ?)?".
|
|
"(ON DELETE ".
|
|
"(CASCADE|RESTRICT|NO ACTION|SET DEFAULT|SET NULL))?$#",
|
|
$params[2], $matches);
|
|
if ($matches[1] === "" && $matches[3] === "")
|
|
$this->DBException ("Parameter foreign invalid: ".
|
|
"Unknown action provided");
|
|
}
|
|
}
|
|
$this->foreign = $foreign;
|
|
return $this;
|
|
}
|
|
/* }}} */
|
|
|
|
/** Get/Set the debug property
|
|
* @param integer|null $debug Set the debug value
|
|
*/
|
|
public function debug ($debug=null)
|
|
/* {{{ */
|
|
{
|
|
$this->debugLog ("Entering debug (",$debug,")");
|
|
if ($debug === null)
|
|
return $this->debug;
|
|
if (! is_int ($debug))
|
|
$this->DBException ("Parameter debug invalid: not an integer");
|
|
$this->debug = $debug;
|
|
return $this;
|
|
}
|
|
/* }}} */
|
|
|
|
/** Get the sep property
|
|
*/
|
|
public function sep ()
|
|
{
|
|
/* {{{ */
|
|
return $this->sep;
|
|
/* }}} */
|
|
}
|
|
|
|
/** Get/Set the dsn property
|
|
* @param string|null $dsn Set the DSN property of PDO
|
|
*/
|
|
public function dsn ($dsn=null)
|
|
/* {{{ */
|
|
{
|
|
$this->debugLog ("Entering dsn (",$dsn,")");
|
|
if ($dsn === null)
|
|
return $this->dsn;
|
|
if (! is_string ($dsn))
|
|
$this->DBException ("Parameter dsn invalid : not a string");
|
|
$this->dsn = $dsn;
|
|
return $this;
|
|
}
|
|
/* }}} */
|
|
|
|
/** Get/Set the titles property
|
|
* @param array|null $titles The titles of the fields
|
|
* @param boolean|null $full Add the table name if the $full is set
|
|
*/
|
|
public function titles ($titles=null, $full = false)
|
|
/* {{{ */
|
|
{
|
|
$this->debugLog ("Entering titles (VALUE)");
|
|
if ($titles === null)
|
|
{
|
|
if ($full === false && $this->joinObject === null)
|
|
return $this->titles;
|
|
$titles = array ();
|
|
foreach ($this->titles as $key=>$val)
|
|
{
|
|
$titles[$this->tableprefix.$this->table.".".$key] = $val;
|
|
}
|
|
if ($this->joinObject !== null)
|
|
{
|
|
foreach ($this->joinObject as $obj)
|
|
{
|
|
$titles = array_merge ($titles, $obj->titles (null, true));
|
|
}
|
|
}
|
|
return $titles;
|
|
}
|
|
if (! is_array ($titles))
|
|
$this->DBException ("Parameter titles invalid: not an array");
|
|
foreach ($titles as $title=>$translation)
|
|
{
|
|
if (! is_string ($title))
|
|
$this->DBException ("Parameter titles invalid: title not a string");
|
|
if (mb_strlen ($title) > 64)
|
|
$this->DBException ("Parameter titles invalid: title too long");
|
|
if (! is_string ($translation))
|
|
$this->DBException ("Parameter titles invalid: ".
|
|
"translation not a string (table '$this->table',".
|
|
"field '$title')");
|
|
if (mb_strlen ($translation) > 64)
|
|
$this->DBException ("Parameter titles invalid: translation too long");
|
|
}
|
|
$this->titles = $titles;
|
|
return $this;
|
|
}
|
|
/* }}} */
|
|
|
|
/** Dump the configuration of the table in an array
|
|
*/
|
|
public function exportConf ()
|
|
/* {{{ */
|
|
{
|
|
return array ("table" => $this->table,
|
|
"tableprefix" => $this->tableprefix,
|
|
"fields" => $this->fields,
|
|
"primary" => $this->primary,
|
|
"unique" => $this->unique,
|
|
"foreign" => $this->foreign,
|
|
"titles" => $this->titles,
|
|
);
|
|
}
|
|
/* }}} */
|
|
|
|
/////////////////////////////////////
|
|
/// MANAGE THE REQUEST BY OOP ///
|
|
/////////////////////////////////////
|
|
/** The command to use
|
|
*/
|
|
private $command = "";
|
|
/** The DISTINCT option
|
|
*/
|
|
private $distinct = "";
|
|
/** The columns to display in SELECT, with the tables names and the separators
|
|
* correctely defined
|
|
*/
|
|
private $displayColumn = null;
|
|
/** The alias associated to each displayColumn
|
|
*/
|
|
private $displayAlias = array ();
|
|
/** Manage the joins
|
|
*/
|
|
private $joins = array ();
|
|
/** The WHERE expression
|
|
*/
|
|
private $whereExpression = array ();
|
|
/** The values for each parameter for the WHERE condition
|
|
*/
|
|
private $whereValues = array ();
|
|
/** The columns in GROUP BY condition
|
|
*/
|
|
private $groupByExpression = null;
|
|
/** The ORDER expression
|
|
*/
|
|
private $orderExpression = array ();
|
|
/** The LIMIT expression
|
|
*/
|
|
private $limitExpression = "";
|
|
/** The values to SET in INSERT/UPDATE
|
|
*/
|
|
private $setValues = array ();
|
|
/** The types to SET in INSERT/UPDATE
|
|
*/
|
|
private $setType = array ();
|
|
/** The dblayeroo object of the foreign keys tables to check
|
|
*/
|
|
private $setForeignObj = array ();
|
|
|
|
/** If we need to join this object with another one, save the second one in
|
|
* this property
|
|
*/
|
|
private $joinObject;
|
|
|
|
/** The debug depth (as we clone object, the depth is increased to debug
|
|
* easily the functions
|
|
*/
|
|
private $debugDepth = 1;
|
|
|
|
/** The sort order of select/order entries crossed the differents objects
|
|
*/
|
|
private static $sortOrder = 0;
|
|
|
|
/** The method to get a new sorti order acrossed the differents objects
|
|
*/
|
|
public function getSortOrder ()
|
|
/* {{{ */
|
|
{
|
|
++self::$sortOrder;
|
|
return "order".self::$sortOrder;
|
|
}
|
|
/* }}} */
|
|
|
|
/** Reinit the SQL request
|
|
*/
|
|
public function clearRequest ()
|
|
/* {{{ */
|
|
{
|
|
$this->debugLog ("Entering clearRequest ()");
|
|
if ($this->joinObject !== null)
|
|
{
|
|
foreach ($this->joinObject as $obj)
|
|
{
|
|
$obj->clearRequest ();
|
|
}
|
|
$this->joinObject = null;
|
|
}
|
|
$this->command = "";
|
|
$this->distinct = "";
|
|
$this->displayColumn = null;
|
|
$this->displayAlias = array ();
|
|
$this->joins = array ();
|
|
$this->whereExpression = array ();
|
|
$this->whereValues = array ();
|
|
$this->groupByExpression = null;
|
|
$this->orderExpression = array ();
|
|
$this->limitExpression = "";
|
|
$this->setValues = array ();
|
|
$this->setType = array ();
|
|
// The foreign keys can be defined in the constructor for all the
|
|
// modifications checks. No need to remove them
|
|
//$this->setForeignObj = array ();
|
|
}
|
|
/* }}} */
|
|
|
|
/** Define a new foreign object
|
|
* @param object $object The dblayeroo object to use for foreign constraint
|
|
* checks
|
|
*/
|
|
public function setForeignObj ($object)
|
|
/* {{{ */
|
|
{
|
|
$this->debugLog ("Entering setForeignObj (OBJECT)");
|
|
if (! is_object ($object))
|
|
$this->DBException ("Invalid setForeignObj parameter: not an object");
|
|
if (! is_subclass_of ($object, __CLASS__) &&
|
|
get_class ($object) !== "dblayeroo")
|
|
$this->DBException (
|
|
"Invalid object provided to setForeignObj (not dblayeroo object)");
|
|
if (! isset ($object->table))
|
|
$this->DBException (
|
|
"Invalid object provided to setForeignObj (no table defined)");
|
|
$this->setForeignObj[$object->tableprefix.$object->table] = $object;
|
|
return $this;
|
|
}
|
|
/* }}} */
|
|
|
|
/** Define the command to execute. Can be
|
|
* "SELECT", "INSERT", "DELETE", "UPDATE".
|
|
* @param string $command The command to execute
|
|
*/
|
|
public function command ($command)
|
|
/* {{{ */
|
|
{
|
|
$this->debugLog ("Entering command (",$command,")");
|
|
$allowed = array ("SELECT", "INSERT", "DELETE", "UPDATE");
|
|
if (! is_string ($command))
|
|
$this->DBException ("Invalid command provided (not string)");
|
|
$command = strtoupper ($command);
|
|
if (! in_array ($command, $allowed))
|
|
$this->DBException ("Invalid command provided (unknown command)");
|
|
$this->command = $command;
|
|
return $this;
|
|
}
|
|
/* }}} */
|
|
|
|
/** Alias of command ("SELECT")
|
|
*/
|
|
public function select ()
|
|
/* {{{ */
|
|
{
|
|
$this->command = "SELECT";
|
|
return $this;
|
|
}
|
|
/* }}} */
|
|
|
|
/** Alias of command ("INSERT")
|
|
*/
|
|
public function insert ()
|
|
/* {{{ */
|
|
{
|
|
$this->command = "INSERT";
|
|
return $this;
|
|
}
|
|
/* }}} */
|
|
|
|
/** Alias of command ("DELETE")
|
|
*/
|
|
public function delete ()
|
|
/* {{{ */
|
|
{
|
|
$this->command = "DELETE";
|
|
return $this;
|
|
}
|
|
/* }}} */
|
|
|
|
/** Alias of command ("UPDATE")
|
|
*/
|
|
public function update ()
|
|
/* {{{ */
|
|
{
|
|
$this->command = "UPDATE";
|
|
return $this;
|
|
}
|
|
/* }}} */
|
|
|
|
/** Set the DISTINCT option
|
|
*/
|
|
public function setDistinct ()
|
|
/* {{{ */
|
|
{
|
|
$this->distinct = "DISTINCT";
|
|
return $this;
|
|
}
|
|
/* }}} */
|
|
|
|
/** Changing the name displayColumns to displayAdd
|
|
* @param array|string|null $columnNames The columns name, separated by comma
|
|
* @deprecated 0.36
|
|
*/
|
|
public function displayColumn ($columnNames = array ())
|
|
/* {{{ */
|
|
{
|
|
return $this->displayAdd ($columnNames);
|
|
}
|
|
/* }}} */
|
|
|
|
/** Set the columns to display for the next SELECT request.
|
|
* The columns are ordered by the first added to the last added
|
|
* @param array|string|null $columnNames The columns name, separated by comma
|
|
* By default, display all the columns if this method is not called
|
|
* If the value is null or not provided or an empty array, do not display
|
|
* any field
|
|
* @param array|string|null $aliasNames Add the Aliases to the displayed
|
|
* columns
|
|
*/
|
|
public function displayAdd ($columnNames = array (), $aliasNames = array ())
|
|
/* {{{ */
|
|
{
|
|
$this->debugLog ("Entering displayAdd (", $columnNames, ",", $aliasNames,
|
|
")");
|
|
if (! is_string ($columnNames) && ! is_array ($columnNames))
|
|
$this->DBException (
|
|
"Invalid columnNames provided (not string and not array)");
|
|
if (! is_string ($aliasNames) && ! is_array ($aliasNames))
|
|
$this->DBException (
|
|
"Invalid aliasNames provided (not string and not array)");
|
|
if (is_string ($columnNames))
|
|
{
|
|
// Remove the GROUP_CONCAT(col,',') as ',' is the default and the comma
|
|
// will be counted in error
|
|
$columnNames = str_replace (",','", "", $columnNames);
|
|
$columnNames = explode (",", $columnNames);
|
|
}
|
|
if (is_string ($aliasNames))
|
|
$aliasNames = explode (",", $aliasNames);
|
|
if (count ($aliasNames) && count ($aliasNames) !== count ($columnNames))
|
|
$this->DBException (
|
|
"The number of aliasNames are not the same as the number of columns");
|
|
// If there is no provided names, reset all the defined ones
|
|
if (count ($columnNames) === 0)
|
|
$this->displayColumn = array ();
|
|
foreach ($columnNames as $nb => $display)
|
|
{
|
|
if (! is_string ($display))
|
|
$this->DBException (
|
|
"displayAdd: The display column name #$nb is not a string");
|
|
$display = $name = trim ($display);
|
|
$pos = strpos ($display, "(");
|
|
if ($pos !== false)
|
|
{
|
|
$func = strtoupper (trim (substr ($name, 0, $pos)));
|
|
$name = trim (substr ($name, $pos+1, -1));
|
|
$separator = "";
|
|
if (in_array ($func, array ("AVG", "COUNT", "GROUP_CONCAT", "MAX",
|
|
"MIN","SUM")))
|
|
{
|
|
$aggregateFunction = true;
|
|
// Aggregate function. Add the non aggregate fields to the GROUP BY
|
|
// expression, if not already done
|
|
if ($this->groupByExpression === null)
|
|
{
|
|
// Set to empty array to demonstrate that a GROUP BY is needed, but
|
|
// there is no already defined displayed Columns.
|
|
// Used if the group by is called from join object
|
|
if ($this->displayColumn === null)
|
|
$this->groupByExpression = array ();
|
|
else
|
|
$this->groupByExpression = $this->displayColumn;
|
|
}
|
|
if ($func === "GROUP_CONCAT" && ($pos = strpos ($name, ",'")))
|
|
{
|
|
// There is a comma: the developper add the separator string
|
|
$separator = addslashes (substr ($name, $pos + 2, -1));
|
|
$name = substr ($name, 0, $pos);
|
|
if ($this->driver === "sqlite" || $this->driver === "psql")
|
|
$separator = ",'$separator'";
|
|
elseif ($this->driver === "mysql")
|
|
$separator = " SEPARATOR '$separator'";
|
|
}
|
|
if ($func === "GROUP_CONCAT" && $this->driver === "pgsql")
|
|
$func = "string_agg";
|
|
}
|
|
}
|
|
$fieldName = $name;
|
|
$distinct = "";
|
|
if (stripos ($name, "DISTINCT ") === 0)
|
|
{
|
|
$distinct = "DISTINCT ";
|
|
$fieldName = substr ($name, strlen ("DISTINCT "));
|
|
}
|
|
if (! array_key_exists ($fieldName, $this->fields))
|
|
$this->DBException (sprintf (
|
|
"Invalid field to display '%s' : not defined in table", $fieldName));
|
|
$getSortOrder = $this->getSortOrder();
|
|
if (! isset ($func))
|
|
$this->displayColumn[$getSortOrder] =
|
|
$distinct.$this->sep.$fieldName.$this->sep;
|
|
elseif ($func === "string_agg")
|
|
{
|
|
// For Postgres, the entry must be :
|
|
// string_agg(distinct "group"::character varying, ',' order by "group")
|
|
if ($separator == "")
|
|
$separator = ", ','";
|
|
$this->displayColumn[$getSortOrder] =
|
|
"$func($distinct$this->sep$fieldName$this->sep".
|
|
"::character varying$separator ".
|
|
"order by $this->sep$fieldName$this->sep)";
|
|
}
|
|
else
|
|
$this->displayColumn[$getSortOrder] =
|
|
"$func($distinct$this->sep$fieldName$this->sep$separator)";
|
|
if ($this->groupByExpression !== null && ! isset ($aggregateFunction))
|
|
{
|
|
// Not a aggregate function, but groupBy is set : add the new field name
|
|
$this->groupByExpression[$getSortOrder] =
|
|
$this->sep.$fieldName.$this->sep;
|
|
}
|
|
unset ($aggregateFunction);
|
|
unset ($func);
|
|
if (key_exists ($nb, $aliasNames))
|
|
$this->displayAlias[$getSortOrder] = $aliasNames[$nb];
|
|
}
|
|
return $this;
|
|
}
|
|
/* }}} */
|
|
|
|
/** Return the name of the display field, with $this->sep
|
|
* Add the table prefix/name if full is set
|
|
* Allow :
|
|
* name,
|
|
* DISTINCT name,
|
|
* $this->sep.$name.$this->sep,
|
|
* DISTINCT $this->sep.$name.$this->sep,
|
|
* func(name),
|
|
* func(DISTINCT name),
|
|
* func($this->sep.$name.$this->sep),
|
|
* func(DISTINCT $this->sep.$name.$this->sep),
|
|
* func($this->sep.$this->tableprefix.$this->table.$this->sep.".".
|
|
* $this->sep.$name.$this->sep)
|
|
* func(DISTINCT $this->sep.$this->tableprefix.$this->table.$this->sep.".".
|
|
* $this->sep.$name.$this->sep)
|
|
* @param string $name The name of the field
|
|
* @param boolean|null $full Add the table prefix/name if set
|
|
*/
|
|
private function displayConvert ($name, $full = false)
|
|
/* {{{ */
|
|
{
|
|
$pos = strpos ($name, "(");
|
|
if ($pos !== false)
|
|
{
|
|
$func = trim (substr ($name, 0, $pos));
|
|
$name = trim (substr ($name, $pos+1, -1));
|
|
}
|
|
$distinct = "";
|
|
if (strpos ($name, "DISTINCT ") === 0)
|
|
{
|
|
$distinct = "DISTINCT ";
|
|
$name = substr ($name, strlen ("DISTINCT "));
|
|
}
|
|
if ($name[0] !== $this->sep)
|
|
$name = $this->sep.$name;
|
|
if (! isset ($func) && substr ($name, -1) !== $this->sep)
|
|
$name = $name.$this->sep;
|
|
if ($full !== false)
|
|
$name = $this->sep.$this->tableprefix.$this->table.$this->sep.".".$name;
|
|
if (isset ($func))
|
|
$name = "$func($distinct$name)";
|
|
else
|
|
$name = "$distinct$name";
|
|
return $name;
|
|
}
|
|
/* }}} */
|
|
|
|
/** Get the columns set in the query by displayAdd. If the $full parameter
|
|
* is set, add the table prefix/name to the result.
|
|
* If the join object is set, ask to it the columns too
|
|
* @param boolean $full Add the table prefix/name if set
|
|
*/
|
|
public function displayGet ($full = false)
|
|
/* {{{ */
|
|
{
|
|
$columns = array ();
|
|
$displayColumn = array ();
|
|
if ($this->joinObject)
|
|
{
|
|
// The join object will be added at the end
|
|
$full = true;
|
|
}
|
|
// If empty $this->displayColumn list the fields of the table (like
|
|
// tablename.*)
|
|
if ($full !== false)
|
|
{
|
|
if ($this->displayColumn === null)
|
|
{
|
|
foreach (array_keys ($this->fields) as $name)
|
|
{
|
|
$displayColumn[$this->getSortOrder()] = $this->displayConvert ($name);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$displayColumn = $this->displayColumn;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ($this->displayColumn === null)
|
|
{
|
|
foreach (array_keys ($this->fields) as $name)
|
|
{
|
|
$displayColumn[$this->getSortOrder()] = $this->displayConvert ($name);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$displayColumn = $this->displayColumn;
|
|
}
|
|
}
|
|
foreach ($displayColumn as $pos=>$name)
|
|
{
|
|
if ($full !== false)
|
|
$columns[$pos] = $this->displayConvert ($name, true);
|
|
else
|
|
$columns[$pos] = $this->displayConvert ($name);
|
|
if (key_exists ($pos, $this->displayAlias))
|
|
$columns[$pos] .= " AS ".
|
|
$this->sep.$this->displayAlias[$pos].$this->sep;
|
|
}
|
|
if ($this->joinObject)
|
|
{
|
|
foreach ($this->joinObject as $obj)
|
|
$columns = array_merge ($columns, $obj->displayGet (true));
|
|
}
|
|
ksort ($columns, SORT_NATURAL);
|
|
return $columns;
|
|
}
|
|
/* }}} */
|
|
|
|
/** Do a inner join between two dblayer objects
|
|
* The join array is a associated array with local field as key and distant
|
|
* field as value
|
|
* @param object $object The dblayeroo object to use for searching the join
|
|
* data
|
|
* @param array $joinArray The values to search for join
|
|
*/
|
|
public function joinInner ($object, $joinArray)
|
|
/* {{{ */
|
|
{
|
|
$this->debugLog ("Entering joinInner (OBJECT, JOINARRAY)");
|
|
return $this->joinReal ("INNER", $object, $joinArray);
|
|
}
|
|
/* }}} */
|
|
|
|
/** Do a left join between two dblayer objects
|
|
* The join array is a associated array with local field as key and distant
|
|
* field as value
|
|
* @param object $object The dblayeroo object to use for searching the join
|
|
* data
|
|
* @param array $joinArray The values to search for join
|
|
*/
|
|
public function joinLeft ($object, $joinArray)
|
|
/* {{{ */
|
|
{
|
|
$this->debugLog ("Entering joinLeft (OBJECT, JOINARRAY)");
|
|
return $this->joinReal ("LEFT", $object, $joinArray);
|
|
}
|
|
/* }}} */
|
|
|
|
/** Do a right join between two dblayer objects
|
|
* The join array is a associated array with local field as key and distant
|
|
* field as value
|
|
* @param object $object The dblayeroo object to use for searching the join
|
|
* data
|
|
* @param array $joinArray The values to search for join
|
|
*/
|
|
public function joinRight ($object, $joinArray)
|
|
/* {{{ */
|
|
{
|
|
$this->debugLog ("Entering joinRight (OBJECT, JOINARRAY)");
|
|
return $this->joinReal ("RIGHT", $object, $joinArray);
|
|
}
|
|
/* }}} */
|
|
|
|
/** Do the real join
|
|
* @param string $joinType The join type to use ("INNER", "LEFT", "RIGHT")
|
|
* @param object $object The dblayeroo object to use for searching the join
|
|
* data
|
|
* @param array $joinArray The values to search for join
|
|
*/
|
|
private function joinReal ($joinType, $object, $joinArray)
|
|
/* {{{ */
|
|
{
|
|
$this->debugLog ("Entering joinReal (",$joinType,", OBJECT, JOINARRAY)");
|
|
if (! is_string ($joinType))
|
|
$this->DBException ("Invalid joinType provided to join (not string)");
|
|
if (! in_array ($joinType, array ("INNER", "LEFT", "RIGHT")))
|
|
$this->DBException ("Invalid joinType provided to join (not known)");
|
|
if (! is_object ($object))
|
|
$this->DBException ("Invalid object provided to join (not object)");
|
|
if (! is_subclass_of ($object, __CLASS__) &&
|
|
get_class ($object) !== "dblayeroo")
|
|
$this->DBException (
|
|
"Invalid object provided to join (not dblayeroo object)");
|
|
if ($this->dsn !== $object->dsn)
|
|
$this->DBException (
|
|
"DSN different : don't support JOIN between databases");
|
|
|
|
if (! is_array ($joinArray))
|
|
$this->DBException ("Invalid joinArray provided (not array)");
|
|
if (empty ($joinArray))
|
|
$this->DBException ("Invalid joinArray provided (empty array)");
|
|
foreach ($joinArray as $fieldLocal=>$fieldToJoin)
|
|
{
|
|
if (! array_key_exists ($fieldLocal, $this->fields))
|
|
$this->DBException (sprintf (
|
|
"Invalid field to join '%s' : not defined in Local table",
|
|
$fieldLocal));
|
|
if (! array_key_exists ($fieldToJoin, $object->fields))
|
|
$this->DBException (sprintf (
|
|
"Invalid field to join '%s' : not defined in Distant table",
|
|
$fieldToJoin));
|
|
}
|
|
if (! isset ($object->table) || $object->table === null ||
|
|
trim ($object->table) === "")
|
|
$this->DBException ("No table defined in the Join object");
|
|
if (! isset ($this->table) || $this->table === null ||
|
|
trim ($this->table) === "")
|
|
$this->DBException ("No table defined in the local object");
|
|
if (! isset ($object->tableprefix) || $object->tableprefix === null)
|
|
$this->DBException ("No tableprefix defined in the Join object");
|
|
$this->joinObject[] = $object;
|
|
$tmp = "";
|
|
foreach ($joinArray as $fieldLocal=>$fieldToJoin)
|
|
{
|
|
if ($tmp !== "")
|
|
$tmp .= " AND ";
|
|
$tmp .=
|
|
$this->sep.$this->tableprefix.$this->table.$this->sep.".".
|
|
$this->sep.$fieldLocal.$this->sep.
|
|
"=".
|
|
$this->sep.$object->tableprefix.$object->table.$this->sep.".".
|
|
$this->sep.$fieldToJoin.$this->sep;
|
|
}
|
|
// Correct the displayQuery in the main display fields with the display
|
|
// fields of object
|
|
$this->joins[] = "$joinType JOIN ".
|
|
$this->sep.$object->tableprefix.$object->table.$this->sep." ON $tmp";
|
|
return $this;
|
|
}
|
|
/* }}} */
|
|
|
|
/** Get the join SQL part with recursive call of the child joins
|
|
* @param object|null $joinObject The joinObject to examine too
|
|
*/
|
|
public function joinsGet ($joinObject = null)
|
|
/* {{{ */
|
|
{
|
|
$joins = "";
|
|
if ($joinObject === null)
|
|
$joinObject = $this;
|
|
if ($joinObject->joins !== array ())
|
|
$joins = implode ("\n ", $joinObject->joins);
|
|
if ($joinObject->joinObject !== null)
|
|
{
|
|
foreach ($joinObject->joinObject as $obj)
|
|
$joins .= "\n ".$joinObject->joinsGet ($obj);
|
|
}
|
|
return trim ($joins);
|
|
}
|
|
/* }}} */
|
|
|
|
/** Set a new WHERE expression value
|
|
* @param string $field The field to check
|
|
* @param string $operator The operator ("=", "<=", ">=", "!=", "NOT LIKE",
|
|
* "LIKE", "IS NULL", "REGEXP", "NOT REGEXP")
|
|
* @param string|null $value The value to search ("" if not provided)
|
|
*/
|
|
public function whereAdd ($field, $operator, $value = "")
|
|
/* {{{ */
|
|
{
|
|
$this->debugLog ("Entering whereAdd (",$field,", ",$operator,", ",$value,
|
|
")");
|
|
if (! is_string ($field))
|
|
$this->DBException ("Invalid field provided (not string)");
|
|
if (! is_string ($operator))
|
|
$this->DBException ("Invalid operator provided (not string)");
|
|
if (! is_string ($value) && ! is_null ($value) && ! is_integer ($value))
|
|
$this->DBException ("Invalid value provided (not string nor null ".
|
|
"nor integer)");
|
|
if (! array_key_exists ($field, $this->fields))
|
|
$this->DBException (sprintf (
|
|
"Invalid field to whereAdd '%s' : not defined in table", $field));
|
|
$operator = strtoupper ($operator);
|
|
$allowed = array ("=", "<", "<=", ">", ">=", "!=",
|
|
"LIKE", "NOT LIKE", "IS NULL", "IS NOT NULL",
|
|
"REGEXP", "NOT REGEXP");
|
|
if (! in_array ($operator, $allowed))
|
|
$this->DBException ("Invalid operator provided (unknown operator)");
|
|
// TODO : Check if the value is corresponding to the type of the column
|
|
if (count ($this->whereExpression) &&
|
|
end ($this->whereExpression) !== "AND" &&
|
|
end ($this->whereExpression) !== "OR" &&
|
|
end ($this->whereExpression) !== "(")
|
|
$this->whereExpression[] = "AND";
|
|
if ($this->driver === "pgsql" && $operator === "REGEXP")
|
|
$operator = "~";
|
|
if ($this->driver === "pgsql" && $operator === "NOT REGEXP")
|
|
$operator = "!~";
|
|
if ($operator === "IS NULL" || $operator === "IS NOT NULL")
|
|
{
|
|
// Operator without parameter
|
|
$this->whereExpression[] =
|
|
$this->sep.$this->tableprefix.$this->table.$this->sep.".".
|
|
$this->sep.$field.$this->sep." ".$operator;
|
|
}
|
|
else
|
|
{
|
|
// Operator with parameter
|
|
$hash = md5 ("$field, $operator, $value");
|
|
$this->whereExpression[] =
|
|
$this->sep.$this->tableprefix.$this->table.$this->sep.".".
|
|
$this->sep.$field.$this->sep." ".$operator." :$hash";
|
|
$this->whereValues[$hash] = array (
|
|
"field"=>$field,
|
|
"fieldfull"=> $this->tableprefix.$this->table.".".$field,
|
|
"operator"=>$operator,
|
|
"value"=>$value,
|
|
"hash"=>$hash,
|
|
"type"=>$this->fieldTypeLight ($field));
|
|
}
|
|
return $this;
|
|
}
|
|
/* }}} */
|
|
|
|
/** Add a new AND to the WHERE expression
|
|
*/
|
|
public function whereAddAND ()
|
|
/* {{{ */
|
|
{
|
|
$this->debugLog ("Entering whereAddAND ()");
|
|
if (count ($this->whereExpression) === 0)
|
|
$this->DBException ("Can not add AND as there is no previous expression");
|
|
$this->whereExpression[] = "AND";
|
|
return $this;
|
|
}
|
|
/* }}} */
|
|
|
|
/** Add a new OR to the WHERE expression
|
|
*/
|
|
public function whereAddOR ()
|
|
/* {{{ */
|
|
{
|
|
$this->debugLog ("Entering whereAddOR ()");
|
|
if (count ($this->whereExpression) === 0)
|
|
$this->DBException ("Can not add OR as there is no previous expression");
|
|
$this->whereExpression[] = "OR";
|
|
return $this;
|
|
}
|
|
/* }}} */
|
|
|
|
/** Add a new Open Parenthesis to the WHERE expression
|
|
*/
|
|
public function whereAddParenthesisOpen ()
|
|
/* {{{ */
|
|
{
|
|
$this->debugLog ("Entering whereAddParenthesisOpen ()");
|
|
if (count ($this->whereExpression) &&
|
|
end ($this->whereExpression) !== "AND" &&
|
|
end ($this->whereExpression) !== "OR" &&
|
|
end ($this->whereExpression) !== "(")
|
|
$this->whereExpression[] = "AND";
|
|
$this->whereExpression[] = "(";
|
|
return $this;
|
|
}
|
|
/* }}} */
|
|
|
|
/** Add a new Close Parenthesis to the WHERE expression
|
|
*/
|
|
public function whereAddParenthesisClose ()
|
|
/* {{{ */
|
|
{
|
|
$this->debugLog ("Entering whereAddParenthesisClose ()");
|
|
$this->whereExpression[] = ")";
|
|
return $this;
|
|
}
|
|
/* }}} */
|
|
|
|
/** Get the WHERE clause of the object.
|
|
* If the joinObject is set, return all the WHERE clauses
|
|
*/
|
|
public function whereGetExpression ()
|
|
/* {{{ */
|
|
{
|
|
$whereExpression = $this->whereExpression;
|
|
if ($whereExpression === null)
|
|
$whereExpression = array ();
|
|
if ($this->joinObject !== null)
|
|
{
|
|
foreach ($this->joinObject as $obj)
|
|
{
|
|
$exp = $obj->whereGetExpression ();
|
|
if (count ($whereExpression) && count ($exp))
|
|
$whereExpression[] = "AND";
|
|
if (count ($exp))
|
|
$whereExpression[] = "(";
|
|
$whereExpression = array_merge ($whereExpression,
|
|
$exp);
|
|
if (count ($exp))
|
|
$whereExpression[] = ")";
|
|
}
|
|
}
|
|
return $whereExpression;
|
|
}
|
|
/* }}} */
|
|
|
|
/** Get the WHERE values of the object.
|
|
* If the joinObject is set, return all the WHERE clauses with AND and
|
|
* parenthesis
|
|
*/
|
|
public function whereGetValues ()
|
|
/* {{{ */
|
|
{
|
|
$whereValues = $this->whereValues;
|
|
if ($whereValues === null)
|
|
$whereValues = array ();
|
|
if ($this->joinObject !== null)
|
|
{
|
|
foreach ($this->joinObject as $obj)
|
|
{
|
|
$whereValues = array_merge ($whereValues,
|
|
$obj->whereGetValues ());
|
|
}
|
|
}
|
|
return $whereValues;
|
|
}
|
|
/* }}} */
|
|
|
|
/** Add a new ORDER sort. The multiple ORDERS are used from the first added to
|
|
* the last added
|
|
* @param string $field The field to sort
|
|
* @param string|null $sort The sort order ("ASC", "DESC", "NATASC",
|
|
* "NATDESC");
|
|
*/
|
|
public function orderAdd ($field, $sort = "ASC")
|
|
/* {{{ */
|
|
{
|
|
$this->debugLog ("Entering orderAdd (",$field,", ",$sort,")");
|
|
if (! is_string ($field))
|
|
$this->DBException ("Invalid field provided (not string)");
|
|
if (! is_string ($sort))
|
|
$this->DBException ("Invalid sort provided (not string)");
|
|
$sort = strtoupper ($sort);
|
|
if (! in_array ($sort, array ("ASC", "DESC", "NATASC", "NATDESC")))
|
|
$this->DBException (
|
|
"Invalid sort provided (not ASC nor DESC nor NATASC nor NATDESC)");
|
|
if (! array_key_exists ($field, $this->fields) &&
|
|
! in_array ($field, $this->displayAlias))
|
|
$this->DBException (sprintf (
|
|
"Invalid field to orderAdd '%s' : not defined in table nor in alias",
|
|
$field));
|
|
$plus = "";
|
|
if (substr ($sort, 0, 3) === "NAT")
|
|
{
|
|
$plus = "+0";
|
|
$sort = substr ($sort, 3);
|
|
}
|
|
$exp = $this->sep.$field.$this->sep.$plus." ".$sort;
|
|
if ($plus !== "")
|
|
$exp .= ",". $this->sep.$this->table.$this->sep.".".
|
|
$this->sep.$field.$this->sep." ".$sort;
|
|
$this->orderExpression[$this->getSortOrder()] = $exp;
|
|
return $this;
|
|
}
|
|
/* }}} */
|
|
|
|
/** Get the ORDER fields defined. If a joinObject is set with ORDER statement,
|
|
* return the joinObject order with its tableprefix/name in addition of
|
|
* the ones of this object
|
|
* If the parameter $full is set, add the table prefix/name to the result
|
|
* @param boolean|null $full Add the table prefix/name if set
|
|
*/
|
|
public function orderGet ($full=false)
|
|
/* {{{ */
|
|
{
|
|
$order = array ();
|
|
if ($this->joinObject)
|
|
$full = true;
|
|
foreach ($this->orderExpression as $pos=>$o)
|
|
{
|
|
if ($full !== false)
|
|
$order[$pos] = $this->sep.$this->tableprefix.$this->table.$this->sep.
|
|
".".$o;
|
|
else
|
|
$order[$pos] = $o;
|
|
}
|
|
if ($this->joinObject)
|
|
{
|
|
foreach ($this->joinObject as $obj)
|
|
$order = array_merge ($order, $obj->orderGet (true));
|
|
}
|
|
ksort ($order, SORT_NATURAL);
|
|
return $order;
|
|
}
|
|
/* }}} */
|
|
|
|
/** Return true if this object or one of the join objects need GROUP BY SQL
|
|
* part
|
|
*/
|
|
public function groupByNeeded ()
|
|
/* {{{ */
|
|
{
|
|
$needGroupBy = false;
|
|
if ($this->groupByExpression !== null)
|
|
$needGroupBy = true;
|
|
if ($this->joinObject)
|
|
{
|
|
foreach ($this->joinObject as $obj)
|
|
{
|
|
if ($obj->groupByNeeded () === true)
|
|
$needGroupBy = true;
|
|
}
|
|
}
|
|
return $needGroupBy;
|
|
}
|
|
/* }}} */
|
|
|
|
/** Get the GROUP BY fields defined. If a joinObject is set with GROUP BY
|
|
* statement, return the joinObject order with its tableprefix/name in
|
|
* addition of the ones of this object
|
|
* If the parameter $full is set, add the table prefix/name to the result
|
|
* @param boolean|null $full Add the table prefix/name if set
|
|
*/
|
|
public function groupByGet ($full=false)
|
|
/* {{{ */
|
|
{
|
|
if ($this->joinObject)
|
|
$full = true;
|
|
|
|
$localGroupBy = array ();
|
|
// Manage the local object group by entries. In full mode, return the
|
|
// groupByExpression if it is set, or the list of the displayed fields.
|
|
if ($full)
|
|
{
|
|
if ($this->groupByExpression !== null)
|
|
{
|
|
foreach ($this->groupByExpression as $pos=>$o)
|
|
{
|
|
if ($localGroupBy === null)
|
|
$localGroupBy = array ();
|
|
$localGroupBy[$pos] = $this->sep.$this->tableprefix.$this->table.
|
|
$this->sep.".".$o;
|
|
}
|
|
}
|
|
elseif ($this->displayColumn !== null)
|
|
{
|
|
foreach ($this->displayColumn as $name)
|
|
{
|
|
$localGroupBy[] = $this->displayConvert ($name, true);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Nothing to return if there is no groupBy and nothing to display
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ($this->groupByExpression !== null)
|
|
$localGroupBy = $this->groupByExpression;
|
|
else
|
|
$localGroupBy = array ();
|
|
}
|
|
|
|
// Add the distant entries
|
|
$distantGroupBy = array ();
|
|
if ($this->joinObject)
|
|
{
|
|
foreach ($this->joinObject as $obj)
|
|
{
|
|
$ext = $obj->groupByGet (true);
|
|
$distantGroupBy = array_merge ($distantGroupBy, $ext);
|
|
}
|
|
}
|
|
|
|
$groupBy = array_merge ($localGroupBy, $distantGroupBy);
|
|
ksort ($groupBy, SORT_NATURAL);
|
|
return $groupBy;
|
|
}
|
|
/* }}} */
|
|
|
|
/** Define a LIMIT for the request.
|
|
* To use only the nbLines, put a 0 on startLine
|
|
* @param integer $startLine The starting line in the result list
|
|
* @param integer $nbLines The number of lines to return
|
|
*/
|
|
public function limit ($startLine, $nbLines)
|
|
/* {{{ */
|
|
{
|
|
$this->debugLog ("Entering limit (",$startLine,", ",$nbLines,")");
|
|
if (! preg_match ("/^\d+$/", $startLine))
|
|
$this->DBException (sprintf (
|
|
"Invalid startLine to limit '%d': not numerical", $startLine));
|
|
if (! preg_match ("/^\d+$/", $nbLines))
|
|
$this->DBException (sprintf (
|
|
"Invalid nbLines to limit '%d': not numerical", $nbLines));
|
|
$startLine = intval ($startLine);
|
|
$nbLines = intval ($nbLines);
|
|
if ($startLine !== 0)
|
|
$this->limitExpression = "$startLine,$nbLines";
|
|
else
|
|
$this->limitExpression = "$nbLines";
|
|
return $this;
|
|
}
|
|
/* }}} */
|
|
|
|
/** Define a LIMIT for the request.
|
|
* @param integer $nbLines The number of lines to return
|
|
*/
|
|
public function limitLines ($nbLines)
|
|
/* {{{ */
|
|
{
|
|
$this->debugLog ("Entering limitLines (",$nbLines,")");
|
|
if (! preg_match ("/^\d+$/", $nbLines))
|
|
$this->DBException (sprintf (
|
|
"Invalid nbLines to limit '%d': not numerical", $nbLines));
|
|
$nbLines = intval ($nbLines);
|
|
$this->limitExpression = "$nbLines";
|
|
return $this;
|
|
}
|
|
/* }}} */
|
|
|
|
/** Set INSERT/UPDATE values
|
|
* - The provided array must be an associative array, the field name must be
|
|
* provided as key and the value as value. Each field=>val will be updated
|
|
* @param array $values The values to INSERT or UPDATE
|
|
*/
|
|
public function setValues ($values)
|
|
/* {{{ */
|
|
{
|
|
$this->debugLog ("Entering setValues (",$values,")");
|
|
if (! is_array ($values))
|
|
$this->DBException ("Invalid values to setValues : not an array");
|
|
$associative = null;
|
|
$tmpValues = array ();
|
|
$tmpType = array ();
|
|
foreach ($values as $key=>$val)
|
|
{
|
|
if (! array_key_exists ($key, $this->fields))
|
|
$this->DBException (sprintf (
|
|
"Invalid field to setValues '%s' : not defined in table", $key));
|
|
if (! is_string ($val) && ! is_int ($val) && ! is_null ($val))
|
|
$this->DBException (sprintf (
|
|
"Invalid field to setValues '%s': not string and not integer", $key));
|
|
$tmpValues[$key] = $val;
|
|
$tmpType[md5 ("$key, $val")] = $this->fieldTypeLight ($key);
|
|
$this->debugLog ("setValues : Type for $key = ".
|
|
$this->fieldTypeLight ($key));
|
|
}
|
|
$this->setValues = $tmpValues;
|
|
$this->setType = $tmpType;
|
|
return $this;
|
|
}
|
|
/* }}} */
|
|
|
|
/** Create the SQL request
|
|
*/
|
|
private function createRequest ()
|
|
/* {{{ */
|
|
{
|
|
if ($this->sep === "")
|
|
$this->DBException (dgettext ("domframework", "Database not connected"));
|
|
if ($this->table === null)
|
|
$this->DBException (dgettext ("domframework",
|
|
"No table name defined to insert in the table"));
|
|
if ($this->unique === null && $this->primary === null)
|
|
$this->DBException (dgettext ("domframework",
|
|
"Unique fields of table are not defined"));
|
|
if (! is_array ($this->unique) && $this->unique !== null)
|
|
$this->DBException (dgettext ("domframework",
|
|
"The unique configuration is not an array"));
|
|
switch ($this->command)
|
|
{
|
|
case "SELECT":
|
|
$sql = "SELECT";
|
|
if ($this->distinct !== "")
|
|
$sql .= " ".$this->distinct;
|
|
if ($this->joinObject)
|
|
$displayColumns = implode (",", $this->displayGet (true));
|
|
elseif (count ($this->displayGet (false)))
|
|
$displayColumns = implode (",", $this->displayGet (false));
|
|
else
|
|
$displayColumns = "*";
|
|
if ($this->joinObject)
|
|
$order = $this->orderGet (true);
|
|
else
|
|
$order = $this->orderGet (false);
|
|
$sql .= " $displayColumns\n FROM $this->sep$this->tableprefix".
|
|
"$this->table$this->sep";
|
|
$joinsExpression = $this->joinsGet ();
|
|
if ($joinsExpression !== "")
|
|
$sql .= "\n ".$this->joinsGet ();
|
|
$whereGetExpression = $this->whereGetExpression ();
|
|
if (! empty ($whereGetExpression))
|
|
$sql .= "\n WHERE ". implode (" ", $whereGetExpression);
|
|
if ($this->groupByNeeded ())
|
|
{
|
|
$groupByExpression = $this->groupByGet ();
|
|
if (count ($groupByExpression))
|
|
$sql .= "\n GROUP BY ". implode (",", $groupByExpression);
|
|
}
|
|
if (count ($order))
|
|
$sql .= "\n ORDER BY ". implode (",", $order);
|
|
if (! empty ($this->limitExpression))
|
|
$sql .= "\n LIMIT $this->limitExpression";
|
|
// No set Values for SELECT
|
|
$this->setValues = array ();
|
|
break;
|
|
case "INSERT":
|
|
$sql = "INSERT INTO $this->sep$this->tableprefix$this->table$this->sep (";
|
|
if (empty ($this->setValues))
|
|
$this->DBException ("No values set to add in INSERT");
|
|
$i = 0;
|
|
foreach ($this->setValues as $key=>$val)
|
|
{
|
|
if ($i > 0)
|
|
$sql .= ",";
|
|
$sql .= $this->sep.$key.$this->sep;
|
|
$i++;
|
|
}
|
|
$sql .= ") VALUES (";
|
|
$i = 0;
|
|
foreach ($this->setValues as $key=>$val)
|
|
{
|
|
if ($i > 0)
|
|
$sql .= ",";
|
|
$sql .= ":".md5 ("$key, $val");
|
|
$i++;
|
|
}
|
|
$sql .= ")";
|
|
// No WHERE in INSERT : remove the WHERE parameters
|
|
$this->whereExpression = array ();
|
|
$this->whereValues = array ();
|
|
break;
|
|
case "DELETE":
|
|
$sql = "DELETE FROM $this->sep$this->tableprefix$this->table$this->sep";
|
|
$whereGetExpression = $this->whereGetExpression ();
|
|
if (! empty ($whereGetExpression))
|
|
$sql .= "\n WHERE ". implode (" ", $whereGetExpression);
|
|
if (! empty ($this->orderExpression))
|
|
$sql .= " ORDER BY ". implode (",", $this->orderExpression);
|
|
if (! empty ($this->limitExpression))
|
|
$sql .= " LIMIT $this->limitExpression";
|
|
// No set Values for DELETE
|
|
$this->setValues = array ();
|
|
break;
|
|
case "UPDATE":
|
|
$sql = "UPDATE $this->sep$this->tableprefix$this->table$this->sep";
|
|
if (empty ($this->setValues))
|
|
$this-> DBException ("No values to set in UPDATE");
|
|
$sql .= " SET ";
|
|
$i = 0;
|
|
foreach ($this->setValues as $key=>$val)
|
|
{
|
|
if ($i > 0)
|
|
$sql .= ",";
|
|
$hash = md5 ("$key, $val");
|
|
$sql .= $this->sep.$key.$this->sep."=:".$hash;
|
|
$i++;
|
|
}
|
|
$whereGetExpression = $this->whereGetExpression ();
|
|
if (! empty ($whereGetExpression))
|
|
$sql .= "\n WHERE ". implode (" ", $whereGetExpression);
|
|
if (! empty ($this->orderExpression))
|
|
$sql .= " ORDER BY ". implode (",", $this->orderExpression);
|
|
if (! empty ($this->limitExpression))
|
|
$sql .= " LIMIT $this->limitExpression";
|
|
break;
|
|
default:
|
|
$this->DBException ("No command specified");
|
|
}
|
|
return $sql;
|
|
}
|
|
/* }}} */
|
|
|
|
/** Prepare the request with the associated entries.
|
|
* If textForm is true, return a string to display what will be done
|
|
* If textForm is false, return a statement
|
|
* @param string $sql The SQL request to prepare
|
|
* @param boolean $textForm If true, return the result. If false prepare
|
|
* really the request
|
|
*/
|
|
private function prepareRequest ($sql, $textForm)
|
|
/* {{{ */
|
|
{
|
|
$text = "";
|
|
if (!$textForm)
|
|
{
|
|
try
|
|
{
|
|
$st = self::$instance[$this->dsn]->prepare ($sql);
|
|
}
|
|
catch (\Exception $e)
|
|
{
|
|
$this->DBException ($e->getMessage ());
|
|
}
|
|
}
|
|
foreach ($this->whereGetValues () as $hash=>$val)
|
|
{
|
|
$field = $val["field"];
|
|
$value = $val["value"];
|
|
$type = $val["type"];
|
|
$text .= "DEBUG BIND WHERE : $hash ($field)->$value ";
|
|
if ($value === null) $text .= "NULL (null)\n";
|
|
elseif ($type === "integer") $text .= "(integer)\n";
|
|
elseif ($type === "varchar") $text .= "(varchar)\n";
|
|
elseif ($type === "datetime") $text .= "(datetime)\n";
|
|
elseif ($type === "date") $text .= "(date)\n";
|
|
else
|
|
{
|
|
$text .= "(UNKNOWN)\n";
|
|
$this->DBException ("TO BE DEVELOPPED : type=".$type);
|
|
}
|
|
if (!$textForm)
|
|
{
|
|
if ($value === null)
|
|
$st->bindValue (":$hash", $value, \PDO::PARAM_NULL);
|
|
elseif ($type === "integer")
|
|
$st->bindValue (":$hash", $value, \PDO::PARAM_INT);
|
|
elseif ($type === "varchar")
|
|
$st->bindValue (":$hash", "$value", \PDO::PARAM_STR);
|
|
elseif ($type === "datetime")
|
|
$st->bindValue (":$hash", $value, \PDO::PARAM_STR);
|
|
elseif ($type === "date")
|
|
$st->bindValue (":$hash", $value, \PDO::PARAM_STR);
|
|
else
|
|
$this->DBException ("prepareRequest:whereValues TO BE DEVELOPPED : ".
|
|
"type=$type");
|
|
}
|
|
}
|
|
foreach ($this->setValues as $field=>$value)
|
|
{
|
|
$hash = md5 ("$field, $value");
|
|
if (! array_key_exists ($hash, $this->setType))
|
|
$this->DBException (sprintf ("Field '%s' not found in Type list",
|
|
$field));
|
|
$type = $this->setType[$hash];
|
|
$text .= "DEBUG BIND SET : $hash ($field)->$value ";
|
|
if ($value === null) $text .= "NULL (null)\n";
|
|
elseif ($type === "integer") $text .= "(integer)\n";
|
|
elseif ($type === "varchar") $text .= "(varchar)\n";
|
|
elseif ($type === "datetime") $text .= "(datetime)\n";
|
|
elseif ($type === "date") $text .= "(date)\n";
|
|
else
|
|
{
|
|
$text .= "(UNKNOWN)\n";
|
|
$this->DBException ("TO BE DEVELOPPED : ".$fields[$field][0]);
|
|
}
|
|
if (!$textForm)
|
|
{
|
|
if ($value === null)
|
|
$st->bindValue (":$hash", $value, \PDO::PARAM_NULL);
|
|
elseif ($this->setType[$hash] === "integer")
|
|
$st->bindValue (":$hash", $value, \PDO::PARAM_INT);
|
|
elseif ($this->setType[$hash] === "varchar")
|
|
$st->bindValue (":$hash", "$value", \PDO::PARAM_STR);
|
|
elseif ($this->setType[$hash] === "datetime")
|
|
$st->bindValue (":$hash", $value, \PDO::PARAM_STR);
|
|
elseif ($this->setType[$hash] === "date")
|
|
$st->bindValue (":$hash", $value, \PDO::PARAM_STR);
|
|
else
|
|
$this->DBException ("prepareRequest:setValues TO BE DEVELOPPED : ".
|
|
$this->fields[$field][0]);
|
|
}
|
|
}
|
|
if ($textForm)
|
|
return $text;
|
|
else
|
|
return $st;
|
|
}
|
|
/* }}} */
|
|
|
|
/** Return the query that will be executed
|
|
*/
|
|
public function getDisplayQuery ()
|
|
/* {{{ */
|
|
{
|
|
$text = "";
|
|
$sql = $this->createRequest ();
|
|
$text .= "$sql";
|
|
$prep = $this->prepareRequest ($sql, true);
|
|
if ($prep !== "")
|
|
$text .= "\n$prep";
|
|
return $text;
|
|
}
|
|
/* }}} */
|
|
|
|
/** Check the provided values which will be inserted or updated against the
|
|
* database structure.
|
|
* In update, do not forget to define the whereAdd parameters !
|
|
* @param array $values The values to test
|
|
* @param boolean|null $update if true UPDATE request, else INSERT request
|
|
* @return array The errors found by field
|
|
*/
|
|
public function verify ($values, $update = false)
|
|
/* {{{ */
|
|
{
|
|
$update = !! $update;
|
|
$errors = array ();
|
|
if ($this->table === null)
|
|
throw new \Exception ("No table name defined", 500);
|
|
if ($this->primary === null)
|
|
throw new \Exception ("No primary key defined for table '$this->table'",
|
|
500);
|
|
if ($update === false)
|
|
{
|
|
// INSERT mode
|
|
if (! array_key_exists ($this->primary, $values))
|
|
$values[$this->primary] = null;
|
|
// - Look if all the NOT NULL fields are filled
|
|
foreach ($this->fields as $field=>$params)
|
|
{
|
|
if (in_array ("not null", $params) &&
|
|
! array_key_exists ($field, $values))
|
|
$errors[$field] = sprintf (dgettext ("domframework",
|
|
"Mandatory field '%s' not provided"),
|
|
$field);
|
|
elseif (! in_array ("not null", $params) &&
|
|
! array_key_exists ($field, $values))
|
|
continue;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// UPDATE mode
|
|
}
|
|
|
|
// INSERT or UPDATE mode
|
|
// - Check the validity of the content of the fields. Should be already done
|
|
// by the application, so just throw an Exception if the error is raised
|
|
foreach ($this->fields as $field=>$params)
|
|
{
|
|
$this->debugLog (" verify the field validity [$field]");
|
|
if ($update !== false && ! array_key_exists ($field, $values))
|
|
// In case of UPDATE, the values are already stored. We can skip the
|
|
// test if the user don't want to modify a field (and do not provide the
|
|
// value
|
|
continue;
|
|
if (! in_array ("not null", $params) &&
|
|
! array_key_exists ($field, $values))
|
|
continue;
|
|
if (in_array ("not null", $params) &&
|
|
! array_key_exists ($field, $values))
|
|
{
|
|
$errors[$field] = sprintf (dgettext ("domframework",
|
|
"Field '%s' mandatory and not provided"), $field);
|
|
continue;
|
|
}
|
|
if (in_array ("not null", $params) &&
|
|
! in_array ("autoincrement", $params) &&
|
|
$values[$field] === null)
|
|
{
|
|
$errors[$field] = sprintf (dgettext ("domframework",
|
|
"Field '%s' null and defined as NOT NULL and ".
|
|
"not Autoincrement"), $field);
|
|
continue;
|
|
}
|
|
if (! is_string ($values[$field]) && ! is_integer ($values[$field]) &&
|
|
! is_null ($values[$field]))
|
|
$errors[$field] = sprintf (dgettext ("domframework",
|
|
"Field '%s' not a string nor a integer"), $field);
|
|
// Do not check the format if the value to store is null. It will never
|
|
// matche any format.
|
|
if ($values[$field] === null)
|
|
continue;
|
|
switch ($this->fieldTypeLight ($field))
|
|
{
|
|
case "integer":
|
|
if (strspn ($values[$field], "0123456789") !== strlen ($values[$field]))
|
|
$errors[$field] = sprintf (dgettext ("domframework",
|
|
"Field '%s' not in integer format"), $field);
|
|
break;
|
|
case "varchar":
|
|
$length = $this->fieldLength ($field);
|
|
if (mb_strlen ($values[$field]) > $length)
|
|
$errors[$field] = sprintf (dgettext ("domframework",
|
|
"Field '%s' : Data field too long"), $field);
|
|
break;
|
|
case "date":
|
|
if (! preg_match ("#^\d{4}-\d{2}-\d{2}$#", $values[$field]))
|
|
$errors[$field] = sprintf (dgettext ("domframework",
|
|
"Field '%s' not in date format"), $field);
|
|
break;
|
|
case "datetime":
|
|
if (! preg_match ("#^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$#",
|
|
$values[$field]))
|
|
$errors[$field] = sprintf (dgettext ("domframework",
|
|
"Field '%s' not in datetime format"), $field);
|
|
break;
|
|
case "time":
|
|
if (! preg_match ("#^\d{2}:\d{2}:\d{2}$#", $values[$field]))
|
|
$errors[$field] = sprintf (dgettext ("domframework",
|
|
"Field '%s' not in time format"), $field);
|
|
break;
|
|
default:
|
|
$errors[$field] = sprintf (dgettext ("domframework",
|
|
"Field '%s' : invalid SQL type (%s) in ".
|
|
"\$this->fields "), $field,
|
|
$this->fieldTypeLight ($field));
|
|
}
|
|
}
|
|
|
|
// - Check the unique entries (if defined)
|
|
// The primary key is always unique : add it if not set by the user
|
|
$this->debugLog (" verify the unique constraint");
|
|
$uniques = $this->unique;
|
|
if (! is_array ($uniques))
|
|
$uniques = array ();
|
|
if (! in_array ($this->primary, $uniques))
|
|
$uniques = array_merge (array ($this->primary), $uniques);
|
|
$setValues = $values;
|
|
$foundImpactedLines = 0;
|
|
foreach ($uniques as $k=>$columns)
|
|
{
|
|
if ($update)
|
|
{
|
|
// Can not update multiple UNIQUE rows with the same value
|
|
if (is_string ($columns))
|
|
$cols = explode (",", $columns);
|
|
elseif (is_array ($columns))
|
|
$cols = $columns;
|
|
else
|
|
$this->DBException (dgettext ("domframework",
|
|
"Unique def is not a string or an array"));
|
|
foreach ($cols as $col)
|
|
{
|
|
if (!key_exists ($col, $setValues))
|
|
continue;
|
|
// One column to set is a unique column : check if the where clause
|
|
// is filtering more than one entry. If there is more than one
|
|
// entry, generate an exception
|
|
$this->debugLog ("CLONE because of checking update unique fields");
|
|
$objTmp = clone $this;
|
|
$objTmp->debugDepth++;
|
|
$objTmp->clearRequest ();
|
|
$objTmp->Select ();
|
|
$objTmp->displayAdd ($this->primary);
|
|
$objTmp->displayAdd ($columns);
|
|
$objTmp->whereValues = $this->whereValues;
|
|
$objTmp->whereExpression = $this->whereExpression;
|
|
$objTmp->limitLines (3);
|
|
$resUpdate = $objTmp->execute ();
|
|
unset ($objTmp);
|
|
if (count ($resUpdate) > 1)
|
|
$this->DBException (sprintf (dgettext ("domframework",
|
|
"Can't update multiple rows with unique value on col '%s'"),
|
|
$col));
|
|
elseif (count ($resUpdate) === 1)
|
|
$foundImpactedLines++;
|
|
}
|
|
if ($foundImpactedLines === 0)
|
|
{
|
|
// There is no row available with the WHERE clause provided
|
|
// Skip all the UNIQUE tests as there will not have any modification
|
|
break;
|
|
}
|
|
}
|
|
if (! array_key_exists ($this->primary, $setValues))
|
|
$setValues[$this->primary] = null;
|
|
|
|
$this->debugLog ("CLONE to check primary and unique constraint");
|
|
$objTmp = clone $this;
|
|
$objTmp->debugDepth++;
|
|
$objTmp->clearRequest ();
|
|
$objTmp->Select ();
|
|
$objTmp->displayAdd ($this->primary);
|
|
if (is_array ($columns))
|
|
{
|
|
// Multiple columns in unique
|
|
$objTmp->debugLog (" verify the unique multiple [",
|
|
implode (",", $columns),"]");
|
|
foreach ($columns as $column)
|
|
{
|
|
if (! array_key_exists ($column, $setValues))
|
|
{
|
|
if ($update !== false && array_key_exists (0, $resUpdate))
|
|
{
|
|
// In UPDATE, if a column is not modified (doesn't appears in
|
|
// setValues), use the old value to search
|
|
$valTest = $resUpdate[0][$column];
|
|
$objTmp->whereAdd ($column, "=", $valTest);
|
|
}
|
|
else
|
|
$errors[$column] = sprintf (dgettext ("domframework",
|
|
"No column '%s' defined but must be unique !"), $column);
|
|
}
|
|
else
|
|
$objTmp->whereAdd ($column, "=", $setValues[$column]);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// One column in unique
|
|
$objTmp->debugLog (" verify the unique one column [$columns]");
|
|
if (! array_key_exists ($columns, $setValues))
|
|
$errors[$columns] = sprintf (dgettext ("domframework",
|
|
"No column '%s' defined but must be unique !"), $columns);
|
|
else
|
|
$objTmp->whereAdd ($columns, "=", $setValues[$columns]);
|
|
}
|
|
if ($update && array_key_exists (0, $resUpdate))
|
|
{
|
|
// If the value already exists, check if it is the same (the SQL can
|
|
// overwrite with the same value)
|
|
// If it is not the same, produce an error
|
|
$objTmp->whereAdd ($this->primary, "!=",
|
|
$resUpdate[0][$this->primary]);
|
|
}
|
|
if (count ($errors) == 0 && count ($objTmp->execute ()))
|
|
$this->DBException (dgettext ("domframework",
|
|
"An entry with these values already exists"));
|
|
unset ($objTmp);
|
|
}
|
|
|
|
// - If foreign keys, check if the value is set in the constraint
|
|
foreach ($this->foreign as $fields=>$params)
|
|
{
|
|
foreach (explode (",", $fields) as $field)
|
|
{
|
|
// Do not test in update if the field is not provided as it was already
|
|
// recorded
|
|
if ($update === true && ! array_key_exists ($field, $values))
|
|
continue 2;
|
|
if (! array_key_exists ($field, $values) &&
|
|
in_array ("not null", $this->fields[$field]))
|
|
{
|
|
$errors[$field] = sprintf (dgettext ("domframework",
|
|
"The field '%s' must be test on foreign, but is not provided"),
|
|
$field);
|
|
continue;
|
|
}
|
|
if (! array_key_exists ($params[0], $this->setForeignObj))
|
|
$this->DBException (sprintf (dgettext ("domframework",
|
|
"No foreign object configured to test the foreign key for table ".
|
|
"'%s'"), $this->table));
|
|
if (! array_key_exists ($field, $values) &&
|
|
! in_array ("not null", $this->fields[$field]))
|
|
$values[$field] = null;
|
|
}
|
|
$this->debugLog ("CLONE to check foreign constraint [$fields]");
|
|
$objTmp = clone $this->setForeignObj[$params[0]];
|
|
$objTmp->debug = $this->debug;
|
|
$objTmp->debugDepth++;
|
|
$objTmp->clearRequest ();
|
|
$objTmp->Select ();
|
|
$objTmp->displayAdd ($objTmp->primary);
|
|
$parentField = explode (",", $params[1]);
|
|
$i = 0;
|
|
foreach (explode (",", $fields) as $key=>$field)
|
|
{
|
|
if ($values[$field] !== null)
|
|
{
|
|
$objTmp->whereAdd ($parentField[$key], "=", $values[$field]);
|
|
$i++;
|
|
}
|
|
}
|
|
if ($i == 0)
|
|
{
|
|
// If the foreign is null, do not test as there is no WHERE clause, and
|
|
// will return all the table
|
|
continue;
|
|
}
|
|
if (count ($objTmp->execute ()) === 0)
|
|
$errors[$field] = sprintf (dgettext ("domframework",
|
|
"The value of the foreign key '%s' doesn't exists in foreign table"),
|
|
$field);
|
|
}
|
|
return $errors;
|
|
}
|
|
/* }}} */
|
|
|
|
/** Check the values before doing really the modification of the database
|
|
* @param boolean|null $update if true UPDATE request, else INSERT request
|
|
*/
|
|
public function checkValues ($update = false)
|
|
/* {{{ */
|
|
{
|
|
$this->debugLog ("Entering checkValues (",$update,")");
|
|
$update = !! $update;
|
|
$values = $this->setValues;
|
|
$errors = $this->verify ($values, $update);
|
|
if (count ($errors))
|
|
$this->DBException (reset ($errors));
|
|
$this->debugLog ("End of checkValues (",$update,") : Nothing in error");
|
|
}
|
|
/* }}} */
|
|
|
|
/** Execute the pre-defined query
|
|
* Return the content array if SELECT command is choosed
|
|
* Return the Last ID if INSERT command is choosed
|
|
* Return the number of modified lines for UPDATE/DELETE command
|
|
*/
|
|
public function execute ()
|
|
/* {{{ */
|
|
{
|
|
$this->debugLog ("Entering execute ()");
|
|
switch ($this->command)
|
|
{
|
|
case "SELECT":
|
|
break;
|
|
case "INSERT":
|
|
$this->checkValues (false);
|
|
break;
|
|
case "UPDATE":
|
|
$this->checkValues (true);
|
|
break;
|
|
case "DELETE":
|
|
break;
|
|
default:
|
|
$this->DBException ("execute : command not defined : no check");
|
|
}
|
|
$this->debugLog ("Entering createRequest ()");
|
|
$sql = $this->createRequest ();
|
|
$this->debugLog ("Entering prepareRequest (XXX, ",false,")");
|
|
$st = $this->prepareRequest ($sql, false);
|
|
$this->debugLog ("'",$this->getDisplayQuery (),"'");
|
|
$st->execute ();
|
|
switch ($this->command)
|
|
{
|
|
case "SELECT":
|
|
$result = $st->fetchAll (\PDO::FETCH_NUM);
|
|
// There is no fetchAll corresponding to the columnName->value. Assign the
|
|
// name to the value by index.
|
|
// FETCH_ASSOC doesn't work in empty left join (return NULL instead of
|
|
// the filled value)
|
|
$fieldsAll = $this->fieldsAll (false);
|
|
if ($this->joinObject === null && count ($this->displayGet (false)))
|
|
{
|
|
// Remove the table name as there is no collisions risk
|
|
// In case of Alias, remove the $this->sep too
|
|
$columns = array ();
|
|
foreach ($this->displayGet (false) as $col)
|
|
{
|
|
$col = str_replace ($this->sep.$this->tableprefix.$this->table.
|
|
$this->sep.".", "", $col);
|
|
if ($col[0] === $this->sep)
|
|
$col = substr ($col, 1);
|
|
elseif (strpos ($col, "DISTINCT ".$this->sep) === 0)
|
|
$col = "DISTINCT ".substr ($col, 10);
|
|
if (substr ($col, -1) === $this->sep &&
|
|
strpos ($col, " AS ".$this->sep) === false)
|
|
{
|
|
$col = substr ($col, 0, -1);
|
|
}
|
|
if (strpos ($col, " AS ".$this->sep))
|
|
{
|
|
// remove the separator before the AS
|
|
$col = substr ($col, 0, strpos ($col, " AS ".$this->sep) -1).
|
|
substr ($col, strpos ($col, " AS ".$this->sep));
|
|
}
|
|
$columns[] = $col;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$columns = $this->displayGet (true);
|
|
}
|
|
$columns = array_values ($columns);
|
|
foreach ($result as $rownb=>$row)
|
|
{
|
|
foreach ($row as $colNb=>$val)
|
|
{
|
|
// Harmonize the fetchAll result between all the databases drivers
|
|
$pos = strpos ($columns[$colNb], "(");
|
|
if ($pos)
|
|
{
|
|
// Function. The function that return an int must be added in this
|
|
// list, to cast correctely the value
|
|
$func = strtoupper (trim (substr ($columns[$colNb], 0, $pos)));
|
|
if (in_array ($func, array ("AVG", "COUNT", "MAX", "MIN", "SUM")))
|
|
$val = intval ($val);
|
|
}
|
|
else
|
|
{
|
|
$name = $columns[$colNb];
|
|
$pos = strpos ($columns[$colNb], " AS ");
|
|
if ($pos)
|
|
$name = substr ($columns[$colNb], 0, $pos);
|
|
$name = str_replace ("DISTINCT ", "", $name);
|
|
if (strtolower ($fieldsAll[$name][0]) === "integer" &&
|
|
$val !== null)
|
|
$val = intval ($val);
|
|
}
|
|
if (($pos = strpos ($columns[$colNb], " AS ".$this->sep)) !== false)
|
|
{
|
|
$pos += strlen (" AS ".$this->sep);
|
|
$colName = substr ($columns[$colNb], $pos, -1);
|
|
}
|
|
else
|
|
$colName = str_replace ($this->sep, "", $columns[$colNb]);
|
|
$result[$rownb][$colName] = $val;
|
|
unset ($result[$rownb][$colNb]);
|
|
}
|
|
}
|
|
return $result;
|
|
case "INSERT":
|
|
// If the primary key is not autoincrement, return the provided value if
|
|
// exists
|
|
if (! in_array ("autoincrement", $this->fields[$this->primary]) &&
|
|
key_exists ($this->primary, $this->setValues))
|
|
return $this->setValues[$this->primary];
|
|
|
|
// If the primary key is autoincrement and the provided value is not null
|
|
// Return it
|
|
if (in_array ("autoincrement", $this->fields[$this->primary]) &&
|
|
key_exists ($this->primary, $this->setValues) &&
|
|
$this->setValues[$this->primary] !== null)
|
|
return $this->setValues[$this->primary];
|
|
|
|
// If the primary key is autoincrement and the provided value is null or
|
|
// not provided, return the autoincremented value
|
|
// PostGres need the name of the column autoincrement to return something
|
|
$autoInc = null;
|
|
if ($this->driver === "pgsql")
|
|
{
|
|
foreach ($this->fields as $col=>$params)
|
|
{
|
|
if (in_array ("autoincrement", $params))
|
|
{
|
|
$autoInc = $col;
|
|
break;
|
|
}
|
|
}
|
|
if ($autoInc !== null)
|
|
$autoInc = $this->table."_${col}_seq";
|
|
}
|
|
$this->debugLog ("INSERT: lastInsertId=",
|
|
self::$instance[$this->dsn]->lastInsertId ($autoInc));
|
|
return self::$instance[$this->dsn]->lastInsertId ($autoInc);
|
|
case "UPDATE":
|
|
case "DELETE":
|
|
return $st->rowCount ();
|
|
default:
|
|
$this->DBException ("execute : command not defined : no RC");
|
|
}
|
|
}
|
|
/* }}} */
|
|
|
|
/** Execute a non prepared query in the database context.
|
|
* As there is no verification, this method is DANGEROUS and should not be
|
|
* used !
|
|
* @param string $sql The SQL request to directely send to the database
|
|
* @return PDOStatement The PDO Statement to traverse
|
|
*/
|
|
public function directQuery ($sql)
|
|
/* {{{ */
|
|
{
|
|
if (! key_exists ($this->dsn, self::$instance))
|
|
$this->DBException ("Direct query without established connection");
|
|
return self::$instance[$this->dsn]->query ($sql, PDO::FETCH_ASSOC);
|
|
}
|
|
/* }}} */
|
|
|
|
/** Error management
|
|
* @param string $message The message to throw in the exception
|
|
*/
|
|
public function DBException ($message)
|
|
/* {{{ */
|
|
{
|
|
$message = $this->DBExceptionMsg ($message);
|
|
throw new \Exception ($message, 500);
|
|
}
|
|
/* }}} */
|
|
|
|
/** Return the $message adapted with the debug trace if needed
|
|
* @param string $message The message to throw in the exception
|
|
*/
|
|
public function DBExceptionMsg ($message)
|
|
/* {{{ */
|
|
{
|
|
$backtrace = debug_backtrace ();
|
|
if ($this->debug)
|
|
{
|
|
require_once ("domframework/backtrace.php");
|
|
\backtrace::show (debug_backtrace ());
|
|
}
|
|
if (! array_key_exists (1, $backtrace))
|
|
unset ($backtrace);
|
|
else
|
|
{
|
|
$backtrace = end ($backtrace);
|
|
$filename = basename ($backtrace["file"]);
|
|
$line = $backtrace["line"];
|
|
$method = $backtrace["function"];
|
|
$message .= " ($filename:$line [$method])";
|
|
}
|
|
return $message;
|
|
}
|
|
|
|
/* }}} */
|
|
/** Debug function
|
|
* @param mixed ...$message The message to display in debug
|
|
*/
|
|
private function debugLog ($message)
|
|
/* {{{ */
|
|
{
|
|
if ((!!$this->debug) === false)
|
|
return;
|
|
echo str_repeat ("=", $this->debugDepth * 2)." ";
|
|
foreach (func_get_args() as $nb=>$arg)
|
|
{
|
|
if (is_string ($arg) || is_int ($arg))
|
|
echo $arg;
|
|
elseif (is_bool ($arg) && $arg === true)
|
|
echo "true";
|
|
elseif (is_bool ($arg) && $arg === false)
|
|
echo "false";
|
|
elseif (is_array ($arg))
|
|
print_r ($arg);
|
|
elseif (is_null ($arg))
|
|
echo "NULL";
|
|
else
|
|
die ("DEBUG TYPE UNKNOWN ".gettype ($arg)."\n");
|
|
}
|
|
echo "\n";
|
|
}
|
|
/* }}} */
|
|
}
|