830 lines
25 KiB
PHP
830 lines
25 KiB
PHP
<?php
|
|
|
|
/** Filtered table representation
|
|
* @method NotORM_Result and(mixed $condition, mixed $parameters = array()) Add AND condition
|
|
* @method NotORM_Result or(mixed $condition, mixed $parameters = array()) Add OR condition
|
|
*/
|
|
class NotORM_Result extends NotORM_Abstract implements Iterator, ArrayAccess, Countable, JsonSerializable {
|
|
protected $single;
|
|
protected $select = array(), $conditions = array(), $where = array(), $parameters = array(), $order = array(), $limit = null, $offset = null, $group = "", $having = "", $lock = null;
|
|
protected $union = array(), $unionOrder = array(), $unionLimit = null, $unionOffset = null;
|
|
protected $data, $referencing = array(), $aggregation = array(), $accessed, $access, $keys = array();
|
|
|
|
/** Create table result
|
|
* @param string
|
|
* @param NotORM
|
|
* @param bool single row
|
|
* @access protected must be public because it is called from NotORM
|
|
*/
|
|
function __construct($table, NotORM $notORM, $single = false) {
|
|
$this->table = $table;
|
|
$this->notORM = $notORM;
|
|
$this->single = $single;
|
|
$this->primary = $notORM->structure->getPrimary($table);
|
|
}
|
|
|
|
/** Save data to cache and empty result
|
|
*/
|
|
function __destruct() {
|
|
if ($this->notORM->cache && !$this->select && isset($this->rows)) {
|
|
$access = $this->access;
|
|
if (is_array($access)) {
|
|
$access = array_filter($access);
|
|
}
|
|
$this->notORM->cache->save("$this->table;" . implode(",", $this->conditions), $access);
|
|
}
|
|
$this->rows = null;
|
|
unset($this->data);
|
|
}
|
|
|
|
protected function limitString($limit, $offset = null) {
|
|
$return = "";
|
|
if (isset($limit) && $this->notORM->driver != "oci" && $this->notORM->driver != "dblib" && $this->notORM->driver != "mssql" && $this->notORM->driver != "sqlsrv") {
|
|
$return .= " LIMIT $limit";
|
|
if ($offset) {
|
|
$return .= " OFFSET $offset";
|
|
}
|
|
}
|
|
return $return;
|
|
}
|
|
|
|
protected function removeExtraDots($expression) {
|
|
return preg_replace('~(?:\\b[a-z_][a-z0-9_.:]*[.:])?([a-z_][a-z0-9_]*)[.:]([a-z_*])~i', '\\1.\\2', $expression); // rewrite tab1.tab2.col
|
|
}
|
|
|
|
protected function whereString() {
|
|
$return = "";
|
|
if ($this->group) {
|
|
$return .= " GROUP BY $this->group";
|
|
}
|
|
if ($this->having) {
|
|
$return .= " HAVING $this->having";
|
|
}
|
|
if ($this->order) {
|
|
$return .= " ORDER BY " . implode(", ", $this->order);
|
|
}
|
|
$return = $this->removeExtraDots($return);
|
|
|
|
$where = $this->where;
|
|
if (isset($this->limit) && $this->notORM->driver == "oci") {
|
|
$where[] = ($where ? " AND " : "") . "(" . ($this->offset ? "rownum > $this->offset AND " : "") . "rownum <= " . ($this->limit + $this->offset) . ")"; //! rownum > doesn't work - requires subselect (see adminer/drivers/oracle.inc.php)
|
|
}
|
|
if ($where) {
|
|
$return = " WHERE " . implode($where) . $return;
|
|
}
|
|
|
|
$return .= $this->limitString($this->limit, $this->offset);
|
|
if (isset($this->lock)) {
|
|
$return .= ($this->lock ? " FOR UPDATE" : " LOCK IN SHARE MODE");
|
|
}
|
|
return $return;
|
|
}
|
|
|
|
protected function topString($limit, $offset = null) {
|
|
if (isset($limit) && ($this->notORM->driver == "dblib" || $this->notORM->driver == "mssql" || $this->notORM->driver == "sqlsrv")) {
|
|
return " TOP ($this->limit)"; //! offset is not supported
|
|
}
|
|
return "";
|
|
}
|
|
|
|
protected function createJoins($val) {
|
|
$return = array();
|
|
preg_match_all('~\\b([a-z_][a-z0-9_.:]*[.:])[a-z_*]~i', $val, $matches);
|
|
foreach ($matches[1] as $names) {
|
|
$parent = $this->table;
|
|
if ($names != "$parent.") { // case-sensitive
|
|
preg_match_all('~\\b([a-z_][a-z0-9_]*)([.:])~i', $names, $matches, PREG_SET_ORDER);
|
|
foreach ($matches as $match) {
|
|
list(, $name, $delimiter) = $match;
|
|
$table = $this->notORM->structure->getReferencedTable($name, $parent);
|
|
$column = ($delimiter == ':' ? $this->notORM->structure->getPrimary($parent) : $this->notORM->structure->getReferencedColumn($name, $parent));
|
|
$primary = ($delimiter == ':' ? $this->notORM->structure->getReferencedColumn($parent, $table) : $this->notORM->structure->getPrimary($table));
|
|
$return[$name] = " LEFT JOIN $table" . ($table != $name ? " AS $name" : "") . " ON $parent.$column = $name.$primary"; // should use alias if the table is used on more places
|
|
$parent = $name;
|
|
}
|
|
}
|
|
}
|
|
return $return;
|
|
}
|
|
|
|
/** Get SQL query
|
|
* @return string
|
|
*/
|
|
function __toString() {
|
|
$return = "SELECT" . $this->topString($this->limit, $this->offset) . " ";
|
|
$join = $this->createJoins(implode(",", $this->conditions) . "," . implode(",", $this->select) . ",$this->group,$this->having," . implode(",", $this->order));
|
|
if (!isset($this->rows) && $this->notORM->cache && !is_string($this->accessed)) {
|
|
$this->accessed = $this->notORM->cache->load("$this->table;" . implode(",", $this->conditions));
|
|
$this->access = $this->accessed;
|
|
}
|
|
if ($this->select) {
|
|
$return .= $this->removeExtraDots(implode(", ", $this->select));
|
|
} elseif ($this->accessed) {
|
|
$return .= ($join ? "$this->table." : "") . implode(", " . ($join ? "$this->table." : ""), array_keys($this->accessed));
|
|
} else {
|
|
$return .= ($join ? "$this->table." : "") . "*";
|
|
}
|
|
$return .= " FROM $this->table" . implode($join) . $this->whereString();
|
|
if ($this->union) {
|
|
$return = ($this->notORM->driver == "sqlite" || $this->notORM->driver == "oci" ? $return : "($return)") . implode($this->union);
|
|
if ($this->unionOrder) {
|
|
$return .= " ORDER BY " . implode(", ", $this->unionOrder);
|
|
}
|
|
$return .= $this->limitString($this->unionLimit, $this->unionOffset);
|
|
$top = $this->topString($this->unionLimit, $this->unionOffset);
|
|
if ($top) {
|
|
$return = "SELECT$top * FROM ($return) t";
|
|
}
|
|
}
|
|
return $return;
|
|
}
|
|
|
|
protected function query($query, $parameters) {
|
|
if ($this->notORM->debug) {
|
|
if (!is_callable($this->notORM->debug)) {
|
|
$debug = "$query;";
|
|
if ($parameters) {
|
|
$debug .= " -- " . implode(", ", array_map(array($this, 'quote'), $parameters));
|
|
}
|
|
$pattern = '(^' . preg_quote(dirname(__FILE__)) . '(\\.php$|[/\\\\]))'; // can be static
|
|
foreach (debug_backtrace() as $backtrace) {
|
|
if (isset($backtrace["file"]) && !preg_match($pattern, $backtrace["file"])) { // stop on first file outside NotORM source codes
|
|
break;
|
|
}
|
|
}
|
|
error_log("$backtrace[file]:$backtrace[line]:$debug\n", 0);
|
|
} elseif (call_user_func($this->notORM->debug, $query, $parameters) === false) {
|
|
return false;
|
|
}
|
|
}
|
|
$return = $this->notORM->connection->prepare($query);
|
|
if (!$return || !$return->execute(array_map(array($this, 'formatValue'), $parameters))) {
|
|
$return = false;
|
|
}
|
|
if ($this->notORM->debugTimer) {
|
|
call_user_func($this->notORM->debugTimer);
|
|
}
|
|
return $return;
|
|
}
|
|
|
|
protected function formatValue($val) {
|
|
if ($val instanceof DateTime) {
|
|
return $val->format("Y-m-d H:i:s"); //! may be driver specific
|
|
}
|
|
return $val;
|
|
}
|
|
|
|
protected function quote($val) {
|
|
if (!isset($val)) {
|
|
return "NULL";
|
|
}
|
|
if (is_array($val)) { // (a, b) IN ((1, 2), (3, 4))
|
|
return "(" . implode(", ", array_map(array($this, 'quote'), $val)) . ")";
|
|
}
|
|
$val = $this->formatValue($val);
|
|
if (is_float($val)) {
|
|
return sprintf("%F", $val); // otherwise depends on setlocale()
|
|
}
|
|
if ($val === false) {
|
|
return "0";
|
|
}
|
|
if (is_int($val) || $val instanceof NotORM_Literal) { // number or SQL code - for example "NOW()"
|
|
return (string) $val;
|
|
}
|
|
return $this->notORM->connection->quote($val);
|
|
}
|
|
|
|
/** Shortcut for call_user_func_array(array($this, 'insert'), $rows)
|
|
* @param array
|
|
* @return int number of affected rows or false in case of an error
|
|
*/
|
|
function insert_multi(array $rows) {
|
|
if ($this->notORM->freeze) {
|
|
return false;
|
|
}
|
|
if (!$rows) {
|
|
return 0;
|
|
}
|
|
$data = reset($rows);
|
|
$parameters = array();
|
|
if ($data instanceof NotORM_Result) {
|
|
$parameters = $data->parameters; //! other parameters
|
|
$data = (string) $data;
|
|
} elseif ($data instanceof Traversable) {
|
|
$data = iterator_to_array($data);
|
|
}
|
|
$insert = $data;
|
|
if (is_array($data)) {
|
|
$values = array();
|
|
foreach ($rows as $value) {
|
|
if ($value instanceof Traversable) {
|
|
$value = iterator_to_array($value);
|
|
}
|
|
$values[] = $this->quote($value);
|
|
foreach ($value as $val) {
|
|
if ($val instanceof NotORM_Literal && $val->parameters) {
|
|
$parameters = array_merge($parameters, $val->parameters);
|
|
}
|
|
}
|
|
}
|
|
//! driver specific extended insert
|
|
$insert = ($data || $this->notORM->driver == "mysql"
|
|
? "(" . implode(", ", array_keys($data)) . ") VALUES " . implode(", ", $values)
|
|
: "DEFAULT VALUES"
|
|
);
|
|
}
|
|
// requires empty $this->parameters
|
|
$return = $this->query("INSERT INTO $this->table $insert", $parameters);
|
|
if (!$return) {
|
|
return false;
|
|
}
|
|
$this->rows = null;
|
|
return $return->rowCount();
|
|
}
|
|
|
|
/** Insert row in a table
|
|
* @param mixed array($column => $value)|Traversable for single row insert or NotORM_Result|string for INSERT ... SELECT
|
|
* @param ... used for extended insert
|
|
* @return mixed inserted NotORM_Row or false in case of an error or number of affected rows for INSERT ... SELECT
|
|
*/
|
|
function insert($data) {
|
|
$rows = func_get_args();
|
|
$return = $this->insert_multi($rows);
|
|
if (!$return) {
|
|
return false;
|
|
}
|
|
if (!is_array($data)) {
|
|
return $return;
|
|
}
|
|
if (!isset($data[$this->primary]) && ($id = $this->notORM->connection->lastInsertId($this->notORM->structure->getSequence($this->table)))) {
|
|
$data[$this->primary] = $id;
|
|
}
|
|
return new $this->notORM->rowClass($data, $this);
|
|
}
|
|
|
|
/** Update all rows in result set
|
|
* @param array ($column => $value)
|
|
* @return int number of affected rows or false in case of an error
|
|
*/
|
|
function update(array $data) {
|
|
if ($this->notORM->freeze) {
|
|
return false;
|
|
}
|
|
if (!$data) {
|
|
return 0;
|
|
}
|
|
$values = array();
|
|
$parameters = array();
|
|
foreach ($data as $key => $val) {
|
|
// doesn't use binding because $this->parameters can be filled by ? or :name
|
|
$values[] = "$key = " . $this->quote($val);
|
|
if ($val instanceof NotORM_Literal && $val->parameters) {
|
|
$parameters = array_merge($parameters, $val->parameters);
|
|
}
|
|
}
|
|
if ($this->parameters) {
|
|
$parameters = array_merge($parameters, $this->parameters);
|
|
}
|
|
// joins in UPDATE are supported only in MySQL
|
|
$return = $this->query("UPDATE" . $this->topString($this->limit, $this->offset) . " $this->table SET " . implode(", ", $values) . $this->whereString(), $parameters);
|
|
if (!$return) {
|
|
return false;
|
|
}
|
|
return $return->rowCount();
|
|
}
|
|
|
|
/** Insert row or update if it already exists
|
|
* @param array ($column => $value)
|
|
* @param array ($column => $value)
|
|
* @param array ($column => $value), empty array means use $insert
|
|
* @return int number of affected rows or false in case of an error
|
|
*/
|
|
function insert_update(array $unique, array $insert, array $update = array()) {
|
|
if (!$update) {
|
|
$update = $insert;
|
|
}
|
|
$insert = $unique + $insert;
|
|
$values = "(" . implode(", ", array_keys($insert)) . ") VALUES " . $this->quote($insert);
|
|
//! parameters
|
|
if ($this->notORM->driver == "mysql") {
|
|
$set = array();
|
|
if (!$update) {
|
|
$update = $unique;
|
|
}
|
|
foreach ($update as $key => $val) {
|
|
$set[] = "$key = " . $this->quote($val);
|
|
//! parameters
|
|
}
|
|
return $this->insert("$values ON DUPLICATE KEY UPDATE " . implode(", ", $set));
|
|
} else {
|
|
$connection = $this->notORM->connection;
|
|
$errorMode = $connection->getAttribute(PDO::ATTR_ERRMODE);
|
|
$connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
|
try {
|
|
$return = $this->insert($values);
|
|
$connection->setAttribute(PDO::ATTR_ERRMODE, $errorMode);
|
|
return $return;
|
|
} catch (PDOException $e) {
|
|
$connection->setAttribute(PDO::ATTR_ERRMODE, $errorMode);
|
|
if ($e->getCode() == "23000" || $e->getCode() == "23505") { // "23000" - duplicate key, "23505" unique constraint pgsql
|
|
if (!$update) {
|
|
return 0;
|
|
}
|
|
$clone = clone $this;
|
|
$return = $clone->where($unique)->update($update);
|
|
return ($return ? $return + 1 : $return);
|
|
}
|
|
if ($errorMode == PDO::ERRMODE_EXCEPTION) {
|
|
throw $e;
|
|
} elseif ($errorMode == PDO::ERRMODE_WARNING) {
|
|
trigger_error("PDOStatement::execute(): " . $e->getMessage(), E_USER_WARNING); // E_WARNING is unusable
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Get last insert ID
|
|
* @return string number
|
|
*/
|
|
function insert_id() {
|
|
return $this->notORM->connection->lastInsertId();
|
|
}
|
|
|
|
/** Delete all rows in result set
|
|
* @return int number of affected rows or false in case of an error
|
|
*/
|
|
function delete() {
|
|
if ($this->notORM->freeze) {
|
|
return false;
|
|
}
|
|
$return = $this->query("DELETE" . $this->topString($this->limit, $this->offset) . " FROM $this->table" . $this->whereString(), $this->parameters);
|
|
if (!$return) {
|
|
return false;
|
|
}
|
|
return $return->rowCount();
|
|
}
|
|
|
|
/** Add select clause, more calls appends to the end
|
|
* @param string for example "column, MD5(column) AS column_md5", empty string to reset previously set columns
|
|
* @param string ...
|
|
* @return NotORM_Result fluent interface
|
|
*/
|
|
function select($columns) {
|
|
$this->__destruct();
|
|
if ($columns != "") {
|
|
foreach (func_get_args() as $columns) {
|
|
$this->select[] = $columns;
|
|
}
|
|
} else {
|
|
$this->select = array();
|
|
}
|
|
return $this;
|
|
}
|
|
|
|
/** Add where condition, more calls appends with AND
|
|
* @param mixed string possibly containing ? or :name; or array($condition => $parameters, ...)
|
|
* @param mixed array accepted by PDOStatement::execute or a scalar value
|
|
* @param mixed ...
|
|
* @return NotORM_Result fluent interface
|
|
*/
|
|
function where($condition, $parameters = array()) {
|
|
$args = func_get_args();
|
|
return $this->whereOperator("AND", $args);
|
|
}
|
|
|
|
protected function whereOperator($operator, array $args) {
|
|
$condition = $args[0];
|
|
$parameters = (count($args) > 1 ? $args[1] : array());
|
|
if (is_array($condition)) { // where(array("column1" => 1, "column2 > ?" => 2))
|
|
foreach ($condition as $key => $val) {
|
|
$this->where($key, $val);
|
|
}
|
|
return $this;
|
|
}
|
|
$this->__destruct();
|
|
$this->conditions[] = "$operator $condition";
|
|
$condition = $this->removeExtraDots($condition);
|
|
if (count($args) != 2 || strpbrk($condition, "?:")) { // where("column < ? OR column > ?", array(1, 2))
|
|
if (count($args) != 2 || !is_array($parameters)) { // where("column < ? OR column > ?", 1, 2)
|
|
$parameters = array_slice($args, 1);
|
|
}
|
|
$this->parameters = array_merge($this->parameters, $parameters);
|
|
} elseif ($parameters === null) { // where("column", null)
|
|
$condition .= " IS NULL";
|
|
} elseif ($parameters instanceof NotORM_Result) { // where("column", $db->$table())
|
|
$clone = clone $parameters;
|
|
if (!$clone->select) {
|
|
$clone->select($this->notORM->structure->getPrimary($clone->table));
|
|
}
|
|
if ($this->notORM->driver != "mysql") {
|
|
if ($clone instanceof NotORM_MultiResult) {
|
|
array_shift($clone->select);
|
|
$clone->single();
|
|
}
|
|
$condition .= " IN ($clone)";
|
|
$this->parameters = array_merge($this->parameters, $clone->parameters);
|
|
} else {
|
|
$in = array();
|
|
foreach ($clone as $row) {
|
|
$row = array_values(iterator_to_array($row));
|
|
if ($clone instanceof NotORM_MultiResult && count($row) > 1) {
|
|
array_shift($row);
|
|
}
|
|
if (count($row) == 1) {
|
|
$in[] = $this->quote($row[0]);
|
|
} else {
|
|
$in[] = $this->quote($row);
|
|
}
|
|
}
|
|
if ($in) {
|
|
$condition .= " IN (" . implode(", ", $in) . ")";
|
|
} else {
|
|
$condition = "($condition) IS NOT NULL AND $condition IS NULL"; // $condition = "NOT id"
|
|
}
|
|
}
|
|
} elseif (!is_array($parameters)) { // where("column", "x")
|
|
$condition .= " = " . $this->quote($parameters);
|
|
} else { // where("column", array(1, 2))
|
|
$condition = $this->whereIn($condition, $parameters);
|
|
}
|
|
$this->where[] = (preg_match('~^\)+$~', $condition)
|
|
? $condition
|
|
: ($this->where ? " $operator " : "") . "($condition)"
|
|
);
|
|
return $this;
|
|
}
|
|
|
|
protected function whereIn($condition, $parameters) {
|
|
if (!$parameters) {
|
|
$condition = "($condition) IS NOT NULL AND $condition IS NULL";
|
|
} elseif ($this->notORM->driver != "oci") {
|
|
$column = $condition;
|
|
$condition .= " IN " . $this->quote($parameters);
|
|
$nulls = array_filter($parameters, 'is_null');
|
|
if ($nulls) {
|
|
$condition = "$condition OR $column IS NULL";
|
|
}
|
|
} else { // http://download.oracle.com/docs/cd/B19306_01/server.102/b14200/expressions014.htm
|
|
$or = array();
|
|
for ($i=0; $i < count($parameters); $i += 1000) {
|
|
$or[] = "$condition IN " . $this->quote(array_slice($parameters, $i, 1000));
|
|
}
|
|
$condition = implode(" OR ", $or);
|
|
}
|
|
return $condition;
|
|
}
|
|
|
|
function __call($name, array $args) {
|
|
$operator = strtoupper($name);
|
|
switch ($operator) {
|
|
case "AND":
|
|
case "OR":
|
|
return $this->whereOperator($operator, $args);
|
|
}
|
|
trigger_error("Call to undefined method NotORM_Result::$name()", E_USER_ERROR);
|
|
}
|
|
|
|
/** Shortcut for where()
|
|
* @param string
|
|
* @param mixed
|
|
* @param mixed ...
|
|
* @return NotORM_Result fluent interface
|
|
*/
|
|
function __invoke($where, $parameters = array()) {
|
|
$args = func_get_args();
|
|
return $this->whereOperator("AND", $args);
|
|
}
|
|
|
|
/** Add order clause, more calls appends to the end
|
|
* @param mixed "column1, column2 DESC" or array("column1", "column2 DESC"), empty string to reset previous order
|
|
* @param string ...
|
|
* @return NotORM_Result fluent interface
|
|
*/
|
|
function order($columns) {
|
|
$this->rows = null;
|
|
if ($columns != "") {
|
|
$columns = (is_array($columns) ? $columns : func_get_args());
|
|
foreach ($columns as $column) {
|
|
if ($this->union) {
|
|
$this->unionOrder[] = $column;
|
|
} else {
|
|
$this->order[] = $column;
|
|
}
|
|
}
|
|
} elseif ($this->union) {
|
|
$this->unionOrder = array();
|
|
} else {
|
|
$this->order = array();
|
|
}
|
|
return $this;
|
|
}
|
|
|
|
/** Set limit clause, more calls rewrite old values
|
|
* @param int
|
|
* @param int
|
|
* @return NotORM_Result fluent interface
|
|
*/
|
|
function limit($limit, $offset = null) {
|
|
$this->rows = null;
|
|
if ($this->union) {
|
|
$this->unionLimit = +$limit;
|
|
$this->unionOffset = +$offset;
|
|
} else {
|
|
$this->limit = +$limit;
|
|
$this->offset = +$offset;
|
|
}
|
|
return $this;
|
|
}
|
|
|
|
/** Set group clause, more calls rewrite old values
|
|
* @param string
|
|
* @param string
|
|
* @return NotORM_Result fluent interface
|
|
*/
|
|
function group($columns, $having = "") {
|
|
$this->__destruct();
|
|
$this->group = $columns;
|
|
$this->having = $having;
|
|
return $this;
|
|
}
|
|
|
|
/** Set select FOR UPDATE or LOCK IN SHARE MODE
|
|
* @param bool
|
|
* @return NotORM_Result fluent interface
|
|
*/
|
|
function lock($exclusive = true) {
|
|
$this->lock = $exclusive;
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* @param NotORM_Result
|
|
* @param bool
|
|
* @return NotORM_Result fluent interface
|
|
*/
|
|
function union(NotORM_Result $result, $all = false) {
|
|
$this->union[] = " UNION " . ($all ? "ALL " : "") . ($this->notORM->driver == "sqlite" || $this->notORM->driver == "oci" ? $result : "($result)");
|
|
$this->parameters = array_merge($this->parameters, $result->parameters);
|
|
return $this;
|
|
}
|
|
|
|
/** Execute aggregation function
|
|
* @param string
|
|
* @return string
|
|
*/
|
|
function aggregation($function) {
|
|
$join = $this->createJoins(implode(",", $this->conditions) . ",$function");
|
|
$query = "SELECT $function FROM $this->table" . implode($join);
|
|
if ($this->where) {
|
|
$query .= " WHERE " . implode($this->where);
|
|
}
|
|
foreach ($this->query($query, $this->parameters)->fetch() as $return) {
|
|
return $return;
|
|
}
|
|
}
|
|
|
|
/** Count number of rows
|
|
* @param string
|
|
* @return int
|
|
*/
|
|
function count($column = "") {
|
|
if (!$column) {
|
|
$this->execute();
|
|
return count($this->data);
|
|
}
|
|
return $this->aggregation("COUNT($column)");
|
|
}
|
|
|
|
/** Return minimum value from a column
|
|
* @param string
|
|
* @return int
|
|
*/
|
|
function min($column) {
|
|
return $this->aggregation("MIN($column)");
|
|
}
|
|
|
|
/** Return maximum value from a column
|
|
* @param string
|
|
* @return int
|
|
*/
|
|
function max($column) {
|
|
return $this->aggregation("MAX($column)");
|
|
}
|
|
|
|
/** Return sum of values in a column
|
|
* @param string
|
|
* @return int
|
|
*/
|
|
function sum($column) {
|
|
return $this->aggregation("SUM($column)");
|
|
}
|
|
|
|
/** Execute the built query
|
|
* @return null
|
|
*/
|
|
protected function execute() {
|
|
if (!isset($this->rows)) {
|
|
$result = false;
|
|
$exception = null;
|
|
$parameters = array();
|
|
foreach (array_merge($this->select, array($this, $this->group, $this->having), $this->order, $this->unionOrder) as $val) {
|
|
if (($val instanceof NotORM_Literal || $val instanceof self) && $val->parameters) {
|
|
$parameters = array_merge($parameters, $val->parameters);
|
|
}
|
|
}
|
|
try {
|
|
$result = $this->query($this->__toString(), $parameters);
|
|
} catch (PDOException $exception) {
|
|
// handled later
|
|
}
|
|
if (!$result) {
|
|
if (!$this->select && $this->accessed) {
|
|
$this->accessed = '';
|
|
$this->access = array();
|
|
$result = $this->query($this->__toString(), $parameters);
|
|
} elseif ($exception) {
|
|
throw $exception;
|
|
}
|
|
}
|
|
$this->rows = array();
|
|
if ($result) {
|
|
$result->setFetchMode(PDO::FETCH_ASSOC);
|
|
foreach ($result as $key => $row) {
|
|
if (isset($row[$this->primary])) {
|
|
$key = $row[$this->primary];
|
|
if (!is_string($this->access)) {
|
|
$this->access[$this->primary] = true;
|
|
}
|
|
}
|
|
$this->rows[$key] = new $this->notORM->rowClass($row, $this);
|
|
}
|
|
}
|
|
$this->data = $this->rows;
|
|
}
|
|
}
|
|
|
|
/** Fetch next row of result
|
|
* @param string column name to return or an empty string for the whole row
|
|
* @return mixed string or null with $column, NotORM_Row without $column, false if there is no row
|
|
*/
|
|
function fetch($column = '') {
|
|
// no $this->select($column) because next calls can access different columns
|
|
$this->execute();
|
|
$return = current($this->data);
|
|
next($this->data);
|
|
if ($return && $column != '') {
|
|
return $return[$column];
|
|
}
|
|
return $return;
|
|
}
|
|
|
|
/** Fetch all rows as associative array
|
|
* @param string
|
|
* @param string column name used for an array value or an empty string for the whole row
|
|
* @return array
|
|
*/
|
|
function fetchPairs($key, $value = '') {
|
|
$return = array();
|
|
$clone = clone $this;
|
|
if ($value != "") {
|
|
$clone->select = array();
|
|
$clone->select("$key, $value"); // MultiResult adds its column
|
|
} elseif ($clone->select) {
|
|
array_unshift($clone->select, $key);
|
|
} else {
|
|
$clone->select = array("$key, $this->table.*");
|
|
}
|
|
foreach ($clone as $row) {
|
|
$values = array_values(iterator_to_array($row));
|
|
if ($value != "" && $clone instanceof NotORM_MultiResult) {
|
|
array_shift($values);
|
|
}
|
|
$return[(string) $values[0]] = ($value != "" ? $values[(array_key_exists(1, $values) ? 1 : 0)] : $row); // isset($values[1]) - fetchPairs("id", "id")
|
|
}
|
|
return $return;
|
|
}
|
|
|
|
protected function access($key, $delete = false) {
|
|
if ($delete) {
|
|
if (is_array($this->access)) {
|
|
$this->access[$key] = false;
|
|
}
|
|
return false;
|
|
}
|
|
if (!isset($key)) {
|
|
$this->access = '';
|
|
} elseif (!is_string($this->access)) {
|
|
$this->access[$key] = true;
|
|
}
|
|
if (!$this->select && $this->accessed && (!isset($key) || !isset($this->accessed[$key]))) {
|
|
$this->accessed = '';
|
|
$this->rows = null;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
protected function single() {
|
|
}
|
|
|
|
// Iterator implementation (not IteratorAggregate because $this->data can be changed during iteration)
|
|
|
|
function rewind() {
|
|
$this->execute();
|
|
$this->keys = array_keys($this->data);
|
|
reset($this->keys);
|
|
}
|
|
|
|
/** @return NotORM_Row */
|
|
function current() {
|
|
return $this->data[current($this->keys)];
|
|
}
|
|
|
|
/** @return string row ID */
|
|
function key() {
|
|
return current($this->keys);
|
|
}
|
|
|
|
function next() {
|
|
next($this->keys);
|
|
}
|
|
|
|
function valid() {
|
|
return current($this->keys) !== false;
|
|
}
|
|
|
|
// ArrayAccess implementation
|
|
|
|
/** Test if row exists
|
|
* @param string row ID or array for where conditions
|
|
* @return bool
|
|
*/
|
|
function offsetExists($key) {
|
|
$row = $this->offsetGet($key);
|
|
return isset($row);
|
|
}
|
|
|
|
/** Get specified row
|
|
* @param string row ID or array for where conditions
|
|
* @return NotORM_Row or null if there is no such row
|
|
*/
|
|
function offsetGet($key) {
|
|
if ($this->single && !isset($this->data)) {
|
|
$clone = clone $this;
|
|
if (is_array($key)) {
|
|
$clone->where($key)->limit(1);
|
|
} else {
|
|
$clone->where($this->primary, $key);
|
|
}
|
|
$return = $clone->fetch();
|
|
if ($return) {
|
|
return $return;
|
|
}
|
|
} else {
|
|
$this->execute();
|
|
if (is_array($key)) {
|
|
foreach ($this->data as $row) {
|
|
foreach ($key as $k => $v) {
|
|
if ((isset($v) && $row[$k] !== null ? $row[$k] != $v : $row[$k] !== $v)) {
|
|
continue 2;
|
|
}
|
|
}
|
|
return $row;
|
|
}
|
|
} elseif (isset($this->data[$key])) {
|
|
return $this->data[$key];
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Mimic row
|
|
* @param string row ID
|
|
* @param NotORM_Row
|
|
* @return null
|
|
*/
|
|
function offsetSet($key, $value) {
|
|
$this->execute();
|
|
$this->data[$key] = $value;
|
|
}
|
|
|
|
/** Remove row from result set
|
|
* @param string row ID
|
|
* @return null
|
|
*/
|
|
function offsetUnset($key) {
|
|
$this->execute();
|
|
unset($this->data[$key]);
|
|
}
|
|
|
|
// JsonSerializable implementation
|
|
|
|
function jsonSerialize() {
|
|
$this->execute();
|
|
if ($this->notORM->jsonAsArray) {
|
|
return array_values($this->data);
|
|
} else {
|
|
return $this->data;
|
|
}
|
|
}
|
|
|
|
}
|