| <?php | 
|   | 
| /** Filtered table representation | 
|  * @method NotORM_Result and (mixed $condition, mixed $parameters = array()) Add AND condition | 
|  * @method NotORM_Result or (mixed $condition, mixed $parameters = array()) Add OR condition | 
|  * @property string  sequence | 
|  */ | 
| class NotORM_Result extends NotORM_Abstract implements Iterator, ArrayAccess, Countable, JsonSerializable{ | 
|     protected $single; | 
|     protected $select             = array(), $conditions = array(), $where = array(), $parameters = array(), $order = array(), $limit = null, $offset = null, $group = "", $having = "", $lock = null; | 
|     protected $union              = array(), $unionOrder = array(), $unionLimit = null, $unionOffset = null; | 
|     protected $data, $referencing = array(), $aggregation = array(), $accessed, $access, $keys = array(); | 
|   | 
|     public static $queryTimes = 0; | 
|   | 
|     /** Create table result | 
|      * | 
|      * @param string $table | 
|      * @param NotORM $notORM | 
|      * @param bool $single row | 
|      * | 
|      * @access protected must be public because it is called from NotORM | 
|      */ | 
|     function __construct($table, NotORM $notORM, $single = false){ | 
|         $this->table   = $table; | 
|         $this->notORM  = $notORM; | 
|         $this->single  = $single; | 
|         $this->primary = $notORM->structure->getPrimary($table); | 
|     } | 
|   | 
|     /** Save data to cache and empty result | 
|      */ | 
|     function __destruct(){ | 
|         if($this->notORM->cache && !$this->select && isset($this->rows)){ | 
|             $access = $this->access; | 
|             if(is_array($access)){ | 
|                 $access = array_filter($access); | 
|             } | 
|             $this->notORM->cache->save("$this->table;" . implode(",", $this->conditions), $access); | 
|         } | 
|         $this->rows = null; | 
|         unset($this->data); | 
|     } | 
|   | 
|     protected function limitString($limit, $offset = null){ | 
|         $return = ""; | 
|         if(isset($limit) && $this->notORM->driver != "oci" && $this->notORM->driver != "dblib" && $this->notORM->driver != "mssql" && $this->notORM->driver != "sqlsrv"){ | 
|             $return .= " LIMIT $limit"; | 
|             if($offset){ | 
|                 //$return .= " OFFSET $offset"; | 
|                 $return .= ",$offset";      //@dogstar 2014-10-24 | 
|             } | 
|         } | 
|         return $return; | 
|     } | 
|   | 
|     protected function removeExtraDots($expression){ | 
|         return preg_replace('~(?:\\b[a-z_][a-z0-9_.:]*[.:])?([a-z_][a-z0-9_]*)[.:]([a-z_*])~i', '\\1.\\2', $expression); // rewrite tab1.tab2.col | 
|     } | 
|   | 
|     protected function whereString(){ | 
|         $return = ""; | 
|         if($this->group){ | 
|             $return .= " GROUP BY $this->group"; | 
|         } | 
|         if($this->having){ | 
|             $return .= " HAVING $this->having"; | 
|         } | 
|         if($this->order){ | 
|             $return .= " ORDER BY " . implode(", ", $this->order); | 
|         } | 
|         $return = $this->removeExtraDots($return); | 
|   | 
|         $where = $this->where; | 
|         if(isset($this->limit) && $this->notORM->driver == "oci"){ | 
|             $where[] = ($where ? " AND " : "") . "(" . ($this->offset ? "rownum > $this->offset AND " | 
|                     : "") . "rownum <= " . ($this->limit + $this->offset) . ")"; //! rownum > doesn't work - requires subselect (see adminer/drivers/oracle.inc.php) | 
|         } | 
|         if($where){ | 
|             $return = " WHERE " . implode($where) . $return; | 
|         } | 
|   | 
|         $return .= $this->limitString($this->limit, $this->offset); | 
|         if(isset($this->lock)){ | 
|             $return .= ($this->lock ? " FOR UPDATE" : " LOCK IN SHARE MODE"); | 
|         } | 
|         return $return; | 
|     } | 
|   | 
|     /** | 
|      * @param      $limit | 
|      * | 
|      * @return string | 
|      */ | 
|     protected function topString( $limit){ | 
|         if(isset($limit) && ($this->notORM->driver == "dblib" || $this->notORM->driver == "mssql" || $this->notORM->driver == "sqlsrv")){ | 
|             return " TOP ($this->limit)"; //! offset is not supported | 
|         } | 
|         return ""; | 
|     } | 
|   | 
|     protected function createJoins($val){ | 
|         $return = array(); | 
|         preg_match_all('~\\b([a-z_][a-z0-9_.:]*[.:])[a-z_*]~i', $val, $matches); | 
|         foreach($matches[1] as $names){ | 
|             $parent = $this->table; | 
|             if($names != "$parent."){ // case-sensitive | 
|                 preg_match_all('~\\b([a-z_][a-z0-9_]*)([.:])~i', $names, $matches, PREG_SET_ORDER); | 
|                 foreach($matches as $match){ | 
|                     list(, $name, $delimiter) = $match; | 
|                     $table         = $this->notORM->structure->getReferencedTable($name, $parent); | 
|                     $column        = ($delimiter == ':' ? $this->notORM->structure->getPrimary($parent) | 
|                         : $this->notORM->structure->getReferencedColumn($name, $parent)); | 
|                     $primary       = ($delimiter == ':' ? $this->notORM->structure->getReferencedColumn($parent, $table) | 
|                         : $this->notORM->structure->getPrimary($table)); | 
|                     $return[$name] = " LEFT JOIN $table" . ($table != $name ? " AS $name" | 
|                             : "") . " ON $parent.$column = $name.$primary"; // should use alias if the table is used on more places | 
|                     $parent        = $name; | 
|                 } | 
|             } | 
|         } | 
|         return $return; | 
|     } | 
|   | 
|     /** Get SQL query | 
|      * @return string | 
|      */ | 
|     function __toString(){ | 
|         $return = "SELECT" . $this->topString($this->limit) . " "; | 
|         $join   = $this->createJoins(implode(",", $this->conditions) . "," . implode(",", $this->select) . ",$this->group,$this->having," . implode(",", $this->order)); | 
|         if(!isset($this->rows) && $this->notORM->cache && !is_string($this->accessed)){ | 
|             $this->accessed = $this->notORM->cache->load("$this->table;" . implode(",", $this->conditions)); | 
|             $this->access   = $this->accessed; | 
|         } | 
|         if($this->select){ | 
|             $return .= $this->removeExtraDots(implode(", ", $this->select)); | 
|         }elseif($this->accessed){ | 
|             $return .= ($join ? "$this->table." : "") . implode(", " . ($join ? "$this->table." | 
|                         : ""), array_keys($this->accessed)); | 
|         }else{ | 
|             $return .= ($join ? "$this->table." : "") . "*"; | 
|         } | 
|         $return .= " FROM $this->table" . implode($join) . $this->whereString(); | 
|         if($this->union){ | 
|             $return = ($this->notORM->driver == "sqlite" || $this->notORM->driver == "oci" ? $return | 
|                     : "($return)") . implode($this->union); | 
|             if($this->unionOrder){ | 
|                 $return .= " ORDER BY " . implode(", ", $this->unionOrder); | 
|             } | 
|             $return .= $this->limitString($this->unionLimit, $this->unionOffset); | 
|             $top = $this->topString($this->unionLimit); | 
|             if($top){ | 
|                 $return = "SELECT$top * FROM ($return) t"; | 
|             } | 
|         } | 
|         return $return; | 
|     } | 
|   | 
|     /** | 
|      * 放开限制 protected -> public @scott 反馈 | 
|      * | 
|      * @param $query        string | 
|      * @param $parameters   array | 
|      * | 
|      * @return bool|PDOStatement | 
|      */ | 
|     public function query($query, $parameters){ | 
|         $debugTrace = array(); | 
|   | 
|         self::$queryTimes++; | 
|   | 
|         /** | 
|          * 修正当参数过多时的SQLSTATE[HY093] @dogstar 2014-11-18 | 
|          */ | 
|         $parameters = array_map(array($this, 'formatValue'), $parameters); | 
|         foreach($parameters as $key => $val){ | 
|             if(substr($key, 0, 1) == ':' && stripos($query, $key) === false){ | 
|                 unset($parameters[$key]); | 
|             } | 
|         } | 
|   | 
|         if($this->notORM->debug){ | 
|             $debugTrace['startTime'] = microtime(true); | 
|   | 
|             if(!is_callable($this->notORM->debug)){ | 
|                 $debug = "$query;"; | 
|                 if($parameters){ | 
|                     $debug .= " -- " . implode(", ", array_map(array($this, 'quote'), $parameters)); | 
|                 } | 
|                 $pattern = '(^' . preg_quote(dirname(__FILE__)) . '(\\.php$|[/\\\\]))'; // can be static | 
|                 foreach(debug_backtrace() as $backtrace){ | 
|                     if(isset($backtrace["file"]) && !preg_match($pattern, $backtrace["file"])){ // stop on first file outside NotORM source codes | 
|                         break; | 
|                     } | 
|                 } | 
|                 /** @var array $backtrace */ | 
|                 //error_log("{$backtrace['file']}:{$backtrace['line']}:$debug\n", 0); | 
|                 //if ($this->notORM->debug) echo "$debug<br />\n";    //@dogstar 2014-10-31 | 
|   | 
|                 $debugTrace['sql'] = $debug; | 
|             }elseif(call_user_func($this->notORM->debug, $query, $parameters) === false){ | 
|                 return false; | 
|             } | 
|         } | 
|   | 
|         $return = $this->notORM->connection->prepare($query); | 
|   | 
|         //对于替换参数进行处理   @喵了个咪 2015-12-25 | 
|         $sum = 1; | 
|         foreach($parameters as $key => $val){ | 
|             if(strstr($key, ':')){ | 
|                 if(is_int($val)){ | 
|                     $return->bindValue($key, $val, PDO::PARAM_INT); | 
|                 }elseif(is_bool($val)){ | 
|                     $return->bindValue($key, $val, PDO::PARAM_BOOL); | 
|                 }elseif(is_null($val)){ | 
|                     $return->bindValue($key, $val, PDO::PARAM_NULL); | 
|                 }else{ | 
|                     $return->bindValue($key, $val); | 
|                 } | 
|             }else{ | 
|                 if(is_int($val)){ | 
|                     $return->bindValue($sum, $val, PDO::PARAM_INT); | 
|                 }elseif(is_bool($val)){ | 
|                     $return->bindValue($sum, $val, PDO::PARAM_BOOL); | 
|                 }elseif(is_null($val)){ | 
|                     $return->bindValue($sum, $val, PDO::PARAM_NULL); | 
|                 }else{ | 
|                     $return->bindValue($sum, $val); | 
|                 } | 
|                 $sum++; | 
|             } | 
|         } | 
|   | 
|         $errorMessage = null; | 
|         if(!$return || !$return->execute()){ | 
|   | 
|             $errorInfo = $return->errorInfo(); | 
|   | 
|             $errorMessage = isset($errorInfo[2]) ? $errorInfo[2] : $errorMessage; | 
|   | 
|             $return = false; | 
|         } | 
|   | 
|         if($this->notORM->debug){ | 
|             $debugTrace['endTime'] = microtime(true); | 
|   | 
|             $sqlInfo = sprintf("[%s - %sms]%s",  | 
|                 self::$queryTimes,  | 
|                 round(($debugTrace['endTime'] - $debugTrace['startTime']) * 1000, 2),  | 
|                 $debugTrace['sql'] | 
|             ); | 
|             DI()->tracer->sql($sqlInfo); | 
|         } | 
|   | 
|         //显式抛出异常,以让开发同学尽早发现SQL语法问题 @dogstar 20150426 | 
|         if($return === false && $errorMessage !== null){ | 
|             throw new PDOException($errorMessage); | 
|         } | 
|   | 
|         if($this->notORM->debugTimer){ | 
|             call_user_func($this->notORM->debugTimer); | 
|         } | 
|   | 
|         return $return; | 
|     } | 
|   | 
|     protected function formatValue($val){ | 
|         if($val instanceof DateTime){ | 
|             return $val->format("Y-m-d H:i:s"); //! may be driver specific | 
|         } | 
|         return $val; | 
|     } | 
|   | 
|     protected function quote($val){ | 
|         if(!isset($val)){ | 
|             return "NULL"; | 
|         } | 
|         if(is_array($val)){ // (a, b) IN ((1, 2), (3, 4)) | 
|             return "(" . implode(", ", array_map(array($this, 'quote'), $val)) . ")"; | 
|         } | 
|         $val = $this->formatValue($val); | 
|         if(is_float($val)){ | 
|             return sprintf("%F", $val); // otherwise depends on setlocale() | 
|         } | 
|         if($val === false){ | 
|             return "0"; | 
|         } | 
|         if(is_int($val) || $val instanceof NotORM_Literal){ // number or SQL code - for example "NOW()" | 
|             return (string) $val; | 
|         } | 
|         return $this->notORM->connection->quote($val); | 
|     } | 
|   | 
|     /** Shortcut for call_user_func_array(array($this, 'insert'), $rows) | 
|      * | 
|      * @param array | 
|      * | 
|      * @return int number of affected rows or false in case of an error | 
|      */ | 
|     function insert_multi(array $rows){ | 
|         if($this->notORM->freeze){ | 
|             return false; | 
|         } | 
|         if(!$rows){ | 
|             return 0; | 
|         } | 
|         $data       = reset($rows); | 
|         $parameters = array(); | 
|         if($data instanceof NotORM_Result){ | 
|             $parameters = $data->parameters; //! other parameters | 
|             $data       = (string) $data; | 
|         }elseif($data instanceof Traversable){ | 
|             $data = iterator_to_array($data); | 
|         } | 
|         $insert = $data; | 
|         if(is_array($data)){ | 
|             $values = array(); | 
|             foreach($rows as $value){ | 
|                 if($value instanceof Traversable){ | 
|                     $value = iterator_to_array($value); | 
|                 } | 
|                 $values[] = $this->quote($value); | 
|                 foreach($value as $val){ | 
|                     if($val instanceof NotORM_Literal && $val->parameters){ | 
|                         $parameters = array_merge($parameters, $val->parameters); | 
|                     } | 
|                 } | 
|             } | 
|   | 
|             $quoteChar = $this->getQuoteChar(); | 
|   | 
|             //! driver specific extended insert | 
|             $insert = ($data || $this->notORM->driver == "mysql") | 
|                 ? "({$quoteChar}" . implode("{$quoteChar}, {$quoteChar}", array_keys($data)) . "{$quoteChar}) VALUES " . implode(", ", $values)  | 
|                 : "DEFAULT VALUES"; | 
|         } | 
|         // requires empty $this->parameters | 
|         $return = $this->query("INSERT INTO $this->table $insert", $parameters); | 
|         if(!$return){ | 
|             return false; | 
|         } | 
|         $this->rows = null; | 
|         return $return->rowCount(); | 
|     } | 
|   | 
|     /** Insert row in a table | 
|      * | 
|      * @param mixed array($column => $value)|Traversable for single row insert or NotORM_Result|string for INSERT ... SELECT | 
|      * @param ... used for extended insert | 
|      * | 
|      * @return mixed inserted NotORM_Row or false in case of an error or number of affected rows for INSERT ... SELECT | 
|      */ | 
|     function insert($data){ | 
|         $rows   = func_get_args(); | 
|         $return = $this->insert_multi($rows); | 
|         if(!$return){ | 
|             return false; | 
|         } | 
|         if(!is_array($data)){ | 
|             return $return; | 
|         } | 
|   | 
|         // #56 postgresql无法获取新增数据的主键ID @ clov4r-连友 201608 | 
|         if ($this->notORM->driver == "pgsql") { | 
|             if (!isset($data[$this->primary])) { | 
|                 //获取序列名称 | 
|                 $pgss = $this->query("SELECT pg_get_serial_sequence('" . $this->table . "', '" . $this->primary . "') pgss", $this->parameters)->fetch(); | 
|                 if (isset($pgss['pgss'])) { | 
|                     $rs                   = $this->query("select last_value id from " . $pgss['pgss'], $this->parameters)->fetch(); | 
|                     $data[$this->primary] = $rs['id']; | 
|                     $this->sequence       = $rs['id']; | 
|                 } | 
|             } | 
|         } else { | 
|             if (!isset($data[$this->primary]) && ($id = $this->notORM->connection->lastInsertId($this->notORM->structure->getSequence($this->table)))) { | 
|                 $data[$this->primary] = $id; | 
|             } | 
|         } | 
|         return new $this->notORM->rowClass($data, $this); | 
|     } | 
|   | 
|     /** Update all rows in result set | 
|      * | 
|      * @param array ($column => $value) | 
|      * | 
|      * @return int number of affected rows or false in case of an error | 
|      */ | 
|     function update(array $data){ | 
|         if($this->notORM->freeze){ | 
|             return false; | 
|         } | 
|         if(!$data){ | 
|             return 0; | 
|         } | 
|         $values     = array(); | 
|         $parameters = array(); | 
|         $quoteChar  = $this->getQuoteChar(); | 
|         foreach($data as $key => $val){ | 
|             // doesn't use binding because $this->parameters can be filled by ? or :name | 
|             $values[] = "{$quoteChar}{$key}{$quoteChar} = " . $this->quote($val); | 
|             if($val instanceof NotORM_Literal && $val->parameters){ | 
|                 $parameters = array_merge($parameters, $val->parameters); | 
|             } | 
|         } | 
|         if($this->parameters){ | 
|             $parameters = array_merge($parameters, $this->parameters); | 
|         } | 
|         // joins in UPDATE are supported only in MySQL | 
|         $return = $this->query("UPDATE" . $this->topString($this->limit) . " $this->table SET " . implode(", ", $values) . $this->whereString(), $parameters); | 
|         if(!$return){ | 
|             return false; | 
|         } | 
|         return $return->rowCount(); | 
|     } | 
|   | 
|     /** | 
|      * 获取数据库字段的引号 | 
|      * @author dogstar 20170617 | 
|      */ | 
|     protected function getQuoteChar() { | 
|         $allowDBs = array('mysql'); | 
|         return in_array($this->notORM->driver, $allowDBs) ? '`' : ''; | 
|     } | 
|   | 
|     /** Insert row or update if it already exists | 
|      * | 
|      * @param array ($column => $value) | 
|      * @param array ($column => $value) | 
|      * @param array ($column => $value), empty array means use $insert | 
|      * | 
|      * @return int number of affected rows or false in case of an error | 
|      */ | 
|     function insert_update(array $unique, array $insert, array $update = array()){ | 
|         if(!$update){ | 
|             $update = $insert; | 
|         } | 
|         $insert = $unique + $insert; | 
|         $values = "(" . implode(", ", array_keys($insert)) . ") VALUES " . $this->quote($insert); | 
|         //! parameters | 
|         if($this->notORM->driver == "mysql"){ | 
|             $set = array(); | 
|             if(!$update){ | 
|                 $update = $unique; | 
|             } | 
|             $quoteChar  = $this->getQuoteChar(); | 
|   | 
|             foreach($update as $key => $val){ | 
|                 $set[] = "{$quoteChar}{$key}{$quoteChar} = " . $this->quote($val); | 
|                 //! parameters | 
|             } | 
|             return $this->insert("$values ON DUPLICATE KEY UPDATE " . implode(", ", $set)); | 
|         }else{ | 
|             $connection = $this->notORM->connection; | 
|             $errorMode  = $connection->getAttribute(PDO::ATTR_ERRMODE); | 
|             $connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); | 
|             try{ | 
|                 $return = $this->insert($values); | 
|                 $connection->setAttribute(PDO::ATTR_ERRMODE, $errorMode); | 
|                 return $return; | 
|             } | 
|             catch(PDOException $e){ | 
|                 $connection->setAttribute(PDO::ATTR_ERRMODE, $errorMode); | 
|                 if($e->getCode() == "23000" || $e->getCode() == "23505"){ // "23000" - duplicate key, "23505" unique constraint pgsql | 
|                     if(!$update){ | 
|                         return 0; | 
|                     } | 
|                     $clone  = clone $this; | 
|                     $return = $clone->where($unique)->update($update); | 
|                     return ($return ? $return + 1 : $return); | 
|                 } | 
|                 if($errorMode == PDO::ERRMODE_EXCEPTION){ | 
|                     throw $e; | 
|                 }elseif($errorMode == PDO::ERRMODE_WARNING){ | 
|                     trigger_error("PDOStatement::execute(): " . $e->getMessage(), E_USER_WARNING); // E_WARNING is unusable | 
|                 } | 
|             } | 
|         } | 
|         return 0; | 
|     } | 
|   | 
|     /** Get last insert ID | 
|      * @return string number | 
|      */ | 
|     function insert_id(){ | 
|         return $this->notORM->connection->lastInsertId(); | 
|     } | 
|   | 
|     /** Delete all rows in result set | 
|      * @return bool|int number of affected rows or false in case of an error | 
|      * @throws Exception | 
|      */ | 
|     function delete(){ | 
|         if($this->notORM->freeze){ | 
|             return false; | 
|         } | 
|         //防止误删,禁止全表的删除 | 
|         //@dogstar - 2014-10-24 | 
|         $where = $this->whereString(); | 
|         if(empty($where)){ | 
|             throw new Exception('sorry, you can not delete the whole table --dogstar'); | 
|         } | 
|   | 
|         $return = $this->query("DELETE" . $this->topString($this->limit) . " FROM $this->table" . $where, $this->parameters); | 
|         if(!$return){ | 
|             return false; | 
|         } | 
|         return $return->rowCount(); | 
|     } | 
|   | 
|     /** Add select clause, more calls appends to the end | 
|      * | 
|      * @param string $columns for example "column, MD5(column) AS column_md5", empty string to reset previously set columns | 
|      * @param string ... | 
|      * | 
|      * @return NotORM_Result fluent interface | 
|      */ | 
|     function select($columns){ | 
|         $this->__destruct(); | 
|         if($columns != ""){ | 
|             foreach(func_get_args() as $columns){ | 
|                 $this->select[] = $columns; | 
|             } | 
|         }else{ | 
|             $this->select = array(); | 
|         } | 
|         return $this; | 
|     } | 
|   | 
|     /** Add where condition, more calls appends with AND | 
|      * | 
|      * @param mixed string possibly containing ? or :name; or array($condition => $parameters, ...) | 
|      * @param mixed array accepted by PDOStatement::execute or a scalar value | 
|      * @param mixed ... | 
|      * | 
|      * @return NotORM_Result fluent interface | 
|      */ | 
|     function where($condition, $parameters = array()){ | 
|         $args = func_get_args(); | 
|         return $this->whereOperator("AND", $args); | 
|     } | 
|   | 
|     protected function whereOperator($operator, array $args){ | 
|         $condition  = $args[0]; | 
|         $parameters = (count($args) > 1 ? $args[1] : array()); | 
|         if(is_array($condition)){ // where(array("column1" => 1, "column2 > ?" => 2)) | 
|             foreach($condition as $key => $val){ | 
|                 $this->where($key, $val); | 
|             } | 
|             return $this; | 
|         } | 
|         $this->__destruct(); | 
|         $this->conditions[] = "$operator $condition"; | 
|         $condition          = $this->removeExtraDots($condition); | 
|         if(count($args) != 2 || strpbrk($condition, "?:")){ // where("column < ? OR column > ?", array(1, 2)) | 
|             if(count($args) != 2 || !is_array($parameters)){ // where("column < ? OR column > ?", 1, 2) | 
|                 $parameters = array_slice($args, 1); | 
|             } | 
|             $this->parameters = array_merge($this->parameters, $parameters); | 
|         }elseif($parameters === null){ // where("column", null) | 
|             $condition .= " IS NULL"; | 
|         }elseif($parameters instanceof NotORM_Result){ // where("column", $db->$table()) | 
|             $clone = clone $parameters; | 
|             if(!$clone->select){ | 
|                 $clone->select($this->notORM->structure->getPrimary($clone->table)); | 
|             } | 
|             if($this->notORM->driver != "mysql"){ | 
|                 if($clone instanceof NotORM_MultiResult){ | 
|                     array_shift($clone->select); | 
|                     $clone->single(); | 
|                 } | 
|                 $condition .= " IN ($clone)"; | 
|                 $this->parameters = array_merge($this->parameters, $clone->parameters); | 
|             }else{ | 
|                 $in = array(); | 
|                 foreach($clone as $row){ | 
|                     $row = array_values(iterator_to_array($row)); | 
|                     if($clone instanceof NotORM_MultiResult && count($row) > 1){ | 
|                         array_shift($row); | 
|                     } | 
|                     if(count($row) == 1){ | 
|                         $in[] = $this->quote($row[0]); | 
|                     }else{ | 
|                         $in[] = $this->quote($row); | 
|                     } | 
|                 } | 
|                 if($in){ | 
|                     $condition .= " IN (" . implode(", ", $in) . ")"; | 
|                 }else{ | 
|                     $condition = "($condition) IS NOT NULL AND $condition IS NULL"; // $condition = "NOT id" | 
|                 } | 
|             } | 
|         }elseif(!is_array($parameters)){ // where("column", "x") | 
|             $condition .= " = " . $this->quote($parameters); | 
|         }else{ // where("column", array(1, 2)) | 
|             $condition = $this->whereIn($condition, $parameters); | 
|         } | 
|         $this->where[] = (preg_match('~^\)+$~', $condition) | 
|             ? $condition : ($this->where ? " $operator " | 
|                 : "") . "($condition)"); | 
|         return $this; | 
|     } | 
|   | 
|     protected function whereIn($condition, $parameters){ | 
|         if(!$parameters){ | 
|             $condition = "($condition) IS NOT NULL AND $condition IS NULL"; | 
|         }elseif($this->notORM->driver != "oci"){ | 
|             $column = $condition; | 
|             $condition .= " IN " . $this->quote($parameters); | 
|             $nulls = array_filter($parameters, 'is_null'); | 
|             if($nulls){ | 
|                 $condition = "$condition OR $column IS NULL"; | 
|             } | 
|         }else{ // http://download.oracle.com/docs/cd/B19306_01/server.102/b14200/expressions014.htm | 
|             $or = array(); | 
|             for($i = 0; $i < count($parameters); $i += 1000){ | 
|                 $or[] = "$condition IN " . $this->quote(array_slice($parameters, $i, 1000)); | 
|             } | 
|             $condition = implode(" OR ", $or); | 
|         } | 
|         return $condition; | 
|     } | 
|   | 
|     function __call($name, array $args){ | 
|         $operator = strtoupper($name); | 
|         switch($operator){ | 
|             case "AND": | 
|             case "OR": | 
|                 return $this->whereOperator($operator, $args); | 
|         } | 
|         trigger_error("Call to undefined method NotORM_Result::$name()", E_USER_ERROR); | 
|         return FALSE; | 
|     } | 
|   | 
|     /** Shortcut for where() | 
|      * | 
|      * @param string | 
|      * @param mixed | 
|      * @param mixed ... | 
|      * | 
|      * @return NotORM_Result fluent interface | 
|      */ | 
|     function __invoke($where, $parameters = array()){ | 
|         $args = func_get_args(); | 
|         return $this->whereOperator("AND", $args); | 
|     } | 
|   | 
|     /** Add order clause, more calls appends to the end | 
|      * | 
|      * @param string $columns for example "column1, column2 DESC", empty string to reset previous order | 
|      * @param string ... | 
|      * | 
|      * @return NotORM_Result fluent interface | 
|      */ | 
|     function order($columns){ | 
|         $this->rows = null; | 
|         if($columns != ""){ | 
|             foreach(func_get_args() as $columns){ | 
|                 if($this->union){ | 
|                     $this->unionOrder[] = $columns; | 
|                 }else{ | 
|                     $this->order[] = $columns; | 
|                 } | 
|             } | 
|         }elseif($this->union){ | 
|             $this->unionOrder = array(); | 
|         }else{ | 
|             $this->order = array(); | 
|         } | 
|         return $this; | 
|     } | 
|   | 
|     /** | 
|      * 对查询进行limit操作 | 
|      * 请注意以下两种用法,与NotORM原来的定义有所区别 | 
|      * - limit(数量) | 
|      * - limit(开始位置,数量) | 
|      * Set limit clause, more calls rewrite old values | 
|      * | 
|      * @param int | 
|      * @param int | 
|      * | 
|      * @return NotORM_Result fluent interface | 
|      */ | 
|     function limit($limit, $offset = null){ | 
|         $this->rows = null; | 
|         if($this->union){ | 
|             $this->unionLimit  = +$limit; | 
|             $this->unionOffset = +$offset; | 
|         }else{ | 
|             $this->limit  = +$limit; | 
|             $this->offset = +$offset; | 
|         } | 
|         return $this; | 
|     } | 
|   | 
|     /** Set group clause, more calls rewrite old values | 
|      * | 
|      * @param string | 
|      * @param string | 
|      * | 
|      * @return NotORM_Result fluent interface | 
|      */ | 
|     function group($columns, $having = ""){ | 
|         $this->__destruct(); | 
|         $this->group  = $columns; | 
|         $this->having = $having; | 
|         return $this; | 
|     } | 
|   | 
|     /** Set select FOR UPDATE or LOCK IN SHARE MODE | 
|      * | 
|      * @param bool | 
|      * | 
|      * @return NotORM_Result fluent interface | 
|      */ | 
|     function lock($exclusive = true){ | 
|         $this->lock = $exclusive; | 
|         return $this; | 
|     } | 
|   | 
|     /** | 
|      * @param NotORM_Result | 
|      * @param bool | 
|      * | 
|      * @return NotORM_Result fluent interface | 
|      */ | 
|     function union(NotORM_Result $result, $all = false){ | 
|         $this->union[]    = " UNION " . ($all ? "ALL " | 
|                 : "") . ($this->notORM->driver == "sqlite" || $this->notORM->driver == "oci" ? $result : "($result)"); | 
|         $this->parameters = array_merge($this->parameters, $result->parameters); | 
|         return $this; | 
|     } | 
|   | 
|     /** Execute aggregation function | 
|      * | 
|      * @param string $function | 
|      * | 
|      * @return string | 
|      */ | 
|     function aggregation($function){ | 
|         $join  = $this->createJoins(implode(",", $this->conditions) . ",$function"); | 
|         $query = "SELECT $function FROM $this->table" . implode($join); | 
|         if($this->where){ | 
|             $query .= " WHERE " . implode($this->where); | 
|         } | 
|   | 
|         foreach($this->query($query, $this->parameters)->fetch() as $return){ | 
|             return $return; | 
|         } | 
|     } | 
|   | 
|     /** Count number of rows | 
|      * | 
|      * @param string | 
|      * | 
|      * @return int | 
|      */ | 
|     function count($column = "*"){ | 
|         return $this->aggregation("COUNT($column)"); | 
|     } | 
|   | 
|     /** Return minimum value from a column | 
|      * | 
|      * @param string | 
|      * | 
|      * @return int | 
|      */ | 
|     function min($column){ | 
|         return $this->aggregation("MIN($column)"); | 
|     } | 
|   | 
|     /** Return maximum value from a column | 
|      * | 
|      * @param string | 
|      * | 
|      * @return int | 
|      */ | 
|     function max($column){ | 
|         return $this->aggregation("MAX($column)"); | 
|     } | 
|   | 
|     /** Return sum of values in a column | 
|      * | 
|      * @param string | 
|      * | 
|      * @return int | 
|      */ | 
|     function sum($column){ | 
|         return $this->aggregation("SUM($column)"); | 
|     } | 
|   | 
|     /** Execute the built query | 
|      * | 
|      */ | 
|     protected function execute(){ | 
|         if(!isset($this->rows)){ | 
|             $result     = false; | 
|             $exception  = null; | 
|             $parameters = array(); | 
|             foreach(array_merge($this->select, array( | 
|                 $this, | 
|                 $this->group, | 
|                 $this->having | 
|             ), $this->order, $this->unionOrder) as $val){ | 
|                 if(($val instanceof NotORM_Literal || $val instanceof self) && $val->parameters){ | 
|                     $parameters = array_merge($parameters, $val->parameters); | 
|                 } | 
|             } | 
|             try{ | 
|                 $result = $this->query($this->__toString(), $parameters); | 
|             } | 
|             catch(PDOException $exception){ | 
|                 // handled later | 
|             } | 
|             if(!$result){ | 
|                 if(!$this->select && $this->accessed){ | 
|                     $this->accessed = ''; | 
|                     $this->access   = array(); | 
|                     $result         = $this->query($this->__toString(), $parameters); | 
|                 }elseif($exception){ | 
|                     throw $exception; | 
|                 } | 
|             } | 
|             $this->rows = array(); | 
|             if($result){ | 
|                 $result->setFetchMode(PDO::FETCH_ASSOC); | 
|                 foreach($result as $key => $row){ | 
|                     if(isset($row[$this->primary])){ | 
|                         $key = $row[$this->primary]; | 
|                         if(!is_string($this->access)){ | 
|                             $this->access[$this->primary] = true; | 
|                         } | 
|                     } | 
|                     //$this->rows[$key] = new $this->notORM->rowClass($row, $this); | 
|                     if ($this->notORM->isKeepPrimaryKeyIndex) { | 
|                         //@dogstar 采用主键作为下标 2015-12-30 | 
|                         $this->rows[$key] = $row; | 
|                     } else { | 
|                         //@dogstar 改用返回数组 2014-11-01 | 
|                         $this->rows[] = $row; | 
|                     } | 
|                 } | 
|             } | 
|             $this->data = $this->rows; | 
|         } | 
|     } | 
|   | 
|     /** Fetch next row of result | 
|      * | 
|      * @param string $column name to return or an empty string for the whole row | 
|      * | 
|      * @return mixed string or null with $column, NotORM_Row without $column, false if there is no row | 
|      */ | 
|     function fetch($column = ''){ | 
|         // no $this->select($column) because next calls can access different columns | 
|         $this->execute(); | 
|         $return = current($this->data); | 
|         next($this->data); | 
|         if($return && $column != ''){ | 
|             return $return[$column]; | 
|         } | 
|         return $return; | 
|     } | 
|   | 
|     /** | 
|      * fetchRow别名,等效于NotORM_Result::fetchRow() | 
|      * @author: dogstar 2015-04-26 | 
|      * | 
|      * @param string $column | 
|      * | 
|      * @return mixed | 
|      */ | 
|     function fetchOne($column = ''){ | 
|         return $this->fetchRow($column); | 
|     } | 
|   | 
|     /** | 
|      * 只查询第一行纪录,等效于NotORM_Result::fetchOne() | 
|      * @author: dogstar 2015-04-18 | 
|      * | 
|      * @param string $column | 
|      * | 
|      * @return mixed | 
|      */ | 
|     function fetchRow($column = ''){ | 
|         $this->limit(1)->execute(); | 
|         return $this->fetch($column); | 
|     } | 
|   | 
|     /** | 
|      * 返回全部的数据,等效于NotORM_Result::fetchRows() | 
|      * @author: dogstar 2014-11-01 | 
|      */ | 
|     function fetchAll(){ | 
|         $this->execute(); | 
|         return $this->data; | 
|     } | 
|   | 
|     /** | 
|      * fetchAll别名,等效于NotORM_Result::fetchAll() | 
|      * @author: dogstar 2015-04-26 | 
|      */ | 
|     function fetchRows(){ | 
|         return $this->fetchAll(); | 
|     } | 
|   | 
|     /** | 
|      * queryRows别名,等效于NotORM_Result::queryRows($sql, $parmas) | 
|      * @author: dogstar 2015-04-26 | 
|      * | 
|      * @param string    $sql | 
|      * @param array     $parmas | 
|      * | 
|      * @return array | 
|      */ | 
|     function queryAll($sql, $parmas = array()){ | 
|         return $this->queryRows($sql, $parmas); | 
|     } | 
|   | 
|     /** | 
|      * 根据SQL查询全部数据,等效于NotORM_Result::queryAll($sql, $parmas) | 
|      * | 
|      * @param string    $sql | 
|      * @param array     $parmas | 
|      * | 
|      * @return array | 
|      * @author: dogstar 2014-11-01 | 
|      */ | 
|     function queryRows($sql, $parmas = array()){ | 
|         $result = $this->query($sql, $parmas); | 
|   | 
|         $rows = array(); | 
|         if($result){ | 
|             $result->setFetchMode(PDO::FETCH_ASSOC); | 
|             foreach($result as $row){ | 
|                 if(isset($row[$this->primary])){ | 
|                     if(!is_string($this->access)){ | 
|                         $this->access[$this->primary] = true; | 
|                     } | 
|                 } | 
|                 $rows[] = $row; | 
|             } | 
|         } | 
|   | 
|         return $rows; | 
|     } | 
|   | 
|     /** Fetch all rows as associative array | 
|      * | 
|      * @param string $key | 
|      * @param string $value column name used for an array value or an empty string for the whole row | 
|      * | 
|      * @return array | 
|      */ | 
|     function fetchPairs($key, $value = ''){ | 
|         $return = array(); | 
|         $clone  = clone $this; | 
|         if($value != ""){ | 
|             $clone->select = array(); | 
|             $clone->select("$key, $value"); // MultiResult adds its column | 
|         }elseif($clone->select){ | 
|             array_unshift($clone->select, $key); | 
|         }else{ | 
|             $clone->select = array("$key, $this->table.*"); | 
|         } | 
|         foreach($clone as $row){ | 
|             //$values = array_values(iterator_to_array($row)); | 
|             //bugfixed: Argument 1 passed to iterator_to_array() must implement interface Traversable, array given | 
|             //@Scott @dogstar 20151122 | 
|             /** @var Traversable|array $row */ | 
|             $values = ($row instanceof Traversable) ? array_values(iterator_to_array($row)) : array_values($row); | 
|             if($value != "" && $clone instanceof NotORM_MultiResult){ | 
|                 array_shift($values); | 
|             } | 
|             $return[(string) $values[0]] = ($value != "" ? $values[(array_key_exists(1, $values) ? 1 : 0)] | 
|                 : $row); // isset($values[1]) - fetchPairs("id", "id") | 
|         } | 
|         return $return; | 
|     } | 
|   | 
|     protected function access($key, $delete = false){ | 
|         if($delete){ | 
|             if(is_array($this->access)){ | 
|                 $this->access[$key] = false; | 
|             } | 
|             return false; | 
|         } | 
|         if(!isset($key)){ | 
|             $this->access = ''; | 
|         }elseif(!is_string($this->access)){ | 
|             $this->access[$key] = true; | 
|         } | 
|         if(!$this->select && $this->accessed && (!isset($key) || !isset($this->accessed[$key]))){ | 
|             $this->accessed = ''; | 
|             $this->rows     = null; | 
|             return true; | 
|         } | 
|         return false; | 
|     } | 
|   | 
|     protected function single(){ | 
|     } | 
|   | 
|     // Iterator implementation (not IteratorAggregate because $this->data can be changed during iteration) | 
|   | 
|     function rewind(){ | 
|         $this->execute(); | 
|         $this->keys = array_keys($this->data); | 
|         reset($this->keys); | 
|     } | 
|   | 
|     /** @return NotORM_Row */ | 
|     function current(){ | 
|         return $this->data[current($this->keys)]; | 
|     } | 
|   | 
|     /** @return string row ID */ | 
|     function key(){ | 
|         return current($this->keys); | 
|     } | 
|   | 
|     function next(){ | 
|         next($this->keys); | 
|     } | 
|   | 
|     function valid(){ | 
|         return current($this->keys) !== false; | 
|     } | 
|   | 
|     // ArrayAccess implementation | 
|   | 
|     /** Test if row exists | 
|      * | 
|      * @param mixed $key | 
|      * | 
|      * @return bool | 
|      * | 
|      */ | 
|     function offsetExists($key){ | 
|         $row = $this->offsetGet($key); | 
|         return isset($row); | 
|     } | 
|   | 
|     /** Get specified row | 
|      * | 
|      * @param string $key row ID or array for where conditions | 
|      * | 
|      * @return NotORM_Row or null if there is no such row | 
|      */ | 
|     function offsetGet($key){ | 
|         if($this->single && !isset($this->data)){ | 
|             $clone = clone $this; | 
|             if(is_array($key)){ | 
|                 $clone->where($key)->limit(1); | 
|             }else{ | 
|                 $clone->where($this->primary, $key); | 
|             } | 
|             $return = $clone->fetch(); | 
|             if($return){ | 
|                 return $return; | 
|             } | 
|         }else{ | 
|             $this->execute(); | 
|             if(is_array($key)){ | 
|                 foreach($this->data as $row){ | 
|                     foreach($key as $k => $v){ | 
|                         if((isset($v) && $row[$k] !== null ? $row[$k] != $v : $row[$k] !== $v)){ | 
|                             continue 2; | 
|                         } | 
|                     } | 
|                     return $row; | 
|                 } | 
|             }elseif(isset($this->data[$key])){ | 
|                 return $this->data[$key]; | 
|             } | 
|         } | 
|         return NULL; | 
|     } | 
|   | 
|     /** Mimic row | 
|      * | 
|      * @param string $key row ID | 
|      * @param NotORM_Row $value | 
|      * | 
|      */ | 
|     function offsetSet($key, $value){ | 
|         $this->execute(); | 
|         $this->data[$key] = $value; | 
|     } | 
|   | 
|     /** Remove row from result set | 
|      * | 
|      * @param string $key row ID | 
|      * | 
|      */ | 
|     function offsetUnset($key){ | 
|         $this->execute(); | 
|         unset($this->data[$key]); | 
|     } | 
|   | 
|     // JsonSerializable implementation | 
|   | 
|     function jsonSerialize(){ | 
|         $this->execute(); | 
|         if($this->notORM->jsonAsArray){ | 
|             return array_values($this->data); | 
|         }else{ | 
|             return $this->data; | 
|         } | 
|     } | 
|   | 
| } |