<?php if (!defined('BASEPATH')) exit('No direct script access allowed');

/**
 * Class MongoDB操作类
 */
Class Mongo_db
{

    private $CI;
    private $config = array();
    private $manager;
    private $param;
    private $db;
    private $hostname;
    private $port;
    private $database;
    private $username;
    private $password;
    private $debug;
    private $filter = array();
    private $option = array();
    private $selects = array();
    private $updates = array();
    private $pipeline = array();
    private $limit = 999999;
    private $offset = 0;
    private $db_version;


    /**
     * --------------------------------------------------------------------------------
     * Class Constructor
     * --------------------------------------------------------------------------------
     *
     * Automatically check if the Mongo PECL extension has been installed/enabled.
     * Get Access to all CodeIgniter available resources.
     * Load mongodb config file from application/config folder.
     * Prepare the connection variables and establish a connection to the MongoDB.
     * Try to connect on MongoDB server.
     */

    function __construct($param)
    {

        if (!class_exists('MongoDB\Driver\Manager') && !class_exists('MongoDB\Driver\Command')) {
            show_error("The MongoDB Driver extension has not been installed or enabled", 500);
        }
        $this->CI =& get_instance();
        $this->CI->load->config('mongo_db');
        $this->config = $this->CI->config->item('mongo_db');
        $this->param = $param;
        $this->connect();
    }

    /*
    * --------------------------------------------------------------------------------
    * Prepare configuration for mongoDB connection
    * --------------------------------------------------------------------------------
    *
    * Validate group name or autoload default group name from config file.
    * Validate all the properties present in config file of the group.
    */

    private function prepare()
    {
        if (is_array($this->param) && count($this->param) > 0 && isset($this->param['activate']) == TRUE) {
            $this->activate = $this->param['activate'];
        } else if (isset($this->config['active']) && !empty($this->config['active'])) {
            $this->activate = $this->config['active'];
        } else {
            show_error("MongoDB configuration is missing.", 500);
        }

        if (isset($this->config[$this->activate]) == TRUE) {
            if (empty($this->config[$this->activate]['hostname'])) {
                show_error("Hostname missing from mongodb config group : {$this->activate}", 500);
            } else {
                $this->hostname = trim($this->config[$this->activate]['hostname']);
            }

            if (empty($this->config[$this->activate]['port'])) {
                show_error("Port number missing from mongodb config group : {$this->activate}", 500);
            } else {
                $this->port = trim($this->config[$this->activate]['port']);
            }

			if($this->config[$this->activate]['no_auth'] == FALSE){			
				if (empty($this->config[$this->activate]['username'])) {
					show_error("Username missing from mongodb config group : {$this->activate}", 500);
				} else {
					$this->username = trim($this->config[$this->activate]['username']);
				}

				if (empty($this->config[$this->activate]['password'])) {
					show_error("Password missing from mongodb config group : {$this->activate}", 500);
				} else {
					$this->password = trim($this->config[$this->activate]['password']);
				}
			}

            if (empty($this->config[$this->activate]['database'])) {
                show_error("Database name missing from mongodb config group : {$this->activate}", 500);
            } else {
                $this->database = trim($this->config[$this->activate]['database']);
            }

            if (empty($this->config[$this->activate]['db_debug'])) {
                $this->debug = FALSE;
            } else {
                $this->debug = $this->config[$this->activate]['db_debug'];
            }
        } else {
            show_error("mongodb config group :  <strong>{$this->activate}</strong> does not exist.", 500);
        }
    }

    /**
     * --------------------------------------------------------------------------------
     * Connect to MongoDB Database
     * --------------------------------------------------------------------------------
     *
     * Connect to mongoDB database or throw exception with the error message.
     */

    private function connect()
    {
        $this->prepare();
        try {
            $dns = "mongodb://{$this->hostname}:{$this->port}";
            if (isset($this->config[$this->activate]['no_auth']) == TRUE && $this->config[$this->activate]['no_auth'] == TRUE) {
                $options = array();
            } else {
                $options = array('username' => $this->username, 'password' => $this->password);
            }
            $this->manager = new MongoDB\Driver\Manager($dns, $options);
            $this->db = $this->database;
            $this->db_version = $this->get_db_version();
        } catch (Exception $e) {
            if (isset($this->debug) == TRUE && $this->debug == TRUE) {
                show_error("Unable to connect to MongoDB: {$e->getMessage()}", 500);
            } else {
                show_error("Unable to connect to MongoDB", 500);
            }
        }
    }

    public function get_manager(){
        return $this->manager;
    }

    public function get_db_version(){
        $command = new MongoDB\Driver\Command(array("buildinfo" => 1));
        try {
            $cursor = $this->manager->executeCommand("admin", $command);
            $buildinfo = (array)$cursor->toArray()[0];
            if(array_key_exists("version",$buildinfo)){
                return $buildinfo['version'];
            }else{
                return "";
            } 
        }catch (Exception $e) {
            if (isset($this->debug) == TRUE && $this->debug == TRUE) {
                show_error("MongoDB query failed: {$e->getMessage()}", 500);
            } else {
                Throw new Exception("查询数据发生异常!");
            }
        }
    }

    /**
     * 查询数据
     * @param $tales
     */
    public function get($table)
    {
        if (empty($table)) {
            show_error("In order to retrieve documents from MongoDB, a collection name must be passed", 500);
        }
        try {
            if (is_array($this->selects) && count($this->selects) > 0) {
                $this->option['projection'] = $this->selects;
            }
            $query = new MongoDB\Driver\Query($this->filter, $this->option);
            $table = $this->database . '.' . $table;
            $data = $this->manager->executeQuery($table, $query);
            $this->_clear();
            $returns = [];
            foreach ($data as $doc) {
                $returns[] = $this->object_array($doc);
            }
            return $returns;
        } catch (Exception $e) {
            if (isset($this->debug) == TRUE && $this->debug == TRUE) {
                show_error("MongoDB query failed: {$e->getMessage()}", 500);
            } else {
                Throw new Exception("查询数据发生异常!");
            }
        }
    }

    /**
     * 根据条件查询
     * @param string $table
     * @param array $filter
     * @return mixed
     */
    public function get_where($table = "", $filter = array())
    {
        if (is_array($filter) && count($filter) > 0) {
            return $this->where($filter)->get($table);
        } else {
            show_error("Nothing passed to perform search or value is empty.", 500);
        }
    }

    public function select($includes = array(), $excludes = array())
    {
        if (!is_array($includes)) {
            $includes = array();
        }
        if (!is_array($excludes)) {
            $excludes = array();
        }
        if (!empty($includes)) {
            foreach ($includes as $key => $col) {
                $this->selects[$col] = 1;
            }
        }
        if (!empty($excludes)) {
            foreach ($excludes as $col) {
                $this->selects[$col] = 0;
            }
        }
        return ($this);
    }

    public function where($filter, $value = null)
    {
        if (is_array($filter)) {
            foreach ($filter as $wh => $val) {
                $this->filter[$wh] = $val;
            }
        } elseif($filter) {
            $this->filter[$filter] = $value;
        }
        return $this;
    }

    public function where_or($filter = array())
    {
        if (is_array($filter) && count($filter) > 0) {
            if (!isset($this->filter['$or']) || !is_array($this->filter['$or'])) {
                $this->filter['$or'] = array();
            }
            foreach ($filter as $wh => $val) {
                $this->filter['$or'][] = array($wh => $val);
            }
            return ($this);
        } else {
            show_error("Where value should be an array.", 500);
        }
    }

    public function where_in($field = "", $in = array())
    {
        if (empty($field)) {
            show_error("Mongo field is require to perform where in query.", 500);
        }

        if (is_array($in) && count($in) > 0) {
            $this->_w($field);
            $this->filter[$field]['$in'] = $in;
            return ($this);
        } else {
            show_error("in value should be an array.", 500);
        }
    }

    public function where_in_all($field = "", $in = array())
    {
        if (empty($field)) {
            show_error("Mongo field is require to perform where all in query.", 500);
        }

        if (is_array($in) && count($in) > 0) {
            $this->_w($field);
            $this->filter[$field]['$all'] = $in;
            return ($this);
        } else {
            show_error("in value should be an array.", 500);
        }
    }

    public function where_not_in($field = "", $in = array())
    {
        if (empty($field)) {
            show_error("Mongo field is require to perform where not in query.", 500);
        }

        if (is_array($in) && count($in) > 0) {
            $this->_w($field);
            $this->filter[$field]['$nin'] = $in;
            return ($this);
        } else {
            show_error("in value should be an array.", 500);
        }
    }

    public function where_gt($field = "", $x)
    {
        if (!isset($field)) {
            show_error("Mongo field is require to perform greater then query.", 500);
        }

        if (!isset($x)) {
            show_error("Mongo field's value is require to perform greater then query.", 500);
        }

        $this->_w($field);
        $this->filter[$field]['$gt'] = $x;
        return ($this);
    }

    /**
     * --------------------------------------------------------------------------------
     * Where less than
     * --------------------------------------------------------------------------------
     *
     * Get the documents where the value of a $field is less than $x
     *
     * @usage : $this->mongo_db->where_lt('foo', 20);
     */
    public function where_lt($field = "", $x)
    {
        if (!isset($field)) {
            show_error("Mongo field is require to perform less then query.", 500);
        }

        if (!isset($x)) {
            show_error("Mongo field's value is require to perform less then query.", 500);
        }

        $this->_w($field);
        $this->filter[$field]['$lt'] = $x;
        return ($this);
    }

    /**
     * --------------------------------------------------------------------------------
     * Where less than or equal to
     * --------------------------------------------------------------------------------
     *
     * Get the documents where the value of a $field is less than or equal to $x
     *
     * @usage : $this->mongo_db->where_lte('foo', 20);
     */
    public function where_lte($field = "", $x)
    {
        if (!isset($field)) {
            show_error("Mongo field is require to perform less then or equal to query.", 500);
        }

        if (!isset($x)) {
            show_error("Mongo field's value is require to perform less then or equal to query.", 500);
        }

        $this->_w($field);
        $this->filter[$field]['$lte'] = $x;
        return ($this);
    }

    /**
     * --------------------------------------------------------------------------------
     * Where between
     * --------------------------------------------------------------------------------
     *
     * Get the documents where the value of a $field is between $x and $y
     *
     * @usage : $this->mongo_db->where_between('foo', 20, 30);
     */
    public function where_between($field = "", $x, $y)
    {
        if (!isset($field)) {
            show_error("Mongo field is require to perform greater then or equal to query.", 500);
        }

        if (!isset($x)) {
            show_error("Mongo field's start value is require to perform greater then or equal to query.", 500);
        }

        if (!isset($y)) {
            show_error("Mongo field's end value is require to perform greater then or equal to query.", 500);
        }

        $this->_w($field);
        $this->filter[$field]['$gte'] = $x;
        $this->filter[$field]['$lte'] = $y;
        return ($this);
    }

    /**
     * --------------------------------------------------------------------------------
     * Where between and but not equal to
     * --------------------------------------------------------------------------------
     *
     * Get the documents where the value of a $field is between but not equal to $x and $y
     *
     * @usage : $this->mongo_db->where_between_ne('foo', 20, 30);
     */
    public function where_between_ne($field = "", $x, $y)
    {
        if (!isset($field)) {
            show_error("Mongo field is require to perform between and but not equal to query.", 500);
        }

        if (!isset($x)) {
            show_error("Mongo field's start value is require to perform between and but not equal to query.", 500);
        }

        if (!isset($y)) {
            show_error("Mongo field's end value is require to perform between and but not equal to query.", 500);
        }

        $this->_w($field);
        $this->filter[$field]['$gt'] = $x;
        $this->filter[$field]['$lt'] = $y;
        return ($this);
    }

    /**
     * --------------------------------------------------------------------------------
     * Where not equal
     * --------------------------------------------------------------------------------
     *
     * Get the documents where the value of a $field is not equal to $x
     *
     * @usage : $this->mongo_db->where_ne('foo', 1)->get('foobar');
     */
    public function where_ne($field = '', $x)
    {
        if (!isset($field)) {
            show_error("Mongo field is require to perform Where not equal to query.", 500);
        }

        if (!isset($x)) {
            show_error("Mongo field's value is require to perform Where not equal to query.", 500);
        }

        $this->_w($field);
        $this->filter[$field]['$ne'] = $x;
        return ($this);
    }

    /**
     * --------------------------------------------------------------------------------
     * Like
     * --------------------------------------------------------------------------------
     *
     * Get the documents where the (string) value of a $field is like a value. The defaults
     * allow for a case-insensitive search.
     *
     * @param $flags
     * Allows for the typical regular expression flags:
     * i = case insensitive
     * m = multiline
     * x = can contain comments
     * l = locale
     * s = dotall, "." matches everything, including newlines
     * u = match unicode
     *
     * @param $enable_start_wildcard
     * If set to anything other than TRUE, a starting line character "^" will be prepended
     * to the search value, representing only searching for a value at the start of
     * a new line.
     *
     * @param $enable_end_wildcard
     * If set to anything other than TRUE, an ending line character "$" will be appended
     * to the search value, representing only searching for a value at the end of
     * a line.
     *
     * @usage : $this->mongo_db->like('foo', 'bar', 'im', FALSE, TRUE);
     */
    public function like($field = "", $value = "", $flags = "i")
    {
        if (empty($field)) {
            show_error("Mongo field is require to perform like query.", 500);
        }

        if (empty($value)) {
            show_error("Mongo field's value is require to like query.", 500);
        }

        $field = (string)trim($field);
        $this->_w($field);
        $value = (string)trim($value);
        $this->filter[$field] = $this->get_regex($value, $flags);
        return ($this);
    }

    public function create_like($value = "", $flags = "i")
    {
        if (empty($value)) {
            show_error("Mongo field's value is require to like query.", 500);
        }
        $value = (string)trim($value);
        return $this->get_regex($value, $flags);
    }

    /**
     * --------------------------------------------------------------------------------
     * Count
     * --------------------------------------------------------------------------------
     *
     * Count the documents based upon the passed parameters
     *
     * @usage : $this->mongo_db->count('foo');
     */
    public function count($collection = "")
    {
        if (empty($collection)) {
            show_error("In order to retrieve a count of documents from MongoDB, a collection name must be passed", 500);
        }
        $count = $this->query_count($collection, $this->filter);
        $this->_clear();
        return ($count);
    }


    public function order_by($fields = array())
    {
        if(is_array($fields)) {
            foreach ($fields as $col => $val) {
                if ($val == -1 || $val === FALSE || strtolower($val) == 'desc') {
                    $this->option['sort'][$col] = -1;
                } else {
                    $this->option['sort'][$col] = 1;
                }
            }
        }
        return ($this);
    }

    public function limit($x = 99999)
    {
        if ($x !== NULL && is_numeric($x) && $x >= 1) {
            $this->option['limit'] = (int)$x;
        }
        return ($this);
    }

    public function offset($x = 0)
    {
        if ($x !== NULL && is_numeric($x) && $x >= 1) {
            $this->option['skip'] = (int)$x;
        }
        return ($this);
    }

    public function set_wheres($wheres)
    {
        if (is_array($wheres) && count($wheres) > 0) {
            $this->filter = $wheres;
        }
        return ($this);
    }

    public function set($fields, $value = NULL)
    {
        if (is_string($fields)) {
            $this->updates['$set'][$fields] = $value;
        } elseif (is_array($fields)) {
            foreach ($fields as $field => $value) {
                $this->updates['$set'][$field] = $value;
            }
        }
        return $this;
    }

    public function update_all($table, $option = [])
    {
        try {
            $bulk = new MongoDB\Driver\BulkWrite(['ordered' => true]);
            $this->option = array('multi' => true);
            $bulk->update($this->filter, $this->updates, $this->option);
            $table = $this->database . '.' . $table;
            $ret = $this->manager->executeBulkWrite($table, $bulk);
            $this->_clear();
            return $ret;
        } catch (Exception $e) {
            if (isset($this->debug) == TRUE && $this->debug == TRUE) {
                show_error('table: ' . $table . '. pipeline: ' . json_encode($this->filter) . '. MongoDB Exception: ' . $e->getMessage(), $e->getFile(), $e->getLine());
            } else {
                Throw new Exception("更新数据发生异常!");
            }
        }
    }


    /**
     * --------------------------------------------------------------------------------
     * _clear
     * --------------------------------------------------------------------------------
     *
     * Resets the class variables to default settings
     */
    private function _clear()
    {
        $this->filter = array();
        $this->option = array();
        $this->pipeline = array();
        $this->selects = array();
        $this->limit = 999999;
        $this->offset = 0;
    }

    /**
     * --------------------------------------------------------------------------------
     * Where initializer
     * --------------------------------------------------------------------------------
     *
     * Prepares parameters for insertion in $wheres array().
     */
    private function _w($param)
    {
        if (!isset($this->filter[$param])) {
            $this->filter[$param] = array();
        }
    }


    /**
     * 获取MongoDB ObjectID
     * @param $_id
     * @return \MongoDB\BSON\objectID
     */
    public function get_mongo_id($_id)
    {
        if (is_string($_id)) {
            try {
                $_id = new  MongoDB\BSON\objectID($_id);
            } catch (Exception $e) {
                if (isset($this->debug) == TRUE && $this->debug == TRUE) {
                    show_error('get_mongo_id:' . $_id . '. MongoDB Exception: ' . $e->getMessage(), $e->getFile(), $e->getLine());
                } else {
                    Throw new Exception("获取MongoDB ObjectID 发生异常!");
                }
            }
        }
        return $_id;
    }

    /**
     * ָ插入一或多条数据
     * @param string $table 数据库表单名称
     * @param array $data 需要插入的数据
     * @param bool $is_multi 是否批处理
     * @return bool ִ执行结果
     * @throws Exception
     */
    public function insert($table, $data, $is_multi = FALSE)
    {
        try {
            $bulk = new MongoDB\Driver\BulkWrite(['ordered' => true]);
            if (!$is_multi) {
                $bulk->insert($data);
            } else {
                foreach ($data as $one_data) {
                    $bulk->insert($one_data);
                }
            }
            $table = $this->database . '.' . $table;
            return $this->manager->executeBulkWrite($table, $bulk);
        } catch (Exception $e) {
            if (isset($this->debug) == TRUE && $this->debug == TRUE) {
                show_error('table: ' . $table . '. data: ' . json_encode($data) . '. MongoDB Exception: ' . $e->getMessage(), $e->getFile(), $e->getLine());
            } else {
                Throw new Exception("插入数据发生异常!");
            }
        }
    }


    /**
     * 获取单条数据库数据
     * @param string $table 数据库表单名称
     * @param array $filter 过滤条件
     * @param array $option 列、排序、分页等
     * @return objects 符合条件数据
     * @throws Exception
     */
    public function find_one($table)
    {
        if (empty($table)) {
            show_error("In order to retrieve documents from MongoDB, a collection name must be passed", 500);
        }
        try {
            $option = array('limit' => 1);
            $query = new MongoDB\Driver\Query($this->filter, $option);
            $table = $this->database . '.' . $table;
            $cursor = $this->manager->executeQuery($table, $query);
            $this->_clear();
            $return = $cursor->toArray();
            if ($return) {
                return $this->object_array($return[0]);
            } else {
                return null;
            }
        } catch (Exception $e) {
            if (isset($this->debug) == TRUE && $this->debug == TRUE) {
                show_error('table: ' . $table . '. filter: ' . json_encode($$this->filter) . '. MongoDB Exception: ' . $e->getMessage(), $e->getFile(), $e->getLine());
            } else {
                Throw new Exception("获取数据发生异常!");
            }
        }
    }

    /**
     * 将stdClass转换为array
     * @param $array
     * @return array
     */
    public function object_array($array)
    {
        if (is_object($array) && get_class($array) == "stdClass") {
            $array = (array)$array;
        }

        if(is_object($array) && get_class($array) == "MongoDB\BSON\ObjectID"){
            $array = (string)$array;
        }

        if (is_array($array)) {
            foreach ($array as $key => $value) {
                $array[$key] = $this->object_array($value);
            }
        }
        return $array;
    }

    public function set_pipeline($pipeline){
        $this->pipeline = $pipeline;
        return ($this);
    }

    public function lookup($lookup){
        if(is_array($lookup)){
            $this->pipeline['$lookup'] = $lookup;
        }
        return ($this);
    }


    /**
     * 聚合管道查询
     * @param string $table 数据库表单名称
     * @param array $pipeline 聚合管道操作
     * @return array
     * @throws Exception
     */
    public function aggregate($table, $group ,$project=NULL ,$is_cursor = FALSE)
    {
        try {
            $commands = array();
            $commands['aggregate'] = $table;
            if(is_array($project)) {
                $this->pipeline[] = array('$project'=>$project);
            }
            if(count($this->filter)>=1) {
                $this->pipeline[] = array('$match' => $this->filter);
            }
            if(array_key_exists("\$limit",$this->option)){
                $this->pipeline[] = array('$limit'=>$this->option['$limit']);
            }
            if(array_key_exists("\$skip",$this->option)){
                $this->pipeline[] = array('$skip'=>$this->option['$skip']);
            }
            if(is_array($group)) {
                $this->pipeline[] = array('$group'=>$group);
            }
            if(array_key_exists("sort",$this->option)){
                $this->pipeline[] = array('$sort'=>$this->option['sort']);
            }
            $commands['pipeline'] = $this->pipeline;
            $commands['allowDiskUse'] = true;
            if($this->db_version){
                $version = explode(".",$this->db_version);
                if($version[0] == 3 && $version[1]>2){
                    $is_cursor =TRUE;
                }
            }
            if ($is_cursor) {
                $commands['cursor'] = new stdClass;
            }
            $command = new MongoDB\Driver\Command($commands);
            $database_name = str_replace('.', '', $this->database);
            $cursor = $this->manager->executeCommand($database_name, $command);
            if ($is_cursor) {
                $response = [];
                foreach ($cursor as $document) {
                    $response[] = $document;
                }
            } else {
                $response = $cursor->toArray()[0]->result;
            }
            if (!empty($response)) {
                foreach ($response as &$value) {
                    $value = (array)$value;
                }
            }
            $this->_clear();
            return $this->object_array($response);
        } catch (Exception $e) {
            if (isset($this->debug) == TRUE && $this->debug == TRUE) {
                show_error('table: ' . $table . '. pipeline: ' . json_encode($this->pipeline) . '. MongoDB Exception: ' . $e->getMessage(), $e->getFile(), $e->getLine());
            } else {
                Throw new Exception("聚合查询发生异常!");
            }
        }
    }


    /**
     * 创建(多个)索引
     * @param $table string 表名
     * @param array $indexes , 例:
     * [
     *     [
     *         'key' => [
     *             'update_time' => 1       //要索引的字段, 升序还是降序
     *         ],
     *         'name' => 'update_time',     //索引名称
     *         'expireAfterSeconds' => 600  //可选, 索引参数, 这里的expireAfterSeconds表示这是一个ttl索引, 将在600s后被自动删除
     *     ],
     * ]
     * @throws Exception
     */
    public function create_index($table, array $indexes)
    {
        $cmd = [
            'createIndexes' => $table,
            'indexes' => $indexes,
        ];

        $command = new MongoDB\Driver\Command($cmd);
        try {
            $this->manager->executeCommand($this->database, $command);
        } catch (Exception $e) {
            if (isset($this->debug) == TRUE && $this->debug == TRUE) {
                log_message(ERROR_LEVE, 'table: ' . $table . '. pipeline: ' . json_encode($cmd) . '. MongoDB Exception: ' . $e->getMessage(), $e->getFile(), $e->getLine());
            } else {
                Throw new Exception("创建索引发生异常!");
            }
        }
    }

    /**
     * 更新指定记录
     * @param string $table 数据库表单名称
     * @param array $data 数据记录
     * @param array $option 参数选项
     * @param bool $is_multi 是否同时更新多条记录
     * @return \MongoDB\Driver\WriteResult
     * @throws Exception
     */
    public function update($table, $data, $option = [], $is_multi = FALSE)
    {
        try {
            $bulk = new MongoDB\Driver\BulkWrite(['ordered' => true]);
            if (!$is_multi) {
                if(array_key_exists('_id',$data['update'])) {
                    $data['update']['_id'] = $this->get_mongo_id($data['update']['_id']);
                }
                $bulk->update($data['filter'], array('$set' => $data['update']), $option);
            } else {
                foreach ($data as $one_data) {
                    $bulk->update($one_data['filter'], array('$set' => $data['update']), $option);
                }
            }
            $table = $this->database . '.' . $table;
            $ret = $this->manager->executeBulkWrite($table, $bulk);
            $this->_clear();
            return $ret;
        } catch (Exception $e) {
            if (isset($this->debug) == TRUE && $this->debug == TRUE) {
                show_error('table: ' . $table . '. pipeline: ' . json_encode($data) . '. MongoDB Exception: ' . $e->getMessage(), $e->getFile(), $e->getLine());
            } else {
                Throw new Exception("更新数据发生异常!");
            }
        }
    }

    /**
     *
     * 删除记录
     * @param string $table 数据库表单名称
     * @param array $filter 更新的数据
     * @param bool $is_multi 是否同时删除多条记录
     * @param array $opinion mongo语句参数
     * @return \MongoDB\Driver\WriteResult
     * @throws Exception
     */
    public function delete($table)
    {
        try {
            $bulk = new MongoDB\Driver\BulkWrite(['ordered' => true]);
            $bulk->delete($this->filter, $this->option);
            $table = $this->database . '.' . $table;
            $ret = $this->manager->executeBulkWrite($table, $bulk);
            $this->_clear();
            return $ret;
        } catch (Exception $e) {
            if (isset($this->debug) == TRUE && $this->debug == TRUE) {
                show_error('table: ' . $table . '. pipeline: ' . json_encode($this->filter) . '. MongoDB Exception: ' . $e->getMessage(), $e->getFile(), $e->getLine());
            } else {
                Throw new Exception("删除数据发生异常!");
            }
        }
    }

    public function delete_all($table){
        try {
            $bulk = new MongoDB\Driver\BulkWrite(['ordered' => true]);
            $bulk->delete($this->filter, $this->option);
            $table = $this->database . '.' . $table;
            $ret = $this->manager->executeBulkWrite($table, $bulk);
            $this->_clear();
            return $ret;
        } catch (Exception $e) {
            if (isset($this->debug) == TRUE && $this->debug == TRUE) {
                show_error('table: ' . $table . '. pipeline: ' . json_encode($this->filter) . '. MongoDB Exception: ' . $e->getMessage(), $e->getFile(), $e->getLine());
            } else {
                Throw new Exception("删除数据发生异常!");
            }
        }
    }

    /**
     * 获取正则表达式
     * @param $pattern
     * @param string $flags
     * @param bool $preg 是否转义
     * @return \MongoDB\BSON\Regex
     * @throws Exception
     */
    public function get_regex($pattern, $flags = 'i', $preg = true)
    {
        try {
            #TD82216
            if ($preg) {
                $pattern = preg_quote($pattern);
            }
            return new MongoDB\BSON\Regex($pattern, $flags);
        } catch (Exception $e) {
            if (isset($this->debug) == TRUE && $this->debug == TRUE) {
                show_error('pattern: ' . $pattern . '. MongoDB Exception: ' . $e->getMessage(), $e->getFile(), $e->getLine());
            } else {
                Throw new Exception("获取正则表达式发生异常!");
            }
        }
    }

    /**
     * 获取字段不同值的个数
     * @param string $table 表的名称
     * @param string $key 字段
     * @param array $filter 过滤条件
     * @return int 数值
     * @throws Exception
     */
    public function query_distinct_count($table, $key, $filter)
    {
        try {
            $param['distinct'] = $table;
            $param['key'] = $key;
            $param['query'] = $filter;
            $command = new MongoDB\Driver\Command($param);
            $cursor = $this->manager->executeCommand($this->database, $command);
            $result = $cursor->toArray();
            if (empty($result)) {
                return 0;
            }
            return count($result[0]->values);
        } catch (Exception $e) {
            if (isset($this->debug) == TRUE && $this->debug == TRUE) {
                log_message(ERROR_LEVE, 'pattern: ' . $table . ' key: ' . $key . '. MongoDB Exception: ' . $e->getMessage(), $e->getFile(), $e->getLine());
            } else {
                Throw new Exception("获取指定字段的数量发生异常!");
            }
        }
    }

    /*
    * 获取数据查询记录数
    * @param string $table 数据库表单名称
    * @param array $filter 数据查询过滤条件
    * @return int 查询记录数
    * @throws Exception 数据库查询异常
    */
    public function query_nodistinct_count($table, $filter)
    {
        $param['aggregate'] = $table;
        $param['pipeline'] = array(
            array('$match' => $filter),
            array('$group' => array(
                '_id' => null,
                'sum' => array(
                    '$sum' => 1
                )
            )
            )
        );

        try {
            $param['cursor'] = new stdClass();
            $command = new MongoDB\Driver\Command($param);
            $cursor = $this->manager->executeCommand($this->database, $command);
        } catch (Exception $e) {
            $err_msg = sprintf("MongoDB Exception: %s", $e->getMessage());
            if (isset($this->debug) == TRUE && $this->debug == TRUE) {
                show_error($err_msg . $e->getFile() . $e->getLine());
            } else {
                throw new Exception("获取数据查询记录数发生异常!");
            }
        }
        $result = $cursor->toArray();
        if (empty($result)) {
            return 0;
        }

        return $result[0]->sum;
    }

    /**
     * 获取所有的记录数
     * @param string $table 表的名称
     * @param array $filter 过滤条件
     * @return int
     * @throws Exception
     */
    public function query_count($table, $filter)
    {
        $param['count'] = $table;
        $param['query'] = $filter;
        $command = new MongoDB\Driver\Command($param);
        try {
            $cursor = $this->manager->executeCommand($this->database, $command);
        } catch (Exception $e) {
            $err_msg = sprintf("MongoDB Exception: %s", $e->getMessage());
            if (isset($this->debug) == TRUE && $this->debug == TRUE) {
                show_error($err_msg . $e->getFile() . $e->getLine());
            } else {
                throw new Exception("获取所有的记录数发生异常!");
            }
        }
        $result = current($cursor->toArray());
        // Older server versions may return a float
        if (!isset($result->n) || !(is_integer($result->n) || is_float($result->n))) {
            throw new UnexpectedValueException('count command did not return a numeric "n" value');
        }

        return (integer)$result->n;

    }
}