Newer
Older
// The root model will receive the annotations, add in the
// annotation after the root model's fields
$T = $A->toSql($this, $model, $alias);
if ($fieldMap) {
array_splice($fields, count($fieldMap[0][0]), 0, array($T));
$fieldMap[0][0][] = $A->getAlias();
}
else {
// No field map — just add to end of field list
$fields[] = $T;
}
// If no group by has been set yet, use the root model pk
if (!$group_by) {
foreach ($model::$meta['pk'] as $pk)
$group_by[] = $rootAlias .'.'. $pk;
}
// Add in SELECT extras
if (isset($queryset->extra['select'])) {
foreach ($queryset->extra['select'] as $name=>$expr) {
if ($expr instanceof SqlFunction)
$expr = $expr->toSql($this, false, $name);
$fields[] = $expr;
}
}
if (isset($queryset->distinct)) {
foreach ($queryset->distinct as $d)
list($group_by[]) = $this->getField($d, $model);
}
$group_by = $group_by ? ' GROUP BY '.implode(', ', $group_by) : '';
$joins = $this->getJoins($queryset);
$sql = 'SELECT '.implode(', ', $fields).' FROM '
.$table.$joins.$where.$group_by.$having.$sort;
if ($queryset->limit)
$sql .= ' LIMIT '.$queryset->limit;
if ($queryset->offset)
$sql .= ' OFFSET '.$queryset->offset;
switch ($queryset->lock) {
case QuerySet::LOCK_EXCLUSIVE:
$sql .= ' FOR UPDATE';
break;
case QuerySet::LOCK_SHARED:
$sql .= ' LOCK IN SHARE MODE';
break;
}
return new MysqlExecutor($sql, $this->params, $fieldMap);
function __compileUpdateSet($model, array $pk) {
$fields = array();
foreach ($model->dirty as $field=>$old) {
if ($model->__new__ or !in_array($field, $pk)) {
$fields[] = sprintf('%s = %s', $this->quote($field),
$this->input($model->get($field)));
}
}
return ' SET '.implode(', ', $fields);
}
function compileUpdate(VerySimpleModel $model) {
$pk = $model::$meta['pk'];
$sql = 'UPDATE '.$this->quote($model::$meta['table']);
$sql .= $this->__compileUpdateSet($model, $pk);
// Support PK updates
$criteria = array();
foreach ($pk as $f) {
$criteria[$f] = @$model->dirty[$f] ?: $model->get($f);
}
$sql .= ' WHERE '.$this->compileQ(new Q($criteria), $model);
$sql .= ' LIMIT 1';
return new MySqlExecutor($sql, $this->params);
}
function compileInsert(VerySimpleModel $model) {
$pk = $model::$meta['pk'];
$sql = 'INSERT INTO '.$this->quote($model::$meta['table']);
$sql .= $this->__compileUpdateSet($model, $pk);
return new MySqlExecutor($sql, $this->params);
function compileDelete($model) {
$table = $model::$meta['table'];
$where = ' WHERE '.implode(' AND ',
$this->compileConstraints(array(new Q($model->pk)), $model));
$sql = 'DELETE FROM '.$this->quote($table).$where.' LIMIT 1';
return new MySqlExecutor($sql, $this->params);
function compileBulkDelete($queryset) {
$model = $queryset->model;
$table = $model::$meta['table'];
list($where, $having) = $this->getWhereHavingClause($queryset);
$joins = $this->getJoins($queryset);
$sql = 'DELETE '.$this->quote($table).'.* FROM '
.$this->quote($table).$joins.$where;
return new MysqlExecutor($sql, $this->params);
function compileBulkUpdate($queryset, array $what) {
$model = $queryset->model;
$table = $model::$meta['table'];
$set = array();
foreach ($what as $field=>$value)
$set[] = sprintf('%s = %s', $this->quote($field), $this->input($value));
$set = implode(', ', $set);
list($where, $having) = $this->getWhereHavingClause($queryset);
$joins = $this->getJoins($queryset);
$sql = 'UPDATE '.$this->quote($table).$joins.' SET '.$set.$where;
return new MysqlExecutor($sql, $this->params);
}
// Returns meta data about the table used to build queries
function inspectTable($table) {
static $cache = array();
// XXX: Assuming schema is not changing — add support to track
// current schema
if (isset($cache[$table]))
return $cache[$table];
$sql = 'SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS '
.'WHERE TABLE_NAME = '.db_input($table).' AND TABLE_SCHEMA = DATABASE() '
.'ORDER BY ORDINAL_POSITION';
$ex = new MysqlExecutor($sql, array());
$columns = array();
while (list($column) = $ex->getRow()) {
$columns[] = $column;
}
return $cache[$table] = $columns;
}
}
class MysqlExecutor {
var $stmt;
var $fields = array();
var $sql;
var $params;
// Array of [count, model] values representing which fields in the
// result set go with witch model. Useful for handling select_related
// queries
var $map;
function __construct($sql, $params, $map=null) {
$this->sql = $sql;
$this->params = $params;
$this->map = $map;
}
function getMap() {
return $this->map;
$this->execute();
$this->_setup_output();
}
function execute() {
throw new InconsistentModelException(
'Unable to prepare query: '.db_error().' '.$this->sql);
if (count($this->params))
$this->_bind($this->params);
if (!$this->stmt->execute() || ! $this->stmt->store_result()) {
throw new OrmException('Unable to execute query: ' . $this->stmt->error);
}
return true;
}
function _bind($params) {
if (count($params) != $this->stmt->param_count)
throw new Exception(__('Parameter count does not match query'));
if (is_int($p) || is_bool($p))
elseif (is_float($p))
$types .= 'd';
elseif ($p instanceof DateTime) {
$types .= 's';
$p = $p->format('Y-m-d h:i:s');
}
// TODO: Emit error if param is null
array_unshift($ps, $types);
call_user_func_array(array($this->stmt,'bind_param'), $ps);
}
function _setup_output() {
if (!($meta = $this->stmt->result_metadata()))
throw new OrmException('Unable to fetch statment metadata: ', $this->stmt->error);
$this->fields = $meta->fetch_fields();
}
// Iterator interface
function rewind() {
if (!isset($this->stmt))
$this->_prepare();
$this->stmt->data_seek(0);
}
function next() {
$status = $this->stmt->fetch();
if ($status === false)
throw new OrmException($this->stmt->error);
elseif ($status === null) {
$this->close();
return false;
}
return true;
}
function getArray() {
$output = array();
$variables = array();
if (!isset($this->stmt))
$this->_prepare();
foreach ($this->fields as $f)
$variables[] = &$output[$f->name]; // pass by reference
if (!call_user_func_array(array($this->stmt, 'bind_result'), $variables))
throw new OrmException('Unable to bind result: ' . $this->stmt->error);
if (!$this->next())
return false;
return $output;
}
function getRow() {
$output = array();
$variables = array();
if (!isset($this->stmt))
$this->_prepare();
foreach ($this->fields as $f)
$variables[] = &$output[]; // pass by reference
if (!call_user_func_array(array($this->stmt, 'bind_result'), $variables))
throw new OrmException('Unable to bind result: ' . $this->stmt->error);
if (!$this->next())
return false;
return $output;
}
function close() {
if (!$this->stmt)
return;
$this->stmt->close();
$this->stmt = null;
}
function affected_rows() {
return $this->stmt->affected_rows;
}
function insert_id() {
return $this->stmt->insert_id;
}
$self = $this;
$x = 0;
return preg_replace_callback('/\?/', function($m) use ($self, &$x) {
$p = $self->params[$x++];
return db_real_escape($p, is_string($p));
}, $this->sql);
class Q implements Serializable {
const NEGATED = 0x0001;
const ANY = 0x0002;
var $constraints;
var $flags;
var $negated = false;
var $ored = false;
function __construct($filter=array(), $flags=0) {
if (!is_array($filter))
$filter = array($filter);
$this->constraints = $filter;
$this->negated = $flags & self::NEGATED;
$this->ored = $flags & self::ANY;
}
function isNegated() {
return $this->negated;
}
function isOred() {
return $this->ored;
}
function negate() {
$this->negated = !$this->negated;
return $this;
}
function union() {
$this->ored = true;
}
function add($constraints) {
if (is_array($constraints))
$this->constraints = array_merge($this->constraints, $constraints);
elseif ($constraints instanceof static)
$this->constraints[] = $constraints;
else
throw new InvalidArgumentException('Expected an instance of Q or an array thereof');
return $this;
}
static function not(array $constraints) {
return new static($constraints, self::NEGATED);
}
static function any(array $constraints) {
return new static($constraints, self::ANY);
function serialize() {
return serialize(array(
'f' =>
($this->negated ? self::NEGATED : 0)
| ($this->ored ? self::ANY : 0),
'c' => $this->constraints
));
}
function unserialize($data) {
$data = unserialize($data);
$this->constraints = $data['c'];
$this->ored = $data['f'] & self::ANY;
$this->negated = $data['f'] & self::NEGATED;
}