[Solar-svn] Revision 2835

pmjones at solarphp.com pmjones at solarphp.com
Sat Oct 6 11:20:43 CDT 2007


Solar_Sql_Model:  complete and total rewrite of the base SQL model class series.


Copied: trunk/Solar/Sql/Model (from rev 2801, branches/orm/Solar/Sql/Model)

Copied: trunk/Solar/Sql/Model/Catalog (from rev 2801, branches/orm/Solar/Sql/Model/Catalog)

Copied: trunk/Solar/Sql/Model/Catalog/Exception (from rev 2801, branches/orm/Solar/Sql/Model/Catalog/Exception)

Deleted: trunk/Solar/Sql/Model/Catalog/Exception.php
===================================================================
--- branches/orm/Solar/Sql/Model/Catalog/Exception.php	2007-09-29 02:40:28 UTC (rev 2801)
+++ trunk/Solar/Sql/Model/Catalog/Exception.php	2007-10-06 16:20:43 UTC (rev 2835)
@@ -1,2 +0,0 @@
-<?php   
-class Solar_Sql_Model_Catalog_Exception extends Solar_Sql_Model_Exception {}
\ No newline at end of file

Copied: trunk/Solar/Sql/Model/Catalog/Exception.php (from rev 2801, branches/orm/Solar/Sql/Model/Catalog/Exception.php)
===================================================================
--- trunk/Solar/Sql/Model/Catalog/Exception.php	                        (rev 0)
+++ trunk/Solar/Sql/Model/Catalog/Exception.php	2007-10-06 16:20:43 UTC (rev 2835)
@@ -0,0 +1,2 @@
+<?php   
+class Solar_Sql_Model_Catalog_Exception extends Solar_Sql_Model_Exception {}
\ No newline at end of file

Deleted: trunk/Solar/Sql/Model/Catalog.php
===================================================================
--- branches/orm/Solar/Sql/Model/Catalog.php	2007-09-29 02:40:28 UTC (rev 2801)
+++ trunk/Solar/Sql/Model/Catalog.php	2007-10-06 16:20:43 UTC (rev 2835)
@@ -1,1214 +0,0 @@
-<?php
-/**
- * 
- * Provides a catalog of all properties for all loaded models.
- * 
- * @category Solar
- * 
- * @package Solar_Sql_Model
- * 
- * @author Paul M. Jones <pmjones at solarphp.com>
- * 
- * @license http://opensource.org/licenses/bsd-license.php BSD
- * 
- * @version $Id: Catalog.php 907 2007-07-22 19:05:26Z moraes $
- * 
- */
-
-/**
- * 
- * Provides a catalog of all properties for all loaded models.
- * 
- * Why keep a catalog of model properties?  So that you don't need to expend
- * resources every time you instantiate a new model object.  This also lets
- * each model know about the properties of other models, which is needed for
- * things such as paging, primary keys, inheritance, and so on.
- * 
- * @category Solar
- * 
- * @package Solar_Sql_Model
- * 
- * @todo In _fixTableCols(), add a "sync" check to see if column data in the
- * class matches column data in the database, and throw an exception if they
- * don't match pretty closely.
- * 
- * @todo Add 'delete' key to $related, to allow cascading (or refusal) of
- * deleting related records when a main record is deleted.
- * 
- * @todo Add 'custom' or 'sql' key to $related, to allow completely
- * customized SELECT statement?
- * 
- */
-class Solar_Sql_Model_Catalog extends Solar_Base {
-    
-    /**
-     * 
-     * User-provided configuration.
-     * 
-     * Keys are ...
-     * 
-     * `sql`
-     * : (dependency) A Solar_Sql dependency.
-     * 
-     * @var array
-     * 
-     */
-    protected $_Solar_Sql_Model_Catalog = array(
-        'sql'   => 'sql',
-        'cache' => null,
-    );
-    
-    /**
-     * 
-     * Catalog of all model-specific data, so we don't need to instantiate
-     * new models just to find out what their primary key is, etc.
-     * 
-     * @var array
-     * 
-     */
-    static protected $_catalog = array();
-    
-    /**
-     * 
-     * Cache to store catalog data between page loads.
-     * 
-     * @var Solar_Cache
-     * 
-     */
-    protected $_cache;
-    
-    /**
-     * 
-     * SQL object for working with the database.
-     * 
-     * @var Solar_Sql
-     * 
-     */
-    protected $_sql;
-    
-    /**
-     * 
-     * Constructor.
-     * 
-     * @param array $config User-provided configuration values.
-     * 
-     */
-    public function __construct($config = null)
-    {
-        // main construction
-        parent::__construct($config);
-        
-        // connect to the database if needed
-        $this->_sql = Solar::dependency(
-            'Solar_Sql',
-            $this->_config['sql']
-        );
-        
-        // get the optional dependency object for caching the catalog
-        if ($this->_config['cache']) {
-            $this->_cache = Solar::dependency(
-                'Solar_Cache',
-                $this->_config['cache']
-            );
-        }
-    }
-    
-    /**
-     * 
-     * Checks to see if catalog data exists for a model class.
-     * 
-     * Loads the catalog data from the cache as available.
-     * 
-     * @param string $class The model class to check.
-     * 
-     * @return bool True if the class is in the catalog, false if not.
-     * 
-     */
-    public function exists($class)
-    {
-        // do we already have it in the catalog?
-        if (empty(self::$_catalog[$class])) {
-            // do we have a cache?
-            if ($this->_cache) {
-                // is it in the cache?
-                $model = $this->_cache->fetch("Solar_Sql_Model_Catalog/$class");
-                if ($model) {
-                    // save it in the catalog
-                    self::$_catalog[$class] = $model;
-                }
-            }
-        }
-        
-        // the "real" check
-        return ! empty(self::$_catalog[$class]);
-    }
-    
-    /**
-     * 
-     * Sets the catalog data for a model class.
-     * 
-     * Generally called from Solar_Sql_Model::__construct().
-     * 
-     * @param string $class The model class name.
-     * 
-     * @param array $vars All the class properties for the model as key-value
-     * pairs.
-     * 
-     * @return void
-     * 
-     */
-    public function set($class, $vars)
-    {
-        $ignore = array('_sql');
-        
-        // set the catalog entry (blank)
-        self::$_catalog[$class] = new StdClass;
-        
-        // keep a convenient reference to the catalog entry
-        $model = self::$_catalog[$class];
-        
-        // save each of the vars in the catalog entry, as defined by the user
-        foreach ($vars as $key => $val) {
-            $key = preg_replace('/\W/', '', $key);
-            if (! in_array($key, $ignore)) {
-                $model->$key = $val;
-            }
-        }
-        
-        // follow-on cleanup of critical user-defined values
-        $this->_fixStack($model);
-        $this->_fixTableName($model);
-        $this->_fixIndex($model);
-        $this->_fixTableCols($model);
-        $this->_fixModelName($model);
-        $this->_fixOrder($model);
-        $this->_fixPropertyCols($model);
-        $this->_fixFilters($model); // including filter class
-        $this->_fixRelated($model);
-        
-        // do we have a cache?
-        if ($this->_cache) {
-            // save in the cache for next time
-            $this->_cache->save(
-                "Solar_Sql_Model_Catalog/$class",
-                $model
-            );
-        }
-    }
-    
-    /**
-     * 
-     * Resets (removes) all the catalog data, or resets (removes) just the
-     * catalog data for one class.
-     * 
-     * Be very careful when using this method.  If you have model instances
-     * that refer to each other, the relationships might break (because
-     * they use the catalog data to know what the other objects need).  Other
-     * things might break too, so beware.
-     * 
-     * @param string $class The model class name; if empty, all catalog data
-     * will be removed.
-     * 
-     * @return void
-     * 
-     */
-    public function reset($class = null)
-    {
-        if ($class) {
-            unset(self::$_catalog[$class]);
-        } else {
-            self::$_catalog = array();
-        }
-    }
-    
-    /**
-     * 
-     * Gets the catalog data for a model class.
-     * 
-     * @param string|object $spec The model class name, or an instance of the
-     * model class.
-     * 
-     * @return StdClass A clone of the catalog data for the model class.
-     * 
-     */
-    public function get($spec)
-    {
-        // get the class name
-        if (is_object($spec)) {
-            $class = get_class($spec);
-        } else {
-            $class = $spec;
-        }
-        
-        // see if it already exists; this also loads from cache if available
-        if (! $this->exists($class)) {
-            // cause it to self-register
-            // make sure to use the same SQL connection
-            Solar::factory($class, array('sql' => $this->_sql));
-            
-            // save it in the cache
-            if ($this->_cache) {
-                $this->_cache->save(
-                    "Solar_Sql_Model_Catalog/$class",
-                    self::$_catalog[$class]
-                );
-            }
-        }
-        
-        // return a clone of the catalog object (don't want others messing
-        // with the fixed data)
-        return clone(self::$_catalog[$class]);
-    }
-    
-    /**
-     * 
-     * Fixes the stack of parent classes for the model.
-     * 
-     * @param StdClass $model The model property catalog.
-     * 
-     * @return void
-     * 
-     */
-    protected function _fixStack($model)
-    {
-        $parents = Solar::parents($model->_class, true);
-        array_pop($parents); // Solar_Base
-        array_pop($parents); // Solar_Sql_Model
-        $model->_stack = Solar::factory('Solar_Class_Stack');
-        $model->_stack->add($parents);
-    }
-    
-    /**
-     * 
-     * Loads table name into $this->_table_name, and pre-sets the value of
-     * $this->_inherit_model based on the class name.
-     * 
-     * @param StdClass $model The model property catalog.
-     * 
-     * @return void
-     * 
-     */
-    protected function _fixTableName($model)
-    {
-        /**
-         * Pre-set the value of $_inherit_model.  Will be modified one
-         * more time in _fixTableCols().
-         */
-        
-        // find the closest parent called *_Model.  we do this so that
-        // we can honor the top-level table name with inherited models.
-        // *do not* use the class stack, as Solar_Sql_Model has been
-        // removed from it.
-        $parents = Solar::parents($model->_class, true);
-        foreach ($parents as $key => $val) {
-            if (substr($val, -6) == '_Model') {
-                break;
-            }
-        }
-        
-        // $key is now the value of the closest "_Model" class.
-        // -1 to get the first class below that (e.g., *_Model_Nodes).
-        // $parent is then the parent class name that represents the base of
-        // the model-inheritance hierarchy (which may not be the immediate
-        // parent in some cases).
-        $parent = $parents[$key - 1];
-        
-        // compare parent class name to the current class name.
-        // if it has an undersore after the parent class name, this class
-        // is considered to be an inheritance model.
-        $len = strlen($parent) + 1;
-        $class = $model->_class;
-        if (substr($class, 0, $len) == "{$parent}_") {
-            $model->_inherit_model = substr($class, $len);
-            $model->_inherit_base = $parent;
-        }
-        
-        // get the part after the last underscore in the parent class name.
-        // e.g., "Solar_Model_Node" => "Node".  If no underscores, use the
-        // parent class name as-is.
-        $pos = strrpos($parent, '_');
-        if ($pos === false) {
-            $table = $parent;
-        } else {
-            $table = substr($parent, $pos + 1);
-        }
-        
-        /**
-         * Auto-set the table name, if needed.
-         */
-        if (empty($model->_table_name)) {
-            // auto-defined table name. change TableName to table_name.
-            // this is our one concession on inflecting names, because if the
-            // class was called Table_Name, it would set the inheritance
-            // model improperly.
-            $table = preg_replace('/([a-z])([A-Z])/', '$1_$2', $table);
-            $model->_table_name = strtolower($table);
-        } else {
-            // user-defined table name.
-            $model->_table_name = strtolower($model->_table_name);
-        }
-    }
-    
-    /**
-     * 
-     * Fixes table column definitions into $_table_cols, and post-sets
-     * inheritance values.
-     * 
-     * @param StdClass $model The model property catalog.
-     * 
-     * @return void
-     * 
-     */
-    protected function _fixTableCols($model)
-    {
-        // is a table with the same name already at the database?
-        $list = $this->_sql->fetchTableList();
-        
-        // if not found, attempt to create it
-        if (! in_array($model->_table_name, $list)) {
-            $this->_createTableAndIndexes($model);
-        }
-        
-        // reset the columns to be **as they are at the database**
-        $model->_table_cols = $this->_sql->fetchTableCols($model->_table_name);
-        
-        // @todo add a "sync" check to see if column data in the class
-        // matches column data in the database, and throw an exception
-        // if they don't match pretty closely.
-        
-        // set the primary column based on the first primary key;
-        // ignores later primary keys
-        foreach ($model->_table_cols as $key => $val) {
-            if ($val['primary']) {
-                $model->_primary_col = $key;
-                break;
-            }
-        }
-    }
-    
-    /**
-     * 
-     * Fixes up special column indicator properties, and post-sets the
-     * $_inherit_model value based on the existence of the inheritance column.
-     * 
-     * @param StdClass $model The model property catalog.
-     * 
-     * @return void
-     * 
-     * @todo How to make foreign_col recognize that it's inherited, and should
-     * use the parent foreign_col value?  Can we just work up the chain?
-     * 
-     */
-    protected function _fixPropertyCols($model)
-    {
-        // make sure these actually exist in the table, otherwise unset them
-        $list = array(
-            '_created_col',
-            '_updated_col',
-            '_primary_col',
-            '_inherit_col',
-        );
-        
-        foreach ($list as $col) {
-            if (trim($model->$col) == '' ||
-                ! array_key_exists($model->$col, $model->_table_cols)) {
-                // doesn't exist in the table
-                $model->$col = null;
-            }
-        }
-        
-        // post-set the inheritance model value
-        if (! $model->_inherit_col) {
-            $model->_inherit_model = null;
-            $model->_inherit_base = null;
-        }
-        
-        // set up the fetch-cols list
-        settype($model->_fetch_cols, 'array');
-        if (! $model->_fetch_cols) {
-            $model->_fetch_cols = array_keys($model->_table_cols);
-        }
-        
-        // simply force to array
-        settype($model->_serialize_cols, 'array');
-        
-        // the "sequence" columns.  make sure they point to a sequence name.
-        // e.g., string 'col' becomes 'col' => 'col'.
-        $tmp = array();
-        foreach ((array) $model->_sequence_cols as $key => $val) {
-            if (is_int($key)) {
-                $tmp[$val] = $val;
-            } else {
-                $tmp[$key] = $val;
-            }
-        }
-        $model->_sequence_cols = $tmp;
-        
-        // make sure we have a hint to foreign models as to what colname
-        // to use when referring to this model
-        if (empty($model->_foreign_col)) {
-            
-            if (! $model->_inherit_model) {
-                // not inherited
-                $model->_foreign_col = strtolower($model->_model_name)
-                                     . '_' . $model->_primary_col;
-            } else {
-                // inherited, can't just use the model name as a column name.
-                // need to find base model foreign_col value.
-                $base = $this->get($model->_inherit_base);
-                $model->_foreign_col = $base->_foreign_col;
-            }
-        }
-    }
-    
-    /**
-     * 
-     * Loads the array-name for user input to this model.
-     * 
-     * @param StdClass $model The model property catalog.
-     * 
-     * @return void
-     * 
-     */
-    protected function _fixModelName($model)
-    {
-        if (! $model->_model_name) {
-            if ($model->_inherit_model) {
-                $model->_model_name = $model->_inherit_model;
-            } else {
-                // get the part after the last Model_ portion
-                $pos = strpos($model->_class, 'Model_');
-                if ($pos) {
-                    $model->_model_name = substr($model->_class, $pos+6);
-                } else {
-                    $model->_model_name = $model->_class;
-                }
-            }
-            
-            // convert FooBar to foo_bar
-            $model->_model_name = strtolower(
-                preg_replace('/([a-z])([A-Z])/', '$1_$2', $model->_model_name)
-            );
-        }
-    }
-    
-    protected function _fixOrder($model)
-    {
-        if (! $model->_order) {
-            $model->_order = $model->_model_name . '.' . $model->_primary_col;
-        }
-    }
-    
-    /**
-     * 
-     * Loads the baseline data filters for each column.
-     * 
-     * @param StdClass $model The model property catalog.
-     * 
-     * @return void
-     * 
-     */
-    protected function _fixFilters($model)
-    {
-        // make sure we have a filter class
-        if (empty($model->_filter_class)) {
-            $class = $model->_stack->load('Filter', false);
-            if (! $class) {
-                $class = 'Solar_Sql_Model_Filter';
-            }
-            $model->_filter_class = $class;
-        }
-        
-        // make sure filters are an array
-        settype($model->_filters, 'array');
-        
-        // make sure that strings are converted
-        // to arrays so that _applyFilters() works properly.
-        foreach ($model->_filters as $col => $list) {
-            foreach ($list as $key => $val) {
-                if (is_string($val)) {
-                    $model->_filters[$col][$key] = array($val);
-                }
-            }
-        }
-        
-        // low and high range values for integer filters
-        $range = array(
-            'smallint' => array(pow(-2, 15), pow(+2, 15) - 1),
-            'int'      => array(pow(-2, 31), pow(+2, 31) - 1),
-            'bigint'   => array(pow(-2, 63), pow(+2, 63) - 1)
-        );
-        
-        // add final fallback filters based on data type
-        foreach ($model->_table_cols as $col => $info) {
-            
-            
-            $type = $info['type'];
-            switch ($type) {
-            case 'bool':
-                $model->_filters[$col][] = array('validateBool');
-                $model->_filters[$col][] = array('sanitizeBool');
-                break;
-            
-            case 'char':
-            case 'varchar':
-                // only add filters if not serializing
-                if (! in_array($col, $model->_serialize_cols)) {
-                    $model->_filters[$col][] = array('validateString');
-                    $model->_filters[$col][] = array('validateMaxLength',
-                        $info['size']);
-                    $model->_filters[$col][] = array('sanitizeString');
-                }
-                break;
-            
-            case 'smallint':
-            case 'int':
-            case 'bigint':
-                $model->_filters[$col][] = array('validateInt');
-                $model->_filters[$col][] = array('validateRange',
-                    $range[$type][0], $range[$type][1]);
-                $model->_filters[$col][] = array('sanitizeInt');
-                break;
-            
-            case 'numeric':
-                $model->_filters[$col][] = array('validateNumeric');
-                $model->_filters[$col][] = array('validateSizeScope',
-                    $info['size'], $info['scope']);
-                $model->_filters[$col][] = array('sanitizeNumeric');
-                break;
-            
-            case 'float':
-                $model->_filters[$col][] = array('validateFloat');
-                $model->_filters[$col][] = array('sanitizeFloat');
-                break;
-            
-            case 'clob':
-                // no filters, clobs are pretty generic
-                break;
-            
-            case 'date':
-                $model->_filters[$col][] = array('validateIsoDate');
-                $model->_filters[$col][] = array('sanitizeIsoDate');
-                break;
-            
-            case 'time':
-                $model->_filters[$col][] = array('validateIsoTime');
-                $model->_filters[$col][] = array('sanitizeIsoTime');
-                break;
-            
-            case 'timestamp':
-                $model->_filters[$col][] = array('validateIsoTimestamp');
-                $model->_filters[$col][] = array('sanitizeIsoTimestamp');
-                break;
-            }
-        }
-    }
-    
-    /**
-     * 
-     * Corrects the relationship definitions in $this->_related.
-     * 
-     * `name`
-     * : (string) The name for this relationship; becomes a property key in
-     *   the record, so it should not be allowed to conflict with native
-     *   column names.
-     * 
-     * `type`
-     * : (string) The association type: has_one, belongs_to, has_many,
-     *   shares_many.
-     * 
-     * `foreign_model`
-     * : (string) The class name of the foreign model. Default is the first
-     *   matching class for the relationship name, as loaded from the parent
-     *   class stack. Automatically honors single-table inheritance.
-     * 
-     * `foreign_table`
-     * : (string) The name of the table for the foreign model. Default is the
-     *   table specified by the foreign model.
-     * 
-     * `foreign_alias`
-     * : (string) Aliases the foreign table to this name. Default is the
-     *   relationship name.
-     * 
-     * `foreign_col`
-     * : (string) The name of the column to join with in the *foreign* table.
-     *   This forms one-half of the relationship.  Default is per association
-     *   type.
-     * 
-     * `foreign_inherit`
-     * : (string) If the foreign model has an inheritance type, this is a
-     *   condition suitable for WHERE and JOIN clauses to retrieve only
-     *   records of the proper model.
-     * 
-     * `native_col`
-     * : (string) The name of the column to join with in the *native* table.
-     *   This forms one-half of the relationship.  Default is per association
-     *   type.
-     * 
-     * `distinct`
-     * : (bool) Should the relationship be DISTINCT or not?  Default false.
-     * 
-     * `where`
-     * : (string|array) Additional WHERE clause to determine what record is
-     *   returned.  Default is no conditions.
-     * 
-     * `group`
-     * : (string|array) A GROUP clause to determine how foreign records are
-     *   grouped.  Default is none.
-     * 
-     * `having`
-     * : (string|array) A HAVING clause to determine a post-grouping
-     *   condition.  Default is none.
-     * 
-     * `order`
-     * : (string|array) An ORDER clause to determine how foreign records are
-     *   ordered. Default is the foreign model default order.
-     * 
-     * `paging`
-     * : (int) Retrieve this many records per page (0 for all).  Default is
-     *   the foreign model default paging value.
-     * 
-     * `fetch`
-     * : (string) Fetch one, all, assoc, value, etc.
-     * 
-     * `cols`
-     * : (array|string) Retrieve these columns from the foreign model.
-     *   Default is all columns; primary-key and inheritance columns are
-     *   added automatically.
-     * 
-     * <http://ar.rubyonrails.com/classes/ActiveRecord/Associations/ClassMethods.html>
-     * 
-     * There is a virtual element called `foreign_key` that automatically
-     * populates the `native_col` or `foreign_col` value for you, based on the
-     * association type.  This will be used **only** when `native_col` **and**
-     * `foreign_col` are not set.
-     * 
-     * There is a virtual element called `through_key` that automatically 
-     * populates the 'through_foreign_col' value for you.
-     * 
-     * @param StdClass $model The model property catalog.
-     * 
-     * @return void
-     * 
-     * @todo Add `through*` keys to the definition list above.
-     * 
-     * @todo Add 'native_table' and 'native_alias' values? Then *everything*
-     * is in the array, no need for $this->_whatever.
-     * 
-     */
-    protected function _fixRelated($model)
-    {
-        foreach ($model->_related as $name => $opts) {
-            
-            // is the relation name already a column name?
-            if (array_key_exists($name, $model->_table_cols)) {
-                throw $this->_exception(
-                    'ERR_RELATION_NAME_CONFLICT',
-                    array(
-                        'name'  => $name,
-                        'model' => $model,
-                    )
-                );
-            } else {
-                $opts['name'] = $name;
-            }
-            
-            // what type of association is this?
-            $type = $opts['type'];
-            
-            // make sure we have at least a base model name
-            if (empty($opts['foreign_model'])) {
-                $opts['foreign_model'] = $name;
-            }
-            
-            // can we load a related model class from the hierarchy stack?
-            $class = $model->_stack->load($opts['foreign_model'], false);
-            
-            // did we find it?
-            if (! $class) {
-                // look for a "parallel" class name, based on where the word
-                // "Model" is in the current class name. this lets you pull
-                // model classes from the same level, not from the inheritance
-                // stack.
-                $pos = strrpos($model->_class, 'Model_');
-                if ($pos !== false) {
-                    $pos += 6; // "Model_"
-                    $tmp = substr($model->_class, 0, $pos) . ucfirst($opts['foreign_model']);
-                    try {
-                        Solar::loadClass($tmp);
-                        // if no exception, $class gets set
-                        $class = $tmp;
-                    } catch (Exception $e) {
-                        // do nothing, 
-                    }
-                }
-            }
-            
-            // not in the hierarchy, and no parallel class name. look for the
-            // model class literally. this will throw an exception if the
-            // class cannot be found anywhere.
-            if (! $class) {
-                try {
-                    Solar::loadClass($opts['foreign_model']);
-                    // if no exception, $class gets set
-                    $class = $opts['foreign_model'];
-                } catch (Solar_Exception $e) {
-                    throw $this->_exception('ERR_LOAD_FOREIGN_MODEL', array(
-                        'native_model' => $model->_class,
-                        'related_name' => $name,
-                        'related_opts' => $opts,
-                    ));
-                }
-            }
-            
-            // finally we have a class name, keep it as the foreign model class
-            $opts['foreign_model'] = $class;
-            
-            // catalog data for the foreign model
-            $foreign = $this->get($class);
-            
-            // the foreign table name
-            if (empty($opts['foreign_table'])) {
-                $opts['foreign_table'] = $foreign->_table_name;
-            }
-            
-            // the foreign table alias
-            if (empty($opts['foreign_alias'])) {
-                $opts['foreign_alias'] = $name;
-            }
-            
-            // custom WHERE clauses
-            if (empty($opts['where'])) {
-                $opts['where'] = array();
-            }
-            settype($opts['where'], 'array');
-            
-            // the list of foreign table cols to retrieve
-            if (empty($opts['cols'])) {
-                $opts['cols'] = $foreign->_fetch_cols;
-            } elseif (is_string($opts['cols'])) {
-                $opts['cols'] = explode(',', $opts['cols']);
-            } else {
-                settype($opts['cols'], 'array');
-            }
-            
-            // make sure we always retrieve the foreign primary key value,
-            // if there is one.
-            $col = $foreign->_primary_col;
-            if ($col && ! in_array($col, $opts['cols'])) {
-                $opts['cols'][] = $col;
-            }
-            
-            // if inheritance is turned on for the foreign mode,
-            // make sure we always retrieve the foreign inheritance value.
-            $col = $foreign->_inherit_col;
-            if ($col && ! in_array($col, $opts['cols'])) {
-                $opts['cols'][] = $col;
-            }
-            
-            // if inheritance is turned on, force the foreign_inherit
-            // column and value
-            if ($foreign->_inherit_col && $foreign->_inherit_model) {
-                $opts['foreign_inherit_col'] = $foreign->_inherit_col;
-                $opts['foreign_inherit_val'] = $foreign->_inherit_model;
-            } else {
-                $opts['foreign_inherit_col'] = null;
-                $opts['foreign_inherit_val'] = null;
-            }
-            
-            // paging from the foreign model
-            if (empty($opts['paging'])) {
-                $opts['paging'] = $foreign->_paging;
-            }
-            
-            // default page number; use "all pages" if not specified.
-            // note that we don't use empty() here, because we want to allow
-            // for "page = 0".
-            if (! array_key_exists('page', $opts)) {
-                $opts['page'] = 0;
-            }
-            
-            // distinct?
-            if (empty($opts['distinct'])) {
-                $opts['distinct'] = null;
-            }
-            
-            // group?
-            if (empty($opts['group'])) {
-                $opts['group'] = null;
-            }
-            
-            // having?
-            if (empty($opts['having'])) {
-                $opts['having'] = null;
-            }
-            
-            // and now, the tricky part ;-)
-            switch ($opts['type']) {
-            case 'belongs_to':
-                $this->_fixRelatedBelongsTo($opts, $model, $foreign);
-                break;
-            case 'has_one':
-                $this->_fixRelatedHasOne($opts, $model, $foreign);
-                break;
-            case 'has_many':
-                // this will check for "has_many through" as well
-                $this->_fixRelatedHasMany($opts, $model, $foreign);
-                break;
-            }
-            
-            // custom ORDER clause
-            if (empty($opts['order'])) {
-                $opts['order'] = "{$opts['foreign_alias']}.{$foreign->_primary_col}";
-            }
-            settype($opts['order'], 'array');
-            
-            // retain the corrected values in a standard order
-            $model->_related[$name] = array(
-                'name'                => $name,
-                'type'                => $opts['type'],
-                'foreign_model'       => $opts['foreign_model'],
-                'foreign_table'       => $opts['foreign_table'],
-                'foreign_alias'       => $opts['foreign_alias'],
-                'foreign_col'         => $opts['foreign_col'],
-                'foreign_inherit_col' => $opts['foreign_inherit_col'],
-                'foreign_inherit_val' => $opts['foreign_inherit_val'],
-                'foreign_primary_col' => $foreign->_primary_col,
-                'native_table'        => $model->_table_name,
-                'native_alias'        => $model->_model_name,
-                'native_col'          => $opts['native_col'],
-                'through'             => $opts['through'],
-                'through_table'       => $opts['through_table'],
-                'through_alias'       => $opts['through_alias'],
-                'through_native_col'  => $opts['through_native_col'],
-                'through_foreign_col' => $opts['through_foreign_col'],
-                'distinct'            => $opts['distinct'],
-                'where'               => $opts['where'],
-                'group'               => $opts['group'],
-                'having'              => $opts['having'],
-                'order'               => $opts['order'],
-                'paging'              => (int) $opts['paging'],
-                'page'                => (int) $opts['page'],
-                'cols'                => $opts['cols'],
-            );
-        }
-    }
-    
-    /**
-     * 
-     * A support method for _fixRelated() to handle belongs-to relationships.
-     * 
-     * @param array &$opts The relationship options; these are modified in-
-     * place.
-     * 
-     * @param StdClass $model The catalog entry for the native model (i.e.,
-     * this model).
-     * 
-     * @param StdClass $foreign The catalog entry for the foreign model.
-     * 
-     * @return void
-     * 
-     */
-    protected function _fixRelatedBelongsTo(&$opts, $model, $foreign)
-    {
-        // a little magic
-        if (empty($opts['foreign_col']) && empty($opts['native_col']) &&
-            ! empty($opts['foreign_key'])) {
-            // foreign key is stored in the native model
-            $opts['native_col'] = $opts['foreign_key'];
-        }
-        
-        // the foreign column
-        if (empty($opts['foreign_col'])) {
-            // named by foreign primary key (e.g., foreign.id)
-            $opts['foreign_col'] = $foreign->_primary_col;
-        }
-        
-        // the native column
-        if (empty($opts['native_col'])) {
-            // named by foreign table name and foreign primary key
-            // (e.g., native.foreign_id)
-            $opts['native_col'] = $foreign->_foreign_col;
-        }
-        
-        // not "through" anything
-        $this->_fixRelatedNotThrough($opts);
-    }
-    
-    /**
-     * 
-     * A support method for _fixRelated() to handle has-one relationships.
-     * 
-     * @param array &$opts The relationship options; these are modified in-
-     * place.
-     * 
-     * @param StdClass $model The catalog entry for the native model (i.e.,
-     * this model).
-     * 
-     * @param StdClass $foreign The catalog entry for the foreign model.
-     * 
-     * @return void
-     * 
-     */
-    protected function _fixRelatedHasOne(&$opts, $model, $foreign)
-    {
-        // a little magic
-        if (empty($opts['foreign_col']) && empty($opts['native_col']) &&
-            ! empty($opts['foreign_key'])) {
-            // foreign key is stored in the foreign model
-            $opts['foreign_col'] = $opts['foreign_key'];
-        }
-        
-        // the foreign column
-        if (empty($opts['foreign_col'])) {
-            // named by native table and native primary (e.g.,
-            // foreign.native_id)
-            $opts['foreign_col'] = $model->_foreign_col;
-        }
-        
-        // the native column
-        if (empty($opts['native_col'])) {
-            // named by native primary key (e.g., native.id)
-            $opts['native_col'] = $model->_primary_col;
-        }
-        
-        // not "through" anything
-        $this->_fixRelatedNotThrough($opts);
-    }
-    
-    /**
-     * 
-     * A support method for _fixRelated() to handle has-many relationships.
-     * 
-     * @param array &$opts The relationship options; these are modified in-
-     * place.
-     * 
-     * @param StdClass $model The catalog entry for the native model (i.e.,
-     * this model).
-     * 
-     * @param StdClass $foreign The catalog entry for the foreign model.
-     * 
-     * @return void
-     * 
-     */
-    protected function _fixRelatedHasMany(&$opts, $model, $foreign)
-    {
-        // are we working through another relationship?
-        if (! empty($opts['through'])) {
-            // through another relationship, hand off to another method
-            return $this->_fixRelatedHasManyThrough($opts, $model, $foreign);
-        } else {
-            // not "through" anything, clear the "through" keys
-            $this->_fixRelatedNotThrough($opts);
-        }
-        
-        // a little magic
-        if (empty($opts['foreign_col']) && empty($opts['native_col']) &&
-            ! empty($opts['foreign_key'])) {
-            // foreign key is stored in the foreign model
-            $opts['foreign_col'] = $opts['foreign_key'];
-        }
-        
-        // the foreign column
-        if (empty($opts['foreign_col'])) {
-            // named by native table and native primary (e.g.,
-            // foreign.native_id)
-            $opts['foreign_col'] = $model->_foreign_col;
-        }
-        
-        // the native column
-        if (empty($opts['native_col'])) {
-            // named by native primary key (e.g., native.id)
-            $opts['native_col'] = $model->_primary_col;
-        }
-    }
-    
-    /**
-     * 
-     * A support method for _fixRelatedHasMany() to handle "through"
-     * relationships.
-     * 
-     * @param array &$opts The relationship options; these are modified in-
-     * place.
-     * 
-     * @param StdClass $model The catalog entry for the native model (i.e.,
-     * this model).
-     * 
-     * @param StdClass $foreign The catalog entry for the foreign model.
-     * 
-     * @return void
-     * 
-     */
-    protected function _fixRelatedHasManyThrough(&$opts, $model, $foreign)
-    {
-        // make sure the "through" relationship exists
-        if (empty($model->_related[$opts['through']])) {
-            throw $this->_exception('ERR_THROUGH_NOT_EXIST', array(
-                'model'   => $model->_class,
-                'name'    => $opts['name'],
-                'through' => $opts['through'],
-            ));
-        }
-        
-        // a little magic
-        if (empty($opts['foreign_col']) && empty($opts['native_col']) &&
-            ! empty($opts['foreign_key'])) {
-            // foreign key is stored in the foreign model
-            $opts['foreign_col'] = $opts['foreign_key'];
-        }
-        
-        // the foreign column
-        if (empty($opts['foreign_col'])) {
-            // named by foreign primary key (e.g., foreign.id)
-            $opts['foreign_col'] = $foreign->_primary_col;
-        }
-        
-        // the native column
-        if (empty($opts['native_col'])) {
-            // named by native primary key (e.g., native.id)
-            $opts['native_col'] = $model->_primary_col;
-        }
-        
-        // convenient reference to the "through" relationship
-        $through = $model->_related[$opts['through']];
-        
-        // get the through-table
-        if (empty($opts['through_table'])) {
-            $opts['through_table'] = $through['foreign_table'];
-        }
-        
-        // get the through-alias
-        if (empty($opts['through_alias'])) {
-            $opts['through_alias'] = $through['foreign_alias'];
-        }
-        
-        // a little magic
-        if (empty($opts['through_native_col']) &&
-            empty($opts['through_foreign_col']) &&
-            ! empty($opts['through_key'])) {
-            // use this
-            $opts['through_foreign_col'] = $opts['through_key'];
-        }
-        
-        // what's the native model key in the through table?
-        if (empty($opts['through_native_col'])) {
-            $opts['through_native_col'] = $through['foreign_col'];
-        }
-        
-        // what's the foreign model key in the through table?
-        if (empty($opts['through_foreign_col'])) {
-            $opts['through_foreign_col'] = $foreign->_foreign_col;
-        }
-    }
-    
-    /**
-     * 
-     * A support method for _fixRelatedHasMany() to set the "through" options
-     * to blanks.
-     * 
-     * @param array &$opts The relationship options; these are modified in-
-     * place.
-     * 
-     * @param StdClass $model The catalog entry for the native model (i.e.,
-     * this model).
-     * 
-     * @param StdClass $foreign The catalog entry for the foreign model.
-     * 
-     * @return void
-     * 
-     */
-    protected function _fixRelatedNotThrough(&$opts)
-    {
-        $opts['through']                = null;
-        $opts['through_table']          = null;
-        $opts['through_alias']          = null;
-        $opts['through_native_col']     = null;
-        $opts['through_foreign_col']    = null;
-    }
-    
-    /**
-     * 
-     * Fixes $this->_index listings.
-     * 
-     * @param StdClass $model The model property catalog.
-     * 
-     * @return void
-     * 
-     */
-    protected function _fixIndex($model)
-    {
-        // baseline index definition
-        $baseidx = array(
-            'name'    => null,
-            'type'    => 'normal',
-            'cols'    => null,
-        );
-        
-        // fix up each index to have a full set of info
-        foreach ($model->_index as $key => $val) {
-            
-            if (is_int($key) && is_string($val)) {
-                // array('col')
-                $info = array(
-                    'name' => $val,
-                    'type' => 'normal',
-                    'cols' => array($val),
-                );
-            } elseif (is_string($key) && is_string($val)) {
-                // array('col' => 'unique')
-                $info = array(
-                    'name' => $key,
-                    'type' => $val,
-                    'cols' => array($key),
-                );
-            } else {
-                // array('alt' => array('type' => 'normal', 'cols' => array(...)))
-                $info = array_merge($baseidx, (array) $val);
-                $info['name'] = (string) $key;
-                settype($info['cols'], 'array');
-            }
-            
-            $model->_index[$key] = $info;
-        }
-    }
-    
-    /**
-     * 
-     * Creates the table and indexes in the database using $model->_table_cols
-     * and $model->_index.
-     * 
-     * @param StdClass $model The model property catalog.
-     * 
-     * @return void
-     * 
-     */
-    protected function _createTableAndIndexes($model)
-    {
-        /**
-         * Create the table.
-         */
-        $this->_sql->createTable(
-            $model->_table_name,
-            $model->_table_cols
-        );
-        
-        /**
-         * Create the indexes.
-         */
-        foreach ($model->_index as $name => $info) {
-            try {
-                // create this index
-                $this->_sql->createIndex(
-                    $model->_table_name,
-                    $info['name'],
-                    $info['type'] == 'unique',
-                    $info['cols']
-                );
-            } catch (Exception $e) {
-                // cancel the whole deal.
-                $this->_sql->dropTable($model->_table_name);
-                throw $e;
-            }
-        }
-    }
-}

Copied: trunk/Solar/Sql/Model/Catalog.php (from rev 2801, branches/orm/Solar/Sql/Model/Catalog.php)
===================================================================
--- trunk/Solar/Sql/Model/Catalog.php	                        (rev 0)
+++ trunk/Solar/Sql/Model/Catalog.php	2007-10-06 16:20:43 UTC (rev 2835)
@@ -0,0 +1,1214 @@
+<?php
+/**
+ * 
+ * Provides a catalog of all properties for all loaded models.
+ * 
+ * @category Solar
+ * 
+ * @package Solar_Sql_Model
+ * 
+ * @author Paul M. Jones <pmjones at solarphp.com>
+ * 
+ * @license http://opensource.org/licenses/bsd-license.php BSD
+ * 
+ * @version $Id: Catalog.php 907 2007-07-22 19:05:26Z moraes $
+ * 
+ */
+
+/**
+ * 
+ * Provides a catalog of all properties for all loaded models.
+ * 
+ * Why keep a catalog of model properties?  So that you don't need to expend
+ * resources every time you instantiate a new model object.  This also lets
+ * each model know about the properties of other models, which is needed for
+ * things such as paging, primary keys, inheritance, and so on.
+ * 
+ * @category Solar
+ * 
+ * @package Solar_Sql_Model
+ * 
+ * @todo In _fixTableCols(), add a "sync" check to see if column data in the
+ * class matches column data in the database, and throw an exception if they
+ * don't match pretty closely.
+ * 
+ * @todo Add 'delete' key to $related, to allow cascading (or refusal) of
+ * deleting related records when a main record is deleted.
+ * 
+ * @todo Add 'custom' or 'sql' key to $related, to allow completely
+ * customized SELECT statement?
+ * 
+ */
+class Solar_Sql_Model_Catalog extends Solar_Base {
+    
+    /**
+     * 
+     * User-provided configuration.
+     * 
+     * Keys are ...
+     * 
+     * `sql`
+     * : (dependency) A Solar_Sql dependency.
+     * 
+     * @var array
+     * 
+     */
+    protected $_Solar_Sql_Model_Catalog = array(
+        'sql'   => 'sql',
+        'cache' => null,
+    );
+    
+    /**
+     * 
+     * Catalog of all model-specific data, so we don't need to instantiate
+     * new models just to find out what their primary key is, etc.
+     * 
+     * @var array
+     * 
+     */
+    static protected $_catalog = array();
+    
+    /**
+     * 
+     * Cache to store catalog data between page loads.
+     * 
+     * @var Solar_Cache
+     * 
+     */
+    protected $_cache;
+    
+    /**
+     * 
+     * SQL object for working with the database.
+     * 
+     * @var Solar_Sql
+     * 
+     */
+    protected $_sql;
+    
+    /**
+     * 
+     * Constructor.
+     * 
+     * @param array $config User-provided configuration values.
+     * 
+     */
+    public function __construct($config = null)
+    {
+        // main construction
+        parent::__construct($config);
+        
+        // connect to the database if needed
+        $this->_sql = Solar::dependency(
+            'Solar_Sql',
+            $this->_config['sql']
+        );
+        
+        // get the optional dependency object for caching the catalog
+        if ($this->_config['cache']) {
+            $this->_cache = Solar::dependency(
+                'Solar_Cache',
+                $this->_config['cache']
+            );
+        }
+    }
+    
+    /**
+     * 
+     * Checks to see if catalog data exists for a model class.
+     * 
+     * Loads the catalog data from the cache as available.
+     * 
+     * @param string $class The model class to check.
+     * 
+     * @return bool True if the class is in the catalog, false if not.
+     * 
+     */
+    public function exists($class)
+    {
+        // do we already have it in the catalog?
+        if (empty(self::$_catalog[$class])) {
+            // do we have a cache?
+            if ($this->_cache) {
+                // is it in the cache?
+                $model = $this->_cache->fetch("Solar_Sql_Model_Catalog/$class");
+                if ($model) {
+                    // save it in the catalog
+                    self::$_catalog[$class] = $model;
+                }
+            }
+        }
+        
+        // the "real" check
+        return ! empty(self::$_catalog[$class]);
+    }
+    
+    /**
+     * 
+     * Sets the catalog data for a model class.
+     * 
+     * Generally called from Solar_Sql_Model::__construct().
+     * 
+     * @param string $class The model class name.
+     * 
+     * @param array $vars All the class properties for the model as key-value
+     * pairs.
+     * 
+     * @return void
+     * 
+     */
+    public function set($class, $vars)
+    {
+        $ignore = array('_sql');
+        
+        // set the catalog entry (blank)
+        self::$_catalog[$class] = new StdClass;
+        
+        // keep a convenient reference to the catalog entry
+        $model = self::$_catalog[$class];
+        
+        // save each of the vars in the catalog entry, as defined by the user
+        foreach ($vars as $key => $val) {
+            $key = preg_replace('/\W/', '', $key);
+            if (! in_array($key, $ignore)) {
+                $model->$key = $val;
+            }
+        }
+        
+        // follow-on cleanup of critical user-defined values
+        $this->_fixStack($model);
+        $this->_fixTableName($model);
+        $this->_fixIndex($model);
+        $this->_fixTableCols($model);
+        $this->_fixModelName($model);
+        $this->_fixOrder($model);
+        $this->_fixPropertyCols($model);
+        $this->_fixFilters($model); // including filter class
+        $this->_fixRelated($model);
+        
+        // do we have a cache?
+        if ($this->_cache) {
+            // save in the cache for next time
+            $this->_cache->save(
+                "Solar_Sql_Model_Catalog/$class",
+                $model
+            );
+        }
+    }
+    
+    /**
+     * 
+     * Resets (removes) all the catalog data, or resets (removes) just the
+     * catalog data for one class.
+     * 
+     * Be very careful when using this method.  If you have model instances
+     * that refer to each other, the relationships might break (because
+     * they use the catalog data to know what the other objects need).  Other
+     * things might break too, so beware.
+     * 
+     * @param string $class The model class name; if empty, all catalog data
+     * will be removed.
+     * 
+     * @return void
+     * 
+     */
+    public function reset($class = null)
+    {
+        if ($class) {
+            unset(self::$_catalog[$class]);
+        } else {
+            self::$_catalog = array();
+        }
+    }
+    
+    /**
+     * 
+     * Gets the catalog data for a model class.
+     * 
+     * @param string|object $spec The model class name, or an instance of the
+     * model class.
+     * 
+     * @return StdClass A clone of the catalog data for the model class.
+     * 
+     */
+    public function get($spec)
+    {
+        // get the class name
+        if (is_object($spec)) {
+            $class = get_class($spec);
+        } else {
+            $class = $spec;
+        }
+        
+        // see if it already exists; this also loads from cache if available
+        if (! $this->exists($class)) {
+            // cause it to self-register
+            // make sure to use the same SQL connection
+            Solar::factory($class, array('sql' => $this->_sql));
+            
+            // save it in the cache
+            if ($this->_cache) {
+                $this->_cache->save(
+                    "Solar_Sql_Model_Catalog/$class",
+                    self::$_catalog[$class]
+                );
+            }
+        }
+        
+        // return a clone of the catalog object (don't want others messing
+        // with the fixed data)
+        return clone(self::$_catalog[$class]);
+    }
+    
+    /**
+     * 
+     * Fixes the stack of parent classes for the model.
+     * 
+     * @param StdClass $model The model property catalog.
+     * 
+     * @return void
+     * 
+     */
+    protected function _fixStack($model)
+    {
+        $parents = Solar::parents($model->_class, true);
+        array_pop($parents); // Solar_Base
+        array_pop($parents); // Solar_Sql_Model
+        $model->_stack = Solar::factory('Solar_Class_Stack');
+        $model->_stack->add($parents);
+    }
+    
+    /**
+     * 
+     * Loads table name into $this->_table_name, and pre-sets the value of
+     * $this->_inherit_model based on the class name.
+     * 
+     * @param StdClass $model The model property catalog.
+     * 
+     * @return void
+     * 
+     */
+    protected function _fixTableName($model)
+    {
+        /**
+         * Pre-set the value of $_inherit_model.  Will be modified one
+         * more time in _fixTableCols().
+         */
+        
+        // find the closest parent called *_Model.  we do this so that
+        // we can honor the top-level table name with inherited models.
+        // *do not* use the class stack, as Solar_Sql_Model has been
+        // removed from it.
+        $parents = Solar::parents($model->_class, true);
+        foreach ($parents as $key => $val) {
+            if (substr($val, -6) == '_Model') {
+                break;
+            }
+        }
+        
+        // $key is now the value of the closest "_Model" class.
+        // -1 to get the first class below that (e.g., *_Model_Nodes).
+        // $parent is then the parent class name that represents the base of
+        // the model-inheritance hierarchy (which may not be the immediate
+        // parent in some cases).
+        $parent = $parents[$key - 1];
+        
+        // compare parent class name to the current class name.
+        // if it has an undersore after the parent class name, this class
+        // is considered to be an inheritance model.
+        $len = strlen($parent) + 1;
+        $class = $model->_class;
+        if (substr($class, 0, $len) == "{$parent}_") {
+            $model->_inherit_model = substr($class, $len);
+            $model->_inherit_base = $parent;
+        }
+        
+        // get the part after the last underscore in the parent class name.
+        // e.g., "Solar_Model_Node" => "Node".  If no underscores, use the
+        // parent class name as-is.
+        $pos = strrpos($parent, '_');
+        if ($pos === false) {
+            $table = $parent;
+        } else {
+            $table = substr($parent, $pos + 1);
+        }
+        
+        /**
+         * Auto-set the table name, if needed.
+         */
+        if (empty($model->_table_name)) {
+            // auto-defined table name. change TableName to table_name.
+            // this is our one concession on inflecting names, because if the
+            // class was called Table_Name, it would set the inheritance
+            // model improperly.
+            $table = preg_replace('/([a-z])([A-Z])/', '$1_$2', $table);
+            $model->_table_name = strtolower($table);
+        } else {
+            // user-defined table name.
+            $model->_table_name = strtolower($model->_table_name);
+        }
+    }
+    
+    /**
+     * 
+     * Fixes table column definitions into $_table_cols, and post-sets
+     * inheritance values.
+     * 
+     * @param StdClass $model The model property catalog.
+     * 
+     * @return void
+     * 
+     */
+    protected function _fixTableCols($model)
+    {
+        // is a table with the same name already at the database?
+        $list = $this->_sql->fetchTableList();
+        
+        // if not found, attempt to create it
+        if (! in_array($model->_table_name, $list)) {
+            $this->_createTableAndIndexes($model);
+        }
+        
+        // reset the columns to be **as they are at the database**
+        $model->_table_cols = $this->_sql->fetchTableCols($model->_table_name);
+        
+        // @todo add a "sync" check to see if column data in the class
+        // matches column data in the database, and throw an exception
+        // if they don't match pretty closely.
+        
+        // set the primary column based on the first primary key;
+        // ignores later primary keys
+        foreach ($model->_table_cols as $key => $val) {
+            if ($val['primary']) {
+                $model->_primary_col = $key;
+                break;
+            }
+        }
+    }
+    
+    /**
+     * 
+     * Fixes up special column indicator properties, and post-sets the
+     * $_inherit_model value based on the existence of the inheritance column.
+     * 
+     * @param StdClass $model The model property catalog.
+     * 
+     * @return void
+     * 
+     * @todo How to make foreign_col recognize that it's inherited, and should
+     * use the parent foreign_col value?  Can we just work up the chain?
+     * 
+     */
+    protected function _fixPropertyCols($model)
+    {
+        // make sure these actually exist in the table, otherwise unset them
+        $list = array(
+            '_created_col',
+            '_updated_col',
+            '_primary_col',
+            '_inherit_col',
+        );
+        
+        foreach ($list as $col) {
+            if (trim($model->$col) == '' ||
+                ! array_key_exists($model->$col, $model->_table_cols)) {
+                // doesn't exist in the table
+                $model->$col = null;
+            }
+        }
+        
+        // post-set the inheritance model value
+        if (! $model->_inherit_col) {
+            $model->_inherit_model = null;
+            $model->_inherit_base = null;
+        }
+        
+        // set up the fetch-cols list
+        settype($model->_fetch_cols, 'array');
+        if (! $model->_fetch_cols) {
+            $model->_fetch_cols = array_keys($model->_table_cols);
+        }
+        
+        // simply force to array
+        settype($model->_serialize_cols, 'array');
+        
+        // the "sequence" columns.  make sure they point to a sequence name.
+        // e.g., string 'col' becomes 'col' => 'col'.
+        $tmp = array();
+        foreach ((array) $model->_sequence_cols as $key => $val) {
+            if (is_int($key)) {
+                $tmp[$val] = $val;
+            } else {
+                $tmp[$key] = $val;
+            }
+        }
+        $model->_sequence_cols = $tmp;
+        
+        // make sure we have a hint to foreign models as to what colname
+        // to use when referring to this model
+        if (empty($model->_foreign_col)) {
+            
+            if (! $model->_inherit_model) {
+                // not inherited
+                $model->_foreign_col = strtolower($model->_model_name)
+                                     . '_' . $model->_primary_col;
+            } else {
+                // inherited, can't just use the model name as a column name.
+                // need to find base model foreign_col value.
+                $base = $this->get($model->_inherit_base);
+                $model->_foreign_col = $base->_foreign_col;
+            }
+        }
+    }
+    
+    /**
+     * 
+     * Loads the array-name for user input to this model.
+     * 
+     * @param StdClass $model The model property catalog.
+     * 
+     * @return void
+     * 
+     */
+    protected function _fixModelName($model)
+    {
+        if (! $model->_model_name) {
+            if ($model->_inherit_model) {
+                $model->_model_name = $model->_inherit_model;
+            } else {
+                // get the part after the last Model_ portion
+                $pos = strpos($model->_class, 'Model_');
+                if ($pos) {
+                    $model->_model_name = substr($model->_class, $pos+6);
+                } else {
+                    $model->_model_name = $model->_class;
+                }
+            }
+            
+            // convert FooBar to foo_bar
+            $model->_model_name = strtolower(
+                preg_replace('/([a-z])([A-Z])/', '$1_$2', $model->_model_name)
+            );
+        }
+    }
+    
+    protected function _fixOrder($model)
+    {
+        if (! $model->_order) {
+            $model->_order = $model->_model_name . '.' . $model->_primary_col;
+        }
+    }
+    
+    /**
+     * 
+     * Loads the baseline data filters for each column.
+     * 
+     * @param StdClass $model The model property catalog.
+     * 
+     * @return void
+     * 
+     */
+    protected function _fixFilters($model)
+    {
+        // make sure we have a filter class
+        if (empty($model->_filter_class)) {
+            $class = $model->_stack->load('Filter', false);
+            if (! $class) {
+                $class = 'Solar_Sql_Model_Filter';
+            }
+            $model->_filter_class = $class;
+        }
+        
+        // make sure filters are an array
+        settype($model->_filters, 'array');
+        
+        // make sure that strings are converted
+        // to arrays so that _applyFilters() works properly.
+        foreach ($model->_filters as $col => $list) {
+            foreach ($list as $key => $val) {
+                if (is_string($val)) {
+                    $model->_filters[$col][$key] = array($val);
+                }
+            }
+        }
+        
+        // low and high range values for integer filters
+        $range = array(
+            'smallint' => array(pow(-2, 15), pow(+2, 15) - 1),
+            'int'      => array(pow(-2, 31), pow(+2, 31) - 1),
+            'bigint'   => array(pow(-2, 63), pow(+2, 63) - 1)
+        );
+        
+        // add final fallback filters based on data type
+        foreach ($model->_table_cols as $col => $info) {
+            
+            
+            $type = $info['type'];
+            switch ($type) {
+            case 'bool':
+                $model->_filters[$col][] = array('validateBool');
+                $model->_filters[$col][] = array('sanitizeBool');
+                break;
+            
+            case 'char':
+            case 'varchar':
+                // only add filters if not serializing
+                if (! in_array($col, $model->_serialize_cols)) {
+                    $model->_filters[$col][] = array('validateString');
+                    $model->_filters[$col][] = array('validateMaxLength',
+                        $info['size']);
+                    $model->_filters[$col][] = array('sanitizeString');
+                }
+                break;
+            
+            case 'smallint':
+            case 'int':
+            case 'bigint':
+                $model->_filters[$col][] = array('validateInt');
+                $model->_filters[$col][] = array('validateRange',
+                    $range[$type][0], $range[$type][1]);
+                $model->_filters[$col][] = array('sanitizeInt');
+                break;
+            
+            case 'numeric':
+                $model->_filters[$col][] = array('validateNumeric');
+                $model->_filters[$col][] = array('validateSizeScope',
+                    $info['size'], $info['scope']);
+                $model->_filters[$col][] = array('sanitizeNumeric');
+                break;
+            
+            case 'float':
+                $model->_filters[$col][] = array('validateFloat');
+                $model->_filters[$col][] = array('sanitizeFloat');
+                break;
+            
+            case 'clob':
+                // no filters, clobs are pretty generic
+                break;
+            
+            case 'date':
+                $model->_filters[$col][] = array('validateIsoDate');
+                $model->_filters[$col][] = array('sanitizeIsoDate');
+                break;
+            
+            case 'time':
+                $model->_filters[$col][] = array('validateIsoTime');
+                $model->_filters[$col][] = array('sanitizeIsoTime');
+                break;
+            
+            case 'timestamp':
+                $model->_filters[$col][] = array('validateIsoTimestamp');
+                $model->_filters[$col][] = array('sanitizeIsoTimestamp');
+                break;
+            }
+        }
+    }
+    
+    /**
+     * 
+     * Corrects the relationship definitions in $this->_related.
+     * 
+     * `name`
+     * : (string) The name for this relationship; becomes a property key in
+     *   the record, so it should not be allowed to conflict with native
+     *   column names.
+     * 
+     * `type`
+     * : (string) The association type: has_one, belongs_to, has_many,
+     *   shares_many.
+     * 
+     * `foreign_model`
+     * : (string) The class name of the foreign model. Default is the first
+     *   matching class for the relationship name, as loaded from the parent
+     *   class stack. Automatically honors single-table inheritance.
+     * 
+     * `foreign_table`
+     * : (string) The name of the table for the foreign model. Default is the
+     *   table specified by the foreign model.
+     * 
+     * `foreign_alias`
+     * : (string) Aliases the foreign table to this name. Default is the
+     *   relationship name.
+     * 
+     * `foreign_col`
+     * : (string) The name of the column to join with in the *foreign* table.
+     *   This forms one-half of the relationship.  Default is per association
+     *   type.
+     * 
+     * `foreign_inherit`
+     * : (string) If the foreign model has an inheritance type, this is a
+     *   condition suitable for WHERE and JOIN clauses to retrieve only
+     *   records of the proper model.
+     * 
+     * `native_col`
+     * : (string) The name of the column to join with in the *native* table.
+     *   This forms one-half of the relationship.  Default is per association
+     *   type.
+     * 
+     * `distinct`
+     * : (bool) Should the relationship be DISTINCT or not?  Default false.
+     * 
+     * `where`
+     * : (string|array) Additional WHERE clause to determine what record is
+     *   returned.  Default is no conditions.
+     * 
+     * `group`
+     * : (string|array) A GROUP clause to determine how foreign records are
+     *   grouped.  Default is none.
+     * 
+     * `having`
+     * : (string|array) A HAVING clause to determine a post-grouping
+     *   condition.  Default is none.
+     * 
+     * `order`
+     * : (string|array) An ORDER clause to determine how foreign records are
+     *   ordered. Default is the foreign model default order.
+     * 
+     * `paging`
+     * : (int) Retrieve this many records per page (0 for all).  Default is
+     *   the foreign model default paging value.
+     * 
+     * `fetch`
+     * : (string) Fetch one, all, assoc, value, etc.
+     * 
+     * `cols`
+     * : (array|string) Retrieve these columns from the foreign model.
+     *   Default is all columns; primary-key and inheritance columns are
+     *   added automatically.
+     * 
+     * <http://ar.rubyonrails.com/classes/ActiveRecord/Associations/ClassMethods.html>
+     * 
+     * There is a virtual element called `foreign_key` that automatically
+     * populates the `native_col` or `foreign_col` value for you, based on the
+     * association type.  This will be used **only** when `native_col` **and**
+     * `foreign_col` are not set.
+     * 
+     * There is a virtual element called `through_key` that automatically 
+     * populates the 'through_foreign_col' value for you.
+     * 
+     * @param StdClass $model The model property catalog.
+     * 
+     * @return void
+     * 
+     * @todo Add `through*` keys to the definition list above.
+     * 
+     * @todo Add 'native_table' and 'native_alias' values? Then *everything*
+     * is in the array, no need for $this->_whatever.
+     * 
+     */
+    protected function _fixRelated($model)
+    {
+        foreach ($model->_related as $name => $opts) {
+            
+            // is the relation name already a column name?
+            if (array_key_exists($name, $model->_table_cols)) {
+                throw $this->_exception(
+                    'ERR_RELATION_NAME_CONFLICT',
+                    array(
+                        'name'  => $name,
+                        'model' => $model,
+                    )
+                );
+            } else {
+                $opts['name'] = $name;
+            }
+            
+            // what type of association is this?
+            $type = $opts['type'];
+            
+            // make sure we have at least a base model name
+            if (empty($opts['foreign_model'])) {
+                $opts['foreign_model'] = $name;
+            }
+            
+            // can we load a related model class from the hierarchy stack?
+            $class = $model->_stack->load($opts['foreign_model'], false);
+            
+            // did we find it?
+            if (! $class) {
+                // look for a "parallel" class name, based on where the word
+                // "Model" is in the current class name. this lets you pull
+                // model classes from the same level, not from the inheritance
+                // stack.
+                $pos = strrpos($model->_class, 'Model_');
+                if ($pos !== false) {
+                    $pos += 6; // "Model_"
+                    $tmp = substr($model->_class, 0, $pos) . ucfirst($opts['foreign_model']);
+                    try {
+                        Solar::loadClass($tmp);
+                        // if no exception, $class gets set
+                        $class = $tmp;
+                    } catch (Exception $e) {
+                        // do nothing, 
+                    }
+                }
+            }
+            
+            // not in the hierarchy, and no parallel class name. look for the
+            // model class literally. this will throw an exception if the
+            // class cannot be found anywhere.
+            if (! $class) {
+                try {
+                    Solar::loadClass($opts['foreign_model']);
+                    // if no exception, $class gets set
+                    $class = $opts['foreign_model'];
+                } catch (Solar_Exception $e) {
+                    throw $this->_exception('ERR_LOAD_FOREIGN_MODEL', array(
+                        'native_model' => $model->_class,
+                        'related_name' => $name,
+                        'related_opts' => $opts,
+                    ));
+                }
+            }
+            
+            // finally we have a class name, keep it as the foreign model class
+            $opts['foreign_model'] = $class;
+            
+            // catalog data for the foreign model
+            $foreign = $this->get($class);
+            
+            // the foreign table name
+            if (empty($opts['foreign_table'])) {
+                $opts['foreign_table'] = $foreign->_table_name;
+            }
+            
+            // the foreign table alias
+            if (empty($opts['foreign_alias'])) {
+                $opts['foreign_alias'] = $name;
+            }
+            
+            // custom WHERE clauses
+            if (empty($opts['where'])) {
+                $opts['where'] = array();
+            }
+            settype($opts['where'], 'array');
+            
+            // the list of foreign table cols to retrieve
+            if (empty($opts['cols'])) {
+                $opts['cols'] = $foreign->_fetch_cols;
+            } elseif (is_string($opts['cols'])) {
+                $opts['cols'] = explode(',', $opts['cols']);
+            } else {
+                settype($opts['cols'], 'array');
+            }
+            
+            // make sure we always retrieve the foreign primary key value,
+            // if there is one.
+            $col = $foreign->_primary_col;
+            if ($col && ! in_array($col, $opts['cols'])) {
+                $opts['cols'][] = $col;
+            }
+            
+            // if inheritance is turned on for the foreign mode,
+            // make sure we always retrieve the foreign inheritance value.
+            $col = $foreign->_inherit_col;
+            if ($col && ! in_array($col, $opts['cols'])) {
+                $opts['cols'][] = $col;
+            }
+            
+            // if inheritance is turned on, force the foreign_inherit
+            // column and value
+            if ($foreign->_inherit_col && $foreign->_inherit_model) {
+                $opts['foreign_inherit_col'] = $foreign->_inherit_col;
+                $opts['foreign_inherit_val'] = $foreign->_inherit_model;
+            } else {
+                $opts['foreign_inherit_col'] = null;
+                $opts['foreign_inherit_val'] = null;
+            }
+            
+            // paging from the foreign model
+            if (empty($opts['paging'])) {
+                $opts['paging'] = $foreign->_paging;
+            }
+            
+            // default page number; use "all pages" if not specified.
+            // note that we don't use empty() here, because we want to allow
+            // for "page = 0".
+            if (! array_key_exists('page', $opts)) {
+                $opts['page'] = 0;
+            }
+            
+            // distinct?
+            if (empty($opts['distinct'])) {
+                $opts['distinct'] = null;
+            }
+            
+            // group?
+            if (empty($opts['group'])) {
+                $opts['group'] = null;
+            }
+            
+            // having?
+            if (empty($opts['having'])) {
+                $opts['having'] = null;
+            }
+            
+            // and now, the tricky part ;-)
+            switch ($opts['type']) {
+            case 'belongs_to':
+                $this->_fixRelatedBelongsTo($opts, $model, $foreign);
+                break;
+            case 'has_one':
+                $this->_fixRelatedHasOne($opts, $model, $foreign);
+                break;
+            case 'has_many':
+                // this will check for "has_many through" as well
+                $this->_fixRelatedHasMany($opts, $model, $foreign);
+                break;
+            }
+            
+            // custom ORDER clause
+            if (empty($opts['order'])) {
+                $opts['order'] = "{$opts['foreign_alias']}.{$foreign->_primary_col}";
+            }
+            settype($opts['order'], 'array');
+            
+            // retain the corrected values in a standard order
+            $model->_related[$name] = array(
+                'name'                => $name,
+                'type'                => $opts['type'],
+                'foreign_model'       => $opts['foreign_model'],
+                'foreign_table'       => $opts['foreign_table'],
+                'foreign_alias'       => $opts['foreign_alias'],
+                'foreign_col'         => $opts['foreign_col'],
+                'foreign_inherit_col' => $opts['foreign_inherit_col'],
+                'foreign_inherit_val' => $opts['foreign_inherit_val'],
+                'foreign_primary_col' => $foreign->_primary_col,
+                'native_table'        => $model->_table_name,
+                'native_alias'        => $model->_model_name,
+                'native_col'          => $opts['native_col'],
+                'through'             => $opts['through'],
+                'through_table'       => $opts['through_table'],
+                'through_alias'       => $opts['through_alias'],
+                'through_native_col'  => $opts['through_native_col'],
+                'through_foreign_col' => $opts['through_foreign_col'],
+                'distinct'            => $opts['distinct'],
+                'where'               => $opts['where'],
+                'group'               => $opts['group'],
+                'having'              => $opts['having'],
+                'order'               => $opts['order'],
+                'paging'              => (int) $opts['paging'],
+                'page'                => (int) $opts['page'],
+                'cols'                => $opts['cols'],
+            );
+        }
+    }
+    
+    /**
+     * 
+     * A support method for _fixRelated() to handle belongs-to relationships.
+     * 
+     * @param array &$opts The relationship options; these are modified in-
+     * place.
+     * 
+     * @param StdClass $model The catalog entry for the native model (i.e.,
+     * this model).
+     * 
+     * @param StdClass $foreign The catalog entry for the foreign model.
+     * 
+     * @return void
+     * 
+     */
+    protected function _fixRelatedBelongsTo(&$opts, $model, $foreign)
+    {
+        // a little magic
+        if (empty($opts['foreign_col']) && empty($opts['native_col']) &&
+            ! empty($opts['foreign_key'])) {
+            // foreign key is stored in the native model
+            $opts['native_col'] = $opts['foreign_key'];
+        }
+        
+        // the foreign column
+        if (empty($opts['foreign_col'])) {
+            // named by foreign primary key (e.g., foreign.id)
+            $opts['foreign_col'] = $foreign->_primary_col;
+        }
+        
+        // the native column
+        if (empty($opts['native_col'])) {
+            // named by foreign table name and foreign primary key
+            // (e.g., native.foreign_id)
+            $opts['native_col'] = $foreign->_foreign_col;
+        }
+        
+        // not "through" anything
+        $this->_fixRelatedNotThrough($opts);
+    }
+    
+    /**
+     * 
+     * A support method for _fixRelated() to handle has-one relationships.
+     * 
+     * @param array &$opts The relationship options; these are modified in-
+     * place.
+     * 
+     * @param StdClass $model The catalog entry for the native model (i.e.,
+     * this model).
+     * 
+     * @param StdClass $foreign The catalog entry for the foreign model.
+     * 
+     * @return void
+     * 
+     */
+    protected function _fixRelatedHasOne(&$opts, $model, $foreign)
+    {
+        // a little magic
+        if (empty($opts['foreign_col']) && empty($opts['native_col']) &&
+            ! empty($opts['foreign_key'])) {
+            // foreign key is stored in the foreign model
+            $opts['foreign_col'] = $opts['foreign_key'];
+        }
+        
+        // the foreign column
+        if (empty($opts['foreign_col'])) {
+            // named by native table and native primary (e.g.,
+            // foreign.native_id)
+            $opts['foreign_col'] = $model->_foreign_col;
+        }
+        
+        // the native column
+        if (empty($opts['native_col'])) {
+            // named by native primary key (e.g., native.id)
+            $opts['native_col'] = $model->_primary_col;
+        }
+        
+        // not "through" anything
+        $this->_fixRelatedNotThrough($opts);
+    }
+    
+    /**
+     * 
+     * A support method for _fixRelated() to handle has-many relationships.
+     * 
+     * @param array &$opts The relationship options; these are modified in-
+     * place.
+     * 
+     * @param StdClass $model The catalog entry for the native model (i.e.,
+     * this model).
+     * 
+     * @param StdClass $foreign The catalog entry for the foreign model.
+     * 
+     * @return void
+     * 
+     */
+    protected function _fixRelatedHasMany(&$opts, $model, $foreign)
+    {
+        // are we working through another relationship?
+        if (! empty($opts['through'])) {
+            // through another relationship, hand off to another method
+            return $this->_fixRelatedHasManyThrough($opts, $model, $foreign);
+        } else {
+            // not "through" anything, clear the "through" keys
+            $this->_fixRelatedNotThrough($opts);
+        }
+        
+        // a little magic
+        if (empty($opts['foreign_col']) && empty($opts['native_col']) &&
+            ! empty($opts['foreign_key'])) {
+            // foreign key is stored in the foreign model
+            $opts['foreign_col'] = $opts['foreign_key'];
+        }
+        
+        // the foreign column
+        if (empty($opts['foreign_col'])) {
+            // named by native table and native primary (e.g.,
+            // foreign.native_id)
+            $opts['foreign_col'] = $model->_foreign_col;
+        }
+        
+        // the native column
+        if (empty($opts['native_col'])) {
+            // named by native primary key (e.g., native.id)
+            $opts['native_col'] = $model->_primary_col;
+        }
+    }
+    
+    /**
+     * 
+     * A support method for _fixRelatedHasMany() to handle "through"
+     * relationships.
+     * 
+     * @param array &$opts The relationship options; these are modified in-
+     * place.
+     * 
+     * @param StdClass $model The catalog entry for the native model (i.e.,
+     * this model).
+     * 
+     * @param StdClass $foreign The catalog entry for the foreign model.
+     * 
+     * @return void
+     * 
+     */
+    protected function _fixRelatedHasManyThrough(&$opts, $model, $foreign)
+    {
+        // make sure the "through" relationship exists
+        if (empty($model->_related[$opts['through']])) {
+            throw $this->_exception('ERR_THROUGH_NOT_EXIST', array(
+                'model'   => $model->_class,
+                'name'    => $opts['name'],
+                'through' => $opts['through'],
+            ));
+        }
+        
+        // a little magic
+        if (empty($opts['foreign_col']) && empty($opts['native_col']) &&
+            ! empty($opts['foreign_key'])) {
+            // foreign key is stored in the foreign model
+            $opts['foreign_col'] = $opts['foreign_key'];
+        }
+        
+        // the foreign column
+        if (empty($opts['foreign_col'])) {
+            // named by foreign primary key (e.g., foreign.id)
+            $opts['foreign_col'] = $foreign->_primary_col;
+        }
+        
+        // the native column
+        if (empty($opts['native_col'])) {
+            // named by native primary key (e.g., native.id)
+            $opts['native_col'] = $model->_primary_col;
+        }
+        
+        // convenient reference to the "through" relationship
+        $through = $model->_related[$opts['through']];
+        
+        // get the through-table
+        if (empty($opts['through_table'])) {
+            $opts['through_table'] = $through['foreign_table'];
+        }
+        
+        // get the through-alias
+        if (empty($opts['through_alias'])) {
+            $opts['through_alias'] = $through['foreign_alias'];
+        }
+        
+        // a little magic
+        if (empty($opts['through_native_col']) &&
+            empty($opts['through_foreign_col']) &&
+            ! empty($opts['through_key'])) {
+            // use this
+            $opts['through_foreign_col'] = $opts['through_key'];
+        }
+        
+        // what's the native model key in the through table?
+        if (empty($opts['through_native_col'])) {
+            $opts['through_native_col'] = $through['foreign_col'];
+        }
+        
+        // what's the foreign model key in the through table?
+        if (empty($opts['through_foreign_col'])) {
+            $opts['through_foreign_col'] = $foreign->_foreign_col;
+        }
+    }
+    
+    /**
+     * 
+     * A support method for _fixRelatedHasMany() to set the "through" options
+     * to blanks.
+     * 
+     * @param array &$opts The relationship options; these are modified in-
+     * place.
+     * 
+     * @param StdClass $model The catalog entry for the native model (i.e.,
+     * this model).
+     * 
+     * @param StdClass $foreign The catalog entry for the foreign model.
+     * 
+     * @return void
+     * 
+     */
+    protected function _fixRelatedNotThrough(&$opts)
+    {
+        $opts['through']                = null;
+        $opts['through_table']          = null;
+        $opts['through_alias']          = null;
+        $opts['through_native_col']     = null;
+        $opts['through_foreign_col']    = null;
+    }
+    
+    /**
+     * 
+     * Fixes $this->_index listings.
+     * 
+     * @param StdClass $model The model property catalog.
+     * 
+     * @return void
+     * 
+     */
+    protected function _fixIndex($model)
+    {
+        // baseline index definition
+        $baseidx = array(
+            'name'    => null,
+            'type'    => 'normal',
+            'cols'    => null,
+        );
+        
+        // fix up each index to have a full set of info
+        foreach ($model->_index as $key => $val) {
+            
+            if (is_int($key) && is_string($val)) {
+                // array('col')
+                $info = array(
+                    'name' => $val,
+                    'type' => 'normal',
+                    'cols' => array($val),
+                );
+            } elseif (is_string($key) && is_string($val)) {
+                // array('col' => 'unique')
+                $info = array(
+                    'name' => $key,
+                    'type' => $val,
+                    'cols' => array($key),
+                );
+            } else {
+                // array('alt' => array('type' => 'normal', 'cols' => array(...)))
+                $info = array_merge($baseidx, (array) $val);
+                $info['name'] = (string) $key;
+                settype($info['cols'], 'array');
+            }
+            
+            $model->_index[$key] = $info;
+        }
+    }
+    
+    /**
+     * 
+     * Creates the table and indexes in the database using $model->_table_cols
+     * and $model->_index.
+     * 
+     * @param StdClass $model The model property catalog.
+     * 
+     * @return void
+     * 
+     */
+    protected function _createTableAndIndexes($model)
+    {
+        /**
+         * Create the table.
+         */
+        $this->_sql->createTable(
+            $model->_table_name,
+            $model->_table_cols
+        );
+        
+        /**
+         * Create the indexes.
+         */
+        foreach ($model->_index as $name => $info) {
+            try {
+                // create this index
+                $this->_sql->createIndex(
+                    $model->_table_name,
+                    $info['name'],
+                    $info['type'] == 'unique',
+                    $info['cols']
+                );
+            } catch (Exception $e) {
+                // cancel the whole deal.
+                $this->_sql->dropTable($model->_table_name);
+                throw $e;
+            }
+        }
+    }
+}

Deleted: trunk/Solar/Sql/Model/Collection.php
===================================================================
--- branches/orm/Solar/Sql/Model/Collection.php	2007-09-29 02:40:28 UTC (rev 2801)
+++ trunk/Solar/Sql/Model/Collection.php	2007-10-06 16:20:43 UTC (rev 2835)
@@ -1,253 +0,0 @@
-<?php
-/**
- * 
- * Represents a collection of Solar_Sql_Model_Record objects.
- * 
- * @category Solar
- * 
- * @package Solar_Sql_Model
- * 
- * @author Paul M. Jones <pmjones at solarphp.com>
- * 
- * @license http://opensource.org/licenses/bsd-license.php BSD
- * 
- * @version $Id: Collection.php 918 2007-07-24 00:30:40Z moraes $
- * 
- */
-
-/**
- * 
- * Represents a collection of Solar_Sql_Model_Record objects.
- * 
- * @category Solar
- * 
- * @package Solar_Sql_Model
- * 
- * @todo Implement an internal unit-of-work status registry so that we can 
- * handle mass insert/delete without hitting the database unnecessarily.
- * 
- */
-class Solar_Sql_Model_Collection extends Solar_Struct
-{
-    /**
-     * 
-     * The "parent" model for this record.
-     * 
-     * @var Solar_Sql_Model
-     * 
-     */
-    protected $_model;
-    
-    /**
-     * 
-     * Data for related objects.
-     * 
-     */
-    protected $_related = array();
-    
-    /**
-     * 
-     * Injects the model from which the data originates.
-     * 
-     * Also loads accessor method lists for column and related properties.
-     * 
-     * These let users override how the column properties are accessed
-     * through the magic __get, __set, etc. methods.
-     * 
-     * @param Solar_Sql_Model $model The origin model object.
-     * 
-     * @return void
-     * 
-     */
-    public function setModel(Solar_Sql_Model $model)
-    {
-        $this->_model = $model;
-    }
-    
-    /**
-     * 
-     * Returns the model from which the data originates.
-     * 
-     * @return Solar_Sql_Model $model The origin model object.
-     * 
-     */
-    public function getModel()
-    {
-        return $this->_model;
-    }
-    
-    /**
-     * 
-     * Loads *related* data for the collection.
-     * 
-     * Applies particularly to has-many eager loading.
-     * 
-     * This keeps hold of the related data, which will be loaded into the
-     * record by offsetGet().
-     * 
-     * @param string $name The relationship name.
-     * 
-     * @param $data The related data.
-     * 
-     * @return void
-     * 
-     * @see offsetGet()
-     * 
-     */
-    public function loadRelated($name, $data)
-    {
-        $related = $this->_model->getRelated($name);
-        if ($related->type == 'has_many') {
-            foreach ($data as $item) {
-                $id = array_shift($item);
-                $this->_related[$name][$id][] = $item;
-            }
-        } else {
-            $id = array_shift($data);
-            $this->_related[$name][$id] = $data;
-        }
-    }
-    
-    public function toArray()
-    {
-        $data = array();
-        $clone = clone($this);
-        foreach ($clone as $key => $record) {
-            $data[$key] = $record->toArray();
-        }
-        return $data;
-    }
-    
-    /**
-     * 
-     * Saves all the records from this collection to the database, inserting
-     * or updating as needed.
-     * 
-     * @return void
-     * 
-     */
-    public function save()
-    {
-        // pre-logic
-        $this->_preSave();
-        
-        // save, instantiating each record
-        foreach ($this as $record) {
-            $status = $record->getStatus();
-            if ($status != 'deleted') {
-                $record->save();
-            }
-        }
-        
-        // post-logic
-        $this->_postSave();
-    }
-    
-    public function _preSave()
-    {
-    }
-    
-    public function _postSave()
-    {
-    }
-    
-    // deletes each record in the collection
-    public function delete()
-    {
-        $this->_preDelete();
-        foreach ($this as $record) {
-            $status = $record->getStatus();
-            if ($status != 'deleted') {
-                $record->delete();
-            }
-        }
-        $this->_postDelete();
-    }
-    
-    public function _preDelete()
-    {
-    }
-    
-    protected function _postDelete()
-    {
-    }
-    
-    // -----------------------------------------------------------------
-    //
-    // Iterator
-    //
-    // -----------------------------------------------------------------
-    
-    /**
-     * 
-     * Iterator: returns the current record from the collection.
-     * 
-     * @return Solar_Sql_Model_Record A model with a focus on one record.
-     * 
-     */
-    public function current()
-    {
-        return $this->offsetGet($this->key());
-    }
-    
-    // -----------------------------------------------------------------
-    //
-    // ArrayAccess
-    //
-    // -----------------------------------------------------------------
-    
-    public function __get($key)
-    {
-        // convert array to record object
-        // honors single-table inheritance
-        if (is_array($this->_data[$key])) {
-            
-            // convert the data array to an object.
-            // get the main data to load to the record.
-            $load = $this->_data[$key];
-            
-            // add related data to load data
-            $primary = $load[$this->_model->primary_col];
-            foreach ($this->_related as $name => $data) {
-                if (! empty($data[$primary])) {
-                    // add the data 
-                    $load[$name] = $data[$primary];
-                    // save some memory
-                    unset($data[$primary]);
-                }
-            }
-            
-            // done
-            $this->_data[$key] = $this->getModel()->newRecord($load);
-        }
-        
-        // return the record
-        return $this->_data[$key];
-    }
-    
-    /**
-     * 
-     * ArrayAccess: set a key value.
-     * 
-     * @param string $key The requested key.
-     * 
-     * @param string $val The value to set it to.
-     * 
-     * @return void
-     * 
-     * @todo If $key is null, that is [] ("append") notation.  Only let it
-     * work for collections?
-     * 
-     */
-    public function offsetSet($key, $val)
-    {
-        if ($key === null) {
-            $key = $this->count();
-            if (! $key) {
-                $key = 0;
-            }
-        }
-        
-        return $this->__set($key, $val);
-    }
-}
\ No newline at end of file

Copied: trunk/Solar/Sql/Model/Collection.php (from rev 2801, branches/orm/Solar/Sql/Model/Collection.php)
===================================================================
--- trunk/Solar/Sql/Model/Collection.php	                        (rev 0)
+++ trunk/Solar/Sql/Model/Collection.php	2007-10-06 16:20:43 UTC (rev 2835)
@@ -0,0 +1,253 @@
+<?php
+/**
+ * 
+ * Represents a collection of Solar_Sql_Model_Record objects.
+ * 
+ * @category Solar
+ * 
+ * @package Solar_Sql_Model
+ * 
+ * @author Paul M. Jones <pmjones at solarphp.com>
+ * 
+ * @license http://opensource.org/licenses/bsd-license.php BSD
+ * 
+ * @version $Id: Collection.php 918 2007-07-24 00:30:40Z moraes $
+ * 
+ */
+
+/**
+ * 
+ * Represents a collection of Solar_Sql_Model_Record objects.
+ * 
+ * @category Solar
+ * 
+ * @package Solar_Sql_Model
+ * 
+ * @todo Implement an internal unit-of-work status registry so that we can 
+ * handle mass insert/delete without hitting the database unnecessarily.
+ * 
+ */
+class Solar_Sql_Model_Collection extends Solar_Struct
+{
+    /**
+     * 
+     * The "parent" model for this record.
+     * 
+     * @var Solar_Sql_Model
+     * 
+     */
+    protected $_model;
+    
+    /**
+     * 
+     * Data for related objects.
+     * 
+     */
+    protected $_related = array();
+    
+    /**
+     * 
+     * Injects the model from which the data originates.
+     * 
+     * Also loads accessor method lists for column and related properties.
+     * 
+     * These let users override how the column properties are accessed
+     * through the magic __get, __set, etc. methods.
+     * 
+     * @param Solar_Sql_Model $model The origin model object.
+     * 
+     * @return void
+     * 
+     */
+    public function setModel(Solar_Sql_Model $model)
+    {
+        $this->_model = $model;
+    }
+    
+    /**
+     * 
+     * Returns the model from which the data originates.
+     * 
+     * @return Solar_Sql_Model $model The origin model object.
+     * 
+     */
+    public function getModel()
+    {
+        return $this->_model;
+    }
+    
+    /**
+     * 
+     * Loads *related* data for the collection.
+     * 
+     * Applies particularly to has-many eager loading.
+     * 
+     * This keeps hold of the related data, which will be loaded into the
+     * record by offsetGet().
+     * 
+     * @param string $name The relationship name.
+     * 
+     * @param $data The related data.
+     * 
+     * @return void
+     * 
+     * @see offsetGet()
+     * 
+     */
+    public function loadRelated($name, $data)
+    {
+        $related = $this->_model->getRelated($name);
+        if ($related->type == 'has_many') {
+            foreach ($data as $item) {
+                $id = array_shift($item);
+                $this->_related[$name][$id][] = $item;
+            }
+        } else {
+            $id = array_shift($data);
+            $this->_related[$name][$id] = $data;
+        }
+    }
+    
+    public function toArray()
+    {
+        $data = array();
+        $clone = clone($this);
+        foreach ($clone as $key => $record) {
+            $data[$key] = $record->toArray();
+        }
+        return $data;
+    }
+    
+    /**
+     * 
+     * Saves all the records from this collection to the database, inserting
+     * or updating as needed.
+     * 
+     * @return void
+     * 
+     */
+    public function save()
+    {
+        // pre-logic
+        $this->_preSave();
+        
+        // save, instantiating each record
+        foreach ($this as $record) {
+            $status = $record->getStatus();
+            if ($status != 'deleted') {
+                $record->save();
+            }
+        }
+        
+        // post-logic
+        $this->_postSave();
+    }
+    
+    public function _preSave()
+    {
+    }
+    
+    public function _postSave()
+    {
+    }
+    
+    // deletes each record in the collection
+    public function delete()
+    {
+        $this->_preDelete();
+        foreach ($this as $record) {
+            $status = $record->getStatus();
+            if ($status != 'deleted') {
+                $record->delete();
+            }
+        }
+        $this->_postDelete();
+    }
+    
+    public function _preDelete()
+    {
+    }
+    
+    protected function _postDelete()
+    {
+    }
+    
+    // -----------------------------------------------------------------
+    //
+    // Iterator
+    //
+    // -----------------------------------------------------------------
+    
+    /**
+     * 
+     * Iterator: returns the current record from the collection.
+     * 
+     * @return Solar_Sql_Model_Record A model with a focus on one record.
+     * 
+     */
+    public function current()
+    {
+        return $this->offsetGet($this->key());
+    }
+    
+    // -----------------------------------------------------------------
+    //
+    // ArrayAccess
+    //
+    // -----------------------------------------------------------------
+    
+    public function __get($key)
+    {
+        // convert array to record object
+        // honors single-table inheritance
+        if (is_array($this->_data[$key])) {
+            
+            // convert the data array to an object.
+            // get the main data to load to the record.
+            $load = $this->_data[$key];
+            
+            // add related data to load data
+            $primary = $load[$this->_model->primary_col];
+            foreach ($this->_related as $name => $data) {
+                if (! empty($data[$primary])) {
+                    // add the data 
+                    $load[$name] = $data[$primary];
+                    // save some memory
+                    unset($data[$primary]);
+                }
+            }
+            
+            // done
+            $this->_data[$key] = $this->getModel()->newRecord($load);
+        }
+        
+        // return the record
+        return $this->_data[$key];
+    }
+    
+    /**
+     * 
+     * ArrayAccess: set a key value.
+     * 
+     * @param string $key The requested key.
+     * 
+     * @param string $val The value to set it to.
+     * 
+     * @return void
+     * 
+     * @todo If $key is null, that is [] ("append") notation.  Only let it
+     * work for collections?
+     * 
+     */
+    public function offsetSet($key, $val)
+    {
+        if ($key === null) {
+            $key = $this->count();
+            if (! $key) {
+                $key = 0;
+            }
+        }
+        
+        return $this->__set($key, $val);
+    }
+}
\ No newline at end of file

Deleted: trunk/Solar/Sql/Model/Exception.php
===================================================================
--- branches/orm/Solar/Sql/Model/Exception.php	2007-09-29 02:40:28 UTC (rev 2801)
+++ trunk/Solar/Sql/Model/Exception.php	2007-10-06 16:20:43 UTC (rev 2835)
@@ -1,2 +0,0 @@
-<?php
-class Solar_Sql_Model_Exception extends Solar_Sql_Exception {}
\ No newline at end of file

Copied: trunk/Solar/Sql/Model/Exception.php (from rev 2801, branches/orm/Solar/Sql/Model/Exception.php)
===================================================================
--- trunk/Solar/Sql/Model/Exception.php	                        (rev 0)
+++ trunk/Solar/Sql/Model/Exception.php	2007-10-06 16:20:43 UTC (rev 2835)
@@ -0,0 +1,2 @@
+<?php
+class Solar_Sql_Model_Exception extends Solar_Sql_Exception {}
\ No newline at end of file

Copied: trunk/Solar/Sql/Model/Filter (from rev 2801, branches/orm/Solar/Sql/Model/Filter)

Deleted: trunk/Solar/Sql/Model/Filter/ValidateConfirm.php
===================================================================
--- branches/orm/Solar/Sql/Model/Filter/ValidateConfirm.php	2007-09-29 02:40:28 UTC (rev 2801)
+++ trunk/Solar/Sql/Model/Filter/ValidateConfirm.php	2007-10-06 16:20:43 UTC (rev 2835)
@@ -1,38 +0,0 @@
-<?php
-class Solar_Sql_Model_Filter_ValidateConfirm extends Solar_Filter_Abstract {
-    
-    /**
-     * 
-     * Validates that the "confirmation" value is the same as the "real"
-     * value being confirmed.
-     * 
-     * Useful for checking that the user entered the same password twice, or
-     * the same email twice, etc.
-     * 
-     * @param mixed $value The value to validate.
-     * 
-     * @param string $confirm_key Check against the value of this element in
-     * $this->_data.  If $this->_data[$confirm_key] does not exist, the
-     * validation will *pass*.  When empty, defaults to the current data
-     * col being processed, with suffix '_confirm'.
-     * 
-     * @return bool True if the values are the same or if the $confirm_key
-     * is not in the data being processed. False if the values are not the
-     * same.
-     * 
-     */
-    public function validateConfirm($value, $confirm_key = null)
-    {
-        if (! $confirm_key) {
-            $confirm_key = $this->_filter->getDataKey() . '_confirm';
-        }
-        
-        $confirm_val = $this->_filter->getData($confirm_key);
-        
-        if ($confirm_val === null) {
-            return true;
-        } else {
-            return ($value == $confirm_val);
-        }
-    }
-}

Copied: trunk/Solar/Sql/Model/Filter/ValidateConfirm.php (from rev 2801, branches/orm/Solar/Sql/Model/Filter/ValidateConfirm.php)
===================================================================
--- trunk/Solar/Sql/Model/Filter/ValidateConfirm.php	                        (rev 0)
+++ trunk/Solar/Sql/Model/Filter/ValidateConfirm.php	2007-10-06 16:20:43 UTC (rev 2835)
@@ -0,0 +1,38 @@
+<?php
+class Solar_Sql_Model_Filter_ValidateConfirm extends Solar_Filter_Abstract {
+    
+    /**
+     * 
+     * Validates that the "confirmation" value is the same as the "real"
+     * value being confirmed.
+     * 
+     * Useful for checking that the user entered the same password twice, or
+     * the same email twice, etc.
+     * 
+     * @param mixed $value The value to validate.
+     * 
+     * @param string $confirm_key Check against the value of this element in
+     * $this->_data.  If $this->_data[$confirm_key] does not exist, the
+     * validation will *pass*.  When empty, defaults to the current data
+     * col being processed, with suffix '_confirm'.
+     * 
+     * @return bool True if the values are the same or if the $confirm_key
+     * is not in the data being processed. False if the values are not the
+     * same.
+     * 
+     */
+    public function validateConfirm($value, $confirm_key = null)
+    {
+        if (! $confirm_key) {
+            $confirm_key = $this->_filter->getDataKey() . '_confirm';
+        }
+        
+        $confirm_val = $this->_filter->getData($confirm_key);
+        
+        if ($confirm_val === null) {
+            return true;
+        } else {
+            return ($value == $confirm_val);
+        }
+    }
+}

Deleted: trunk/Solar/Sql/Model/Filter/ValidateUnique.php
===================================================================
--- branches/orm/Solar/Sql/Model/Filter/ValidateUnique.php	2007-09-29 02:40:28 UTC (rev 2801)
+++ trunk/Solar/Sql/Model/Filter/ValidateUnique.php	2007-10-06 16:20:43 UTC (rev 2835)
@@ -1,60 +0,0 @@
-<?php
-class Solar_Sql_Model_Filter_ValidateUnique extends Solar_Filter_Abstract {
-    
-    /**
-     * 
-     * Validates that a value for the current data key is unique among all
-     * model records of its inheritance type.
-     * 
-     * This will exclude any record having the same primary-key value as the
-     * current record.
-     * 
-     * {{code: php
-     *     $where = array(
-     *         'id != :id', // or 'id IS NOT NULL' if the ID is null
-     *     );
-     * }}
-     * 
-     * @param mixed $value The value to validate.
-     * 
-     * @param mixed $where Additional "WHERE" conditions to exclude records
-     * from the uniqueness check.
-     * 
-     * @return bool True if unique, false if not.
-     * 
-     */
-    public function validateUnique($value, $where = null)
-    {
-        // make sure the $where is an array
-        settype($where, 'array');
-        
-        // get the record (data) model
-        $model = $this->_filter->getData()->getModel();
-        
-        // what is the primary-key column for the record model?
-        $primary = $model->primary_col;
-        
-        // exclude the current record by its primary key value
-        if ($this->_filter->getData($primary) === null) {
-            $where[] = "$primary IS NOT NULL";
-        } else {
-            $where[] = "$primary != :$primary";
-        }
-        
-        // base condition to check for uniqueness on the current column.
-        // added conditions already exist in the "where"
-        $key = $this->_filter->getDataKey();
-        $where[] = "$key = :$key";
-        
-        // see if we can fetch a row, with only the primary-key column to
-        // reduce resource usage.
-        $result = $model->fetchValue(array(
-            'where' => $where,
-            'cols'  => array($primary),
-            'bind'  => $this->_filter->getData()->toArray(),
-        ));
-        
-        // if empty, no result was returned, so the value is unique.
-        return empty($result);
-    }
-}
\ No newline at end of file

Copied: trunk/Solar/Sql/Model/Filter/ValidateUnique.php (from rev 2801, branches/orm/Solar/Sql/Model/Filter/ValidateUnique.php)
===================================================================
--- trunk/Solar/Sql/Model/Filter/ValidateUnique.php	                        (rev 0)
+++ trunk/Solar/Sql/Model/Filter/ValidateUnique.php	2007-10-06 16:20:43 UTC (rev 2835)
@@ -0,0 +1,60 @@
+<?php
+class Solar_Sql_Model_Filter_ValidateUnique extends Solar_Filter_Abstract {
+    
+    /**
+     * 
+     * Validates that a value for the current data key is unique among all
+     * model records of its inheritance type.
+     * 
+     * This will exclude any record having the same primary-key value as the
+     * current record.
+     * 
+     * {{code: php
+     *     $where = array(
+     *         'id != :id', // or 'id IS NOT NULL' if the ID is null
+     *     );
+     * }}
+     * 
+     * @param mixed $value The value to validate.
+     * 
+     * @param mixed $where Additional "WHERE" conditions to exclude records
+     * from the uniqueness check.
+     * 
+     * @return bool True if unique, false if not.
+     * 
+     */
+    public function validateUnique($value, $where = null)
+    {
+        // make sure the $where is an array
+        settype($where, 'array');
+        
+        // get the record (data) model
+        $model = $this->_filter->getData()->getModel();
+        
+        // what is the primary-key column for the record model?
+        $primary = $model->primary_col;
+        
+        // exclude the current record by its primary key value
+        if ($this->_filter->getData($primary) === null) {
+            $where[] = "$primary IS NOT NULL";
+        } else {
+            $where[] = "$primary != :$primary";
+        }
+        
+        // base condition to check for uniqueness on the current column.
+        // added conditions already exist in the "where"
+        $key = $this->_filter->getDataKey();
+        $where[] = "$key = :$key";
+        
+        // see if we can fetch a row, with only the primary-key column to
+        // reduce resource usage.
+        $result = $model->fetchValue(array(
+            'where' => $where,
+            'cols'  => array($primary),
+            'bind'  => $this->_filter->getData()->toArray(),
+        ));
+        
+        // if empty, no result was returned, so the value is unique.
+        return empty($result);
+    }
+}
\ No newline at end of file

Deleted: trunk/Solar/Sql/Model/Filter.php
===================================================================
--- branches/orm/Solar/Sql/Model/Filter.php	2007-09-29 02:40:28 UTC (rev 2801)
+++ trunk/Solar/Sql/Model/Filter.php	2007-10-06 16:20:43 UTC (rev 2835)
@@ -1,31 +0,0 @@
-<?php
-/**
- * 
- * Collection of external data filter methods for Solar_Sql_Model classes.
- * 
- * @category Solar
- * 
- * @package Solar_Sql_Model
- * 
- * @author Paul M. Jones <pmjones at solarphp.com>
- * 
- * @license http://opensource.org/licenses/bsd-license.php BSD
- * 
- * @version $Id: Filter.php 889 2007-07-21 16:09:34Z moraes $
- * 
- */
-
-/**
- * 
- * Collection of external data filter methods for Solar_Sql_Model classes.
- * 
- * This filter class is an unmodified extension of Solar_Filter; you may
- * extend it for your own models.
- * 
- * @category Solar
- * 
- * @package Solar_Sql_Model
- * 
- */
-class Solar_Sql_Model_Filter extends Solar_Filter {
-}
\ No newline at end of file

Copied: trunk/Solar/Sql/Model/Filter.php (from rev 2801, branches/orm/Solar/Sql/Model/Filter.php)
===================================================================
--- trunk/Solar/Sql/Model/Filter.php	                        (rev 0)
+++ trunk/Solar/Sql/Model/Filter.php	2007-10-06 16:20:43 UTC (rev 2835)
@@ -0,0 +1,31 @@
+<?php
+/**
+ * 
+ * Collection of external data filter methods for Solar_Sql_Model classes.
+ * 
+ * @category Solar
+ * 
+ * @package Solar_Sql_Model
+ * 
+ * @author Paul M. Jones <pmjones at solarphp.com>
+ * 
+ * @license http://opensource.org/licenses/bsd-license.php BSD
+ * 
+ * @version $Id: Filter.php 889 2007-07-21 16:09:34Z moraes $
+ * 
+ */
+
+/**
+ * 
+ * Collection of external data filter methods for Solar_Sql_Model classes.
+ * 
+ * This filter class is an unmodified extension of Solar_Filter; you may
+ * extend it for your own models.
+ * 
+ * @category Solar
+ * 
+ * @package Solar_Sql_Model
+ * 
+ */
+class Solar_Sql_Model_Filter extends Solar_Filter {
+}
\ No newline at end of file

Copied: trunk/Solar/Sql/Model/Record (from rev 2801, branches/orm/Solar/Sql/Model/Record)

Copied: trunk/Solar/Sql/Model/Record/Exception (from rev 2801, branches/orm/Solar/Sql/Model/Record/Exception)

Deleted: trunk/Solar/Sql/Model/Record/Exception/Invalid.php
===================================================================
--- branches/orm/Solar/Sql/Model/Record/Exception/Invalid.php	2007-09-29 02:40:28 UTC (rev 2801)
+++ trunk/Solar/Sql/Model/Record/Exception/Invalid.php	2007-10-06 16:20:43 UTC (rev 2835)
@@ -1,2 +0,0 @@
-<?php
-class Solar_Sql_Model_Record_Exception_Invalid extends Solar_Sql_Model_Record_Exception {}
\ No newline at end of file

Copied: trunk/Solar/Sql/Model/Record/Exception/Invalid.php (from rev 2801, branches/orm/Solar/Sql/Model/Record/Exception/Invalid.php)
===================================================================
--- trunk/Solar/Sql/Model/Record/Exception/Invalid.php	                        (rev 0)
+++ trunk/Solar/Sql/Model/Record/Exception/Invalid.php	2007-10-06 16:20:43 UTC (rev 2835)
@@ -0,0 +1,2 @@
+<?php
+class Solar_Sql_Model_Record_Exception_Invalid extends Solar_Sql_Model_Record_Exception {}
\ No newline at end of file

Deleted: trunk/Solar/Sql/Model/Record/Exception.php
===================================================================
--- branches/orm/Solar/Sql/Model/Record/Exception.php	2007-09-29 02:40:28 UTC (rev 2801)
+++ trunk/Solar/Sql/Model/Record/Exception.php	2007-10-06 16:20:43 UTC (rev 2835)
@@ -1,2 +0,0 @@
-<?php
-class Solar_Sql_Model_Record_Exception extends Solar_Sql_Model_Exception {}
\ No newline at end of file

Copied: trunk/Solar/Sql/Model/Record/Exception.php (from rev 2801, branches/orm/Solar/Sql/Model/Record/Exception.php)
===================================================================
--- trunk/Solar/Sql/Model/Record/Exception.php	                        (rev 0)
+++ trunk/Solar/Sql/Model/Record/Exception.php	2007-10-06 16:20:43 UTC (rev 2835)
@@ -0,0 +1,2 @@
+<?php
+class Solar_Sql_Model_Record_Exception extends Solar_Sql_Model_Exception {}
\ No newline at end of file

Deleted: trunk/Solar/Sql/Model/Record.php
===================================================================
--- branches/orm/Solar/Sql/Model/Record.php	2007-09-29 02:40:28 UTC (rev 2801)
+++ trunk/Solar/Sql/Model/Record.php	2007-10-06 16:20:43 UTC (rev 2835)
@@ -1,883 +0,0 @@
-<?php
-/**
- * 
- * Represents a single record returned from a Solar_Sql_Model.
- * 
- * @category Solar
- * 
- * @package Solar_Sql_Model
- * 
- * @author Paul M. Jones <pmjones at solarphp.com>
- * 
- * @license http://opensource.org/licenses/bsd-license.php BSD
- * 
- * @version $Id: Record.php 920 2007-07-24 04:53:21Z moraes $
- * 
- */
-
-/**
- * 
- * Represents a single record returned from a Solar_Sql_Model.
- * 
- * @category Solar
- * 
- * @package Solar_Sql_Model
- * 
- */
-class Solar_Sql_Model_Record extends Solar_Struct
-{
-    /**
-     * 
-     * The "parent" model for this record.
-     * 
-     * @var Solar_Sql_Model
-     * 
-     */
-    protected $_model;
-    
-    /**
-     * 
-     * The list of accessor methods for individual column properties.
-     * 
-     * For example, a method called __getFooBar() will be registered for
-     * ['get']['foo_bar'] => '__getFooBar'.
-     * 
-     * @var array
-     * 
-     */
-    protected $_access_methods = array();
-    
-    /**
-     * 
-     * Tracks the of the status of this record.
-     * 
-     * Status values are:
-     * 
-     * `clean`
-     * : The record is unmodified from the database.
-     * 
-     * `deleted`
-     * : This record has been deleted; load(), etc. will not work.
-     * 
-     * `dirty`
-     * : At least one record property has changed.
-     * 
-     * `inserted`
-     * : The record was inserted successfully.
-     * 
-     * `invalid`
-     * : Validation was attempted, with failure.
-     * 
-     * `new`
-     * : This is a new record and has not been saved to the database.
-     * 
-     * `updated`
-     * : The record was updated successfully.
-     * 
-     * @var bool
-     * 
-     */
-    protected $_status = 'clean';
-    
-    /**
-     * 
-     * Notes which values are not valid.
-     * 
-     * Keyed on property name => failure message.
-     * 
-     * @var array
-     * 
-     */
-    protected $_invalid = array();
-    
-    /**
-     * 
-     * Tracks which relationship pages are loaded.
-     * 
-     * Keys on the relationship name.
-     * 
-     * @param array
-     * 
-     */
-    protected $_related_page = array();
-    
-    /**
-     * 
-     * Tells whether or not __get() should lazy-load relateds.
-     * 
-     * We need this so that when saving, we don't load every related record.
-     * 
-     * @var bool
-     * 
-     */
-    protected $_lazy_load = true;
-    
-    /**
-     * 
-     * Magic getter for record properties; automatically calls __getColName()
-     * methods when they exist.
-     * 
-     * @param string $key The property name.
-     * 
-     * @return mixed The property value.
-     * 
-     */
-    public function __get($key)
-    {
-        // disallow if status is 'deleted'
-        $this->_checkDeleted();
-        
-        // do we need to load relationship data?
-        $load_related = $this->_lazy_load &&
-                        empty($this->_data[$key]) &&
-                        ! empty($this->_model->related[$key]);
-        
-        if ($load_related) {
-            // the key was for a relation that has no data yet.
-            // load the data.  don't return at this point, look
-            // for accessor methods later.
-            $this->_data[$key] = $this->_model->fetchRelatedObject(
-                $this,
-                $key,
-                $this->_related_page[$key]
-            );
-        }
-        
-        // if an accessor method exists, use it
-        if (! empty($this->_access_methods['get'][$key])) {
-            // use accessor method
-            $method = $this->_access_methods['get'][$key];
-            return $this->$method();
-        } else {
-            // no accessor method; use parent method.
-            return parent::__get($key);
-        }
-    }
-    
-    /**
-     * 
-     * Magic setter for record properties; automatically calls __setColName()
-     * methods when they exist.
-     * 
-     * @param string $key The property name.
-     * 
-     * @param mixed $val The value to set.
-     * 
-     * @return void
-     * 
-     */
-    public function __set($key, $val)
-    {
-        // disallow if status is 'deleted'
-        $this->_checkDeleted();
-        
-        // set to dirty only if not 'new'
-        if ($this->_status != 'new') {
-            $this->_status = 'dirty';
-        }
-        
-        // if an accessor method exists, use it
-        if (! empty($this->_access_methods['set'][$key])) {
-            // use accessor method
-            $method = $this->_access_methods['set'][$key];
-            $this->$method($val);
-        } else {
-            // no accessor method; use parent method
-            parent::__set($key, $val);
-        }
-    }
-    
-    /**
-     * 
-     * Sets a key in the data to null.
-     * 
-     * @param string $key The requested data key.
-     * 
-     * @return void
-     * 
-     */
-    public function __unset($key)
-    {
-        // disallow if status is 'deleted'
-        $this->_checkDeleted();
-        
-        // if an accessor method exists, use it
-        if (! empty($this->_access_methods['unset'][$key])) {
-            // use accessor method
-            $method = $this->_access_methods['unset'][$key];
-            $this->$method();
-        } else {
-            // no accessor method; use parent method
-            parent::__unset($key);
-        }
-    }
-    
-    /**
-     * 
-     * Checks if a data key is set.
-     * 
-     * @param string $key The requested data key.
-     * 
-     * @return void
-     * 
-     */
-    public function __isset($key)
-    {
-        // disallow if status is 'deleted'
-        $this->_checkDeleted();
-        
-        // if an accessor method exists, use it
-        if (! empty($this->_access_methods['isset'][$key])) {
-            // use accessor method
-            $method = $this->_access_methods['isset'][$key];
-            $result = $this->$method();
-        } else {
-            // no accessor method; use parent method
-            $result = parent::__isset($key);
-        }
-        
-        // done
-        return $result;
-    }
-    
-    /**
-     * 
-     * Loads the struct with data from an array or another struct.
-     * 
-     * Also unserializes columns per the "serialize_cols" model property.
-     * 
-     * This is a complete override from the parent load() method.
-     * 
-     * @param array|Solar_Struct $spec The data to load into the object.
-     * 
-     * @param array $cols Load only these columns.
-     * 
-     * @return void
-     * 
-     */
-    public function load($spec, $cols = null)
-    {
-        // force to array
-        if ($spec instanceof Solar_Struct) {
-            // we can do this because $spec is of the same class
-            $data = $spec->_data;
-        } elseif (is_array($spec)) {
-            $data = $spec;
-        } else {
-            $data = array();
-        }
-        
-        // remove columns not in the whitelist
-        if (! empty($cols)) {
-            $cols = (array) $cols;
-            foreach ($data as $key => $val) {
-                if (! in_array($key, $cols)) {
-                    unset($data[$key]);
-                }
-            }
-        }
-        
-        // pull out belongs_to/has_one related data.
-        foreach ($data as $key => $val) {
-            // if the key has double-underscores, it's an eager-load record.
-            if (strpos($key, '__') !== false) {
-                list($rel_name, $rel_key) = explode('__', $key);
-                $this->_data[$rel_name][$rel_key] = $val;
-                unset($data[$key]);
-            }
-        }
-        
-        // unserialize as needed, then add remaining "real" columns.
-        // it's not enough to just merge the data.  although slower, we need
-        // to loop so that __set() is honored.
-        $this->_model->unserializeCols($data);
-        
-        foreach ($data as $key => $val) {
-            $this->__set($key, $val);
-        }
-        
-        // load related data as records and collections
-        $list = array_keys($this->_model->related);
-        foreach ($list as $name) {
-            
-            $related = $this->_model->getRelated($name);
-            
-            // is this a "to-one" association with data already in place?
-            $type = $related->type;
-            if (($type == 'has_one' || $type == 'belongs_to') && ! empty($this->_data[$name])) {
-                    
-                // create a record object from the related model
-                $model = Solar::factory($related->foreign_class, array(
-                    'sql' => $this->_model->sql
-                ));
-                $this->_data[$name] = $model->newRecord($this->_data[$name]);
-                
-            } elseif ($type == 'has_many' && ! empty($this->_data[$name])) {
-                
-                // create a collection object from the related model
-                $model = Solar::factory($related->foreign_class, array(
-                    'sql' => $this->_model->sql
-                ));
-                $this->_data[$name] = $model->newCollection($this->_data[$name]);
-                
-            } else {
-                // set a placeholder for lazy-loading in __get()
-                $this->_data[$name] = null;
-            }
-            
-            // by default get all related records
-            $this->_related_page[$name] = 0;
-        }
-    }
-    
-    // -----------------------------------------------------------------
-    //
-    // Model
-    //
-    // -----------------------------------------------------------------
-    
-    /**
-     * 
-     * Injects the model from which the data originates.
-     * 
-     * Also loads accessor method lists for column and related properties.
-     * 
-     * These let users override how the column properties are accessed
-     * through the magic __get, __set, etc. methods.
-     * 
-     * @param Solar_Sql_Model $model The origin model object.
-     * 
-     * @return void
-     * 
-     */
-    public function setModel(Solar_Sql_Model $model)
-    {
-        $this->_model = $model;
-        
-        // get a list of table-column and related-data properties names
-        $vars = array_merge(
-            array_keys($this->_model->table_cols),
-            array_keys($this->_model->related),
-            (array) $this->_model->calculate_cols
-        );
-        
-        // look for access methods on each one
-        foreach ($vars as $var) {
-            $name = str_replace('_', ' ', $var);
-            $name = str_replace(' ', '', ucwords($name));
-            $list = array(
-                "get"   => "__get$name",
-                "set"   => "__set$name",
-                "isset" => "__isset$name",
-                "unset" => "__unset$name",
-            );
-            foreach ($list as $type => $method) {
-                if (method_exists($this, $method)) {
-                    $this->_access_methods[$type][$var] = $method;
-                }
-            }
-            
-            // put placeholders for each variable; these will be reset by
-            // the load() and/or __set() methods.  need to have this here
-            // because load() uses __set(), and primary keys will be ignored
-            // in that case, leaving the data key unset.  at the same time,
-            // we don't want to override values that are already present.
-            if (! isset($this->_data[$var])) {
-                $this->_data[$var] = null;
-            }
-        }
-    }
-    
-    /**
-     * 
-     * Returns the model from which the data originates.
-     * 
-     * @return Solar_Sql_Model $model The origin model object.
-     * 
-     */
-    public function getModel()
-    {
-        return $this->_model;
-    }
-    
-    // -----------------------------------------------------------------
-    //
-    // Record data
-    //
-    // -----------------------------------------------------------------
-    
-    /**
-     * 
-     * Converts the properties of this model Record or Collection to an array,
-     * including related models stored in properties.
-     * 
-     * @return array
-     * 
-     */
-    public function toArray()
-    {
-        $data = array();
-        $k