Skip to content
Snippets Groups Projects
class.orm.php 44.1 KiB
Newer Older
  • Learn to ignore specific revisions
  •             // "'constant'" => "Model.field_name"
                if ($local[0] == "'") {
                    $constraints[] = sprintf("%s.%s = %s",
                        $alias, $this->quote($right),
                        $this->input(trim($local, '\'"'))
                    );
                }
                else {
                    $constraints[] = sprintf("%s.%s = %s.%s",
                        $table, $this->quote($local), $alias,
                        $this->quote($right)
                    );
                }
    
            }
            return $join.$this->quote($rmodel::$meta['table'])
                .' '.$alias.' ON ('.implode(' AND ', $constraints).')';
    
        function input(&$what) {
    
            if ($what instanceof QuerySet) {
                $q = $what->getQuery(array('nosort'=>true));
    
                $this->params = array_merge($q->params);
    
    Jared Hancock's avatar
    Jared Hancock committed
                return (string)$q;
    
            elseif ($what instanceof SqlFunction) {
    
                return $what->toSql($this);
    
            else {
                $this->params[] = $what;
                return '?';
            }
    
    Jared Hancock's avatar
    Jared Hancock committed
        }
    
        function quote($what) {
            return "`$what`";
        }
    
    
        /**
         * getWhereClause
         *
         * This builds the WHERE ... part of a DML statement. This should be
         * called before ::getJoins(), because it may add joins into the
         * statement based on the relationships used in the where clause
         */
        protected function getWhereClause($queryset) {
    
    Jared Hancock's avatar
    Jared Hancock committed
            $model = $queryset->model;
    
            $where = $this->compileConstraints($queryset->constraints, $model);
            if ($where)
                $where = ' WHERE '.$where;
            return $where ?: '';
    
        }
    
        function compileCount($queryset) {
            $model = $queryset->model;
            $table = $model::$meta['table'];
            $where = $this->getWhereClause($queryset);
    
            $joins = $this->getJoins();
    
    Jared Hancock's avatar
    Jared Hancock committed
            $sql = 'SELECT COUNT(*) AS count FROM '.$this->quote($table).$joins.$where;
            $exec = new MysqlExecutor($sql, $this->params);
            $row = $exec->getArray();
            return $row['count'];
        }
    
        function compileSelect($queryset) {
            $model = $queryset->model;
    
            // Use an alias for the root model table
            $table = $model::$meta['table'];
            $this->joins[''] = array('alias' => ($rootAlias = $this->nextAlias()));
            // Compile the WHERE clause
    
            $where = $this->getWhereClause($queryset);
    
    Jared Hancock's avatar
    Jared Hancock committed
    
            $sort = '';
    
            if ($queryset->ordering && !isset($this->options['nosort'])) {
    
    Jared Hancock's avatar
    Jared Hancock committed
                $orders = array();
                foreach ($queryset->ordering as $sort) {
                    $dir = 'ASC';
                    if (substr($sort, 0, 1) == '-') {
                        $dir = 'DESC';
                        $sort = substr($sort, 1);
                    }
    
                    list($field) = $this->getField($sort, $model);
    
    Jared Hancock's avatar
    Jared Hancock committed
                    $orders[] = $field.' '.$dir;
                }
                $sort = ' ORDER BY '.implode(', ', $orders);
            }
    
    
            // Compile the field listing
    
    Jared Hancock's avatar
    Jared Hancock committed
            $fields = array();
    
            $table = $this->quote($table).' '.$rootAlias;
            // Handle related tables
    
    Jared Hancock's avatar
    Jared Hancock committed
            if ($queryset->related) {
    
                $count = 0;
                $fieldMap = array();
                $defer = $queryset->defer ?: array();
                // Add local fields first
                $model::_inspect();
                foreach ($model::$meta['fields'] as $f) {
                    // Handle deferreds
                    if (isset($defer[$f]))
                        continue;
                    $fields[] = $rootAlias . '.' . $this->quote($f);
    
                $count = count($fields);
                $fieldMap[] = array($count, $model);
                // Add the JOINs to this query
                foreach ($queryset->related as $sr) {
                    $full_path = '';
                    $parts = array();
                    // Track each model traversal and fetch data for each of the
                    // models in the path of the related table
                    foreach (explode('__', $sr) as $field) {
                        $full_path .= $field;
                        $parts[] = $field;
                        list($alias, $fmodel) = $this->getField($full_path, $model,
                            array('table'=>true, 'model'=>true));
                        $fmodel::_inspect();
                        foreach ($fmodel::$meta['fields'] as $f) {
                            // Handle deferreds
                            if (isset($defer[$sr . '__' . $f]))
                                continue;
                            $fields[] = $alias . '.' . $this->quote($f) . " AS {$alias}_$f";
                        }
                        $fieldMap[] = array(count($fields) - $count, $fmodel, $parts, $alias);
                        $full_path .= '__';
                        $count = count($fields);
                    }
                }
            }
            // Support retrieving only a list of values rather than a model
            elseif ($queryset->values) {
    
    Jared Hancock's avatar
    Jared Hancock committed
                foreach ($queryset->values as $v) {
    
                    list($fields[]) = $this->getField($v, $model);
    
            }
            // Simple selection from one table
            else {
    
                if ($queryset->defer) {
                    foreach ($model::$meta['fields'] as $f) {
                        if (isset($queryset->defer[$f]))
                            continue;
                        $fields[] = $this->quote($f);
                    }
                }
                else {
    
                    $fields[] = $rootAlias.'.*';
    
            $joins = $this->getJoins();
    
    Jared Hancock's avatar
    Jared Hancock committed
            $sql = 'SELECT '.implode(', ', $fields).' FROM '
    
                .$table.$joins.$where.$sort;
    
    Jared Hancock's avatar
    Jared Hancock committed
            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);
    
            $sql .= ' WHERE '.$this->compileQ(new Q($model->pk), $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($model->pk));
    
            $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'];
    
            $where = $this->getWhereClause($queryset);
    
            $joins = $this->getJoins();
            $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);
            $where = $this->getWhereClause($queryset);
            $joins = $this->getJoins();
            $sql = 'UPDATE '.$this->quote($table).' SET '.$set.$joins.$where;
            return new MysqlExecutor($sql, $this->params);
        }
    
    
    Jared Hancock's avatar
    Jared Hancock committed
        // 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;
    
    Jared Hancock's avatar
    Jared Hancock committed
        }
    }
    
    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) {
    
    Jared Hancock's avatar
    Jared Hancock committed
            $this->sql = $sql;
            $this->params = $params;
    
            $this->map = $map;
        }
    
        function getMap() {
            return $this->map;
    
    Jared Hancock's avatar
    Jared Hancock committed
        }
    
        function _prepare() {
    
            $this->execute();
            $this->_setup_output();
        }
    
        function execute() {
    
    Jared Hancock's avatar
    Jared Hancock committed
            if (!($this->stmt = db_prepare($this->sql)))
                throw new Exception('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;
    
    Jared Hancock's avatar
    Jared Hancock committed
        }
    
        function _bind($params) {
            if (count($params) != $this->stmt->param_count)
    
                throw new Exception(__('Parameter count does not match query'));
    
    Jared Hancock's avatar
    Jared Hancock committed
    
            $types = '';
            $ps = array();
    
            foreach ($params as &$p) {
    
                if (is_int($p) || is_bool($p))
    
    Jared Hancock's avatar
    Jared Hancock committed
                    $types .= 'i';
    
                elseif (is_float($p))
                    $types .= 'd';
    
    Jared Hancock's avatar
    Jared Hancock committed
                elseif (is_string($p))
                    $types .= 's';
    
                // TODO: Emit error if param is null
    
    Jared Hancock's avatar
    Jared Hancock committed
                $ps[] = &$p;
            }
    
    Jared Hancock's avatar
    Jared Hancock committed
            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();
    
            $meta->free_result();
    
    Jared Hancock's avatar
    Jared Hancock committed
        }
    
        // 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);
    
    Jared Hancock's avatar
    Jared Hancock committed
            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);
    
    
    Jared Hancock's avatar
    Jared Hancock committed
            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;
        }
    
    
    Jared Hancock's avatar
    Jared Hancock committed
        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;
        }
    
    
    Jared Hancock's avatar
    Jared Hancock committed
        function __toString() {
            return $this->sql;
        }
    }
    
    
    class Q {
        const NEGATED = 0x0001;
        const ANY =     0x0002;
    
        var $constraints;
        var $flags;
        var $negated = false;
        var $ored = false;
    
        function __construct($filter, $flags=0) {
            $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;
        }
    
        static function not(array $constraints) {
            return new static($constraints, self::NEGATED);
        }
    
        static function any(array $constraints) {
            return new static($constraints, self::ORED);
        }
    }