<?php
 use TCMS\TException;
 class MongoDb {
 private $conn = null;
 public $_table = null;
 public $_columns = null;
 public $_where = null;
 public $_order = null;
 public $_limit = null;
 public $_offset = null;
 public $_join = null;
 public $_isDistinct = false;
 public $_isDebug = false;
 public function __construct($conn, $table) {
 $this->_table = $table;
 $this->conn = $conn;
 }
 public function enableDebug() {
 $this->_isDebug = true;
 return $this;
 }
 public function query($query, array $prepareData = null) {
 if (empty($query)) return $this;
 $this->_query = $query;
 if (empty($prepareData) === false) $this->_prepareData = $prepareData;
 return $this;
 }
 public function getCollection() {
 return $this->conn->selectCollection($this->_table);
 }
 public function toArray() {
 if ($this->_join) {
 return $this->toJoinData(true);
 }
 $options = $this->buildQueryOption();
 $filters = $this->buildFilter();
 $collection = $this->conn->selectCollection($this->_table);
 $data = $collection->find($filters, $options);
 $retVal = [];
 foreach ($data as $v) {
 $arr = [];
 foreach ($v as $k => $vv) {
 if ($k == '_id' || $k == 'id') {
 $arr['id'] = $vv . '';
 }
 else {
 $arr[$k] = $vv;
 }
 }
 $retVal[] = $arr;
 }
 return $retVal;
 }
 private function buildJoin() {
 $joins = [];
 foreach ($this->_join as $join) {
 $ccl = $join['cc'];
 if ($ccl == 'id') $ccl = '_id';
 $jcl = $join['jc'];
 if ($jcl == 'id') $jcl = '_id';
 $joins[] = [ 'from' => $join['tb'], 'localField' => $ccl, 'foreignField' => $jcl, 'as' => 'join_table' ];
 }
 return $joins;
 }
 private function appendJoin(&$filters, $joins, &$cols) {
 if (!$filters) $filters = [];
 else {
 $f2 = $filters;
 $filters = [];
 $filters['$match'] = $f2;
 }
 foreach ($joins as $k => $join) {
 if ($join['foreignField'] == '_id' && $join['localField'] != '_id') {
 $filters[]['$addFields'] = [ $join['localField'] . 'oid' => [ '$toObjectId' => '$' . $join['localField'] ] ];
 $join['localField'] = $join['localField'] . 'oid';
 }
 $join['as'] = 'jbt_' . $join['from'];
 $filters[]['$lookup'] = $join;
 }
 if ($cols && $cols != '*') {
 $retCol = [];
 foreach ($joins as $k => $join) {
 $tbL = strtolower($join['from'] . '.');
 foreach ($cols as $cls) {
 $cl = $this->parseColumn($cls);
 $startIndex = strpos(strtolower($cl), $tbL);
 if ($startIndex !== false) {
 $clName = trim(substr($cl, $startIndex + strlen($tbL)));
 $spaIndex = strpos($clName, ' ');
 $vIndex = strrpos($clName, ' ');
 $name = trim(substr($clName, $vIndex));
 if ($name != '*') $retCol[$join['from']][trim(substr($clName, 0, $spaIndex))] = $name;
 else $retCol[$join['from']]['*'] = true;
 }
 }
 }
 $cols = $retCol;
 }
 else {
 $cols = [];
 }
 }
 private function toJoinData($isArray) {
 $join = $this->buildJoin();
 $cols = $this->_columns;
 $options = $this->buildQueryOption(true, true);
 $filters = $this->buildFilter();
 $collection = $this->conn->selectCollection($this->_table);
 $this->appendJoin($filters, $join, $cols);
 if ($options) {
 if ($isArray) {
 $f = [];
 foreach ($options as $k => $v) {
 if ($k == '$projection') $k = '$project';
 $f[][$k] = $v;
 }
 foreach ($filters as $k => $v) {
 if (is_array($v) && array_key_exists('$addFields', $v)) {
 $f[]['$addFields'] = $v['$addFields'];
 }
 else {
 if (is_array($v) && array_key_exists('$lookup', $v)) {
 $f[]['$lookup'] = $v['$lookup'];
 }
 else {
 $f[][$k] = $v;
 }
 }
 }
 $filters = $f;
 }
 else {
 $f = [];
 if (array_key_exists('$match', $filters)) {
 $f[]['$match'] = $filters['$match'];
 unset($filters['$match']);
 }
 foreach ($filters as $k => $v) {
 if (is_array($v) && array_key_exists('$addFields', $v)) {
 $f[]['$addFields'] = $v['$addFields'];
 }
 else {
 if (is_array($v) && array_key_exists('$lookup', $v)) {
 $f[]['$lookup'] = $v['$lookup'];
 }
 else {
 $f[][$k] = $v;
 }
 }
 }
 foreach ($options as $k => $v) {
 if ($k == '$projection') $k = '$project';
 $f[][$k] = $v;
 }
 $filters = $f;
 }
 }
 else {
 $f = [];
 foreach ($filters as $k => $v) {
 if (is_array($v) && array_key_exists('$addFields', $v)) {
 $f[]['$addFields'] = $v['$addFields'];
 }
 else {
 if (is_array($v) && array_key_exists('$lookup', $v)) {
 $f[]['$lookup'] = $v['$lookup'];
 }
 else {
 $f[][$k] = $v;
 }
 }
 }
 $filters = $f;
 }
 $data = $collection->aggregate($filters, [])->toArray();
 $retVal = [];
 foreach ($data as $v) {
 if ($isArray) {
 $arr = [];
 foreach ($v as $k => $vv) {
 if (strpos($k, 'jbt_') === 0) {
 $tbName = substr($k, 4);
 $vvv = $vv->jsonSerialize();
 foreach ($vvv as $kkk => $vvvv) {
 $vvvvv = $vvvv->jsonSerialize();
 foreach ($vvvvv as $kkkkk => $vvvvvv) {
 if ($cols && empty($cols[$tbName][$kkkkk]) && !empty($cols[$tbName]['*'])) {
 continue;
 }
 if ($kkkkk == '_id') {
 if (!empty($cols[$tbName][$kkkkk])) {
 $kkClk = $cols[$tbName][$kkkkk];
 }
 else {
 $kkClk = $kkkkk;
 }
 if (!isset($arr[$kkClk])) {
 $arr[$kkClk] = $vvvvvv . '';
 }
 else {
 $kza = ($tbName . '.' . $kkClk);
 $arr[$kza] = $vvvvvv . '';
 }
 }
 else {
 if (!empty($cols[$tbName][$kkkkk])) {
 $kkClk = $cols[$tbName][$kkkkk];
 }
 else {
 $kkClk = $kkkkk;
 }
 if (!isset($arr[$kkClk])) {
 $arr[$kkClk] = $vvvvvv;
 }
 else {
 $kza = ($tbName . '.' . $kkClk);
 $arr[$kza] = $vvvvvv;
 }
 }
 }
 }
 continue;
 }
 if ($k == '_id') {
 $arr['id'] = $vv . '';
 }
 else {
 $arr[$k] = $vv;
 }
 }
 $retVal[] = $arr;
 }
 else {
 $isEmpty = false;
 $stdClass = new \stdClass();
 foreach ($v as $k => $vv) {
 if (strpos($k, "jbt_") === 0) {
 $vvv = $vv->jsonSerialize();
 if (!count($vvv)) {
 $isEmpty = true;
 break;
 }
 $stdClass->$k = [];
 foreach ($vvv as $kkk => $vvvv) {
 $vvvvv = $vvvv->jsonSerialize();
 $stdClass->$k[] = $vvvvv;
 }
 continue;
 }
 if ($k == '_id') {
 $stdClass->id = $vv . '';
 }
 else {
 $stdClass->$k = $vv;
 }
 }
 if (!$isEmpty) $retVal[] = $stdClass;
 }
 }
 return $retVal;
 }
 public function toList() {
 if ($this->_join) {
 return $this->toJoinData(false);
 }
 $options = $this->buildQueryOption();
 $filters = $this->buildFilter();
 $collection = $this->conn->selectCollection($this->_table);
 $data = $collection->find($filters, $options);
 $retVal = [];
 foreach ($data as $v) {
 $stdClass = new \stdClass();
 foreach ($v as $k => $vv) {
 if ($k == '_id') {
 $stdClass->id = $vv . '';
 }
 else {
 $stdClass->$k = $vv;
 }
 }
 $retVal[] = $stdClass;
 }
 return $retVal;
 }
 public function first() {
 $this->limit(1);
 if ($this->_join) {
 $ret = $this->toJoinData(false);
 if ($ret) return $ret[0];
 return false;
 }
 $options = $this->buildQueryOption(false);
 $filters = $this->buildFilter();
 $collection = $this->conn->selectCollection($this->_table);
 $data = $collection->findOne($filters, $options);
 if ($data) {
 $stdClass = new \stdClass();
 foreach ($data as $k => $vv) {
 if ($k == '_id') {
 $stdClass->id = $vv . '';
 }
 else {
 $stdClass->$k = $vv;
 }
 }
 return $stdClass;
 }
 return false;
 }
 public function find($id, string $column = 'id') {
 if ($column == 'id') $column = '_id';
 $this->setWhere($column, $id);
 $this->limit(1);
 if ($this->_join) {
 $ret = $this->toJoinData(false);
 if ($ret) return $ret[0];
 return false;
 }
 $options = $this->buildQueryOption(false);
 $filters = $this->buildFilter();
 $collection = $this->conn->selectCollection($this->_table);
 $data = $collection->findOne($filters, $options);
 if ($data) {
 $stdClass = new \stdClass();
 foreach ($data as $k => $vv) {
 if ($k == '_id') {
 $stdClass->id = $vv . '';
 }
 else {
 $stdClass->$k = $vv;
 }
 }
 return $stdClass;
 }
 return false;
 }
 public function findArray($id, string $column = 'id') {
 if ($column == 'id') $column = '_id';
 $this->setWhere($column, $id);
 $this->limit(1);
 if ($this->_join) {
 $ret = $this->toJoinData(true);
 if ($ret) return $ret[0];
 return false;
 }
 $options = $this->buildQueryOption(false);
 $filters = $this->buildFilter();
 $collection = $this->conn->selectCollection($this->_table);
 $data = $collection->findOne($filters, $options);
 if ($data) {
 $arr = [];
 foreach ($data as $k => $vv) {
 if ($k == '_id') {
 $arr[$k] = $vv . '';
 }
 else {
 $arr[$k] = $vv;
 }
 }
 return $arr;
 }
 return false;
 }
 public function count($column = '*', bool $isRelease = true) {
 if (is_string($column)) {
 if ($column != '*') $col = $column;
 else $col = $column;
 }
 else {
 $col = '*';
 $isRelease = $column;
 }
 $options = $this->buildQueryOption();
 $filters = $this->buildFilter($isRelease);
 $collection = $this->conn->selectCollection($this->_table);
 $c = $collection->count($filters, $options);
 return $c;
 }
 public function sum(string $column = 'id', bool $isRelease = true) {
 if ($column == 'id') $column = '_id';
 $options = $this->buildQueryOption();
 $filters = $this->buildFilter($isRelease);
 $collection = $this->conn->selectCollection($this->_table);
 $data = $collection->find($filters, $options);
 $sum = 0;
 foreach ($data as $v) {
 foreach ($v as $k => $vv) {
 if ($k == $column) {
 $sum += (int) $vv;
 }
 }
 }
 return $sum;
 }
 public function max(string $column = 'id', bool $isRelease = true) {
 if ($column == 'id') $column = '_id';
 $this->limit(1);
 $this->orderByDesc($column);
 $options = $this->buildQueryOption(false);
 $filters = $this->buildFilter($isRelease);
 $collection = $this->conn->selectCollection($this->_table);
 $data = $collection->findOne($filters, $options);
 if ($data) {
 foreach ($data as $k => $vv) {
 if ($k == $column) return $vv;
 }
 }
 return false;
 }
 public function avg($column = 'id', bool $isRelease = true) {
 if ($column == 'id') $column = '_id';
 $options = $this->buildQueryOption();
 $filters = $this->buildFilter($isRelease);
 $collection = $this->conn->selectCollection($this->_table);
 $data = $collection->find($filters, $options);
 $sum = 0;
 foreach ($data as $v) {
 foreach ($v as $k => $vv) {
 if ($k == $column) {
 $sum += (int) $vv;
 }
 }
 }
 return $sum / 2;
 }
 public function limit($limit, $offset = 0) {
 if (is_integer($limit) === false || is_integer($offset) === false) {
 return $this;
 }
 $this->_limit = $limit;
 $this->_offset = $offset;
 return $this;
 }
 public function orderBy(string $column) {
 if (is_string($column) === false) {
 throw new TException("$column phải là string", 500);
 }
 if ($column == 'id') $column = '_id';
 if ($this->_order == null) {
 $this->_order = [];
 }
 $this->_order[$column] = 1;
 return $this;
 }
 public function orderByDesc($column) {
 if (is_string($column) === false) {
 throw new TException("$column phải là string", 500);
 }
 if ($column == 'id') $column = '_id';
 if ($this->_order == null) {
 $this->_order = [];
 }
 $this->_order[$column] = -1;
 return $this;
 }
 public function distinct() {
 throw new TException("mongodb không hỗ trợ distinct", 500);
 }
 public function select() {
 $args = func_get_args();
 if (count($args) == 0) return $this;
 $this->_columns = [];
 foreach ($args as $arg) {
 if (is_array($arg)) {
 foreach ($arg as $k => $v) {
 if ($v == 'id' || $v == '_id') continue;
 $this->_columns[] = $v == 'id' ? '_id' : $v;
 }
 }
 else {
 if ($arg == 'id' || $arg == '_id') continue;
 $this->_columns[] = $arg == 'id' ? '_id' : $arg;
 }
 }
 return $this;
 }
 public function selectCol($cols) {
 $this->_columns = [];
 foreach ($cols as $col) {
 if ($col == 'id' || $col == '_id') continue;
 $this->_columns[] = $col == 'id' ? '_id' : $col;
 }
 return $this;
 }
 public function where($column, $value = null, $operator = '=') {
 $this->setWhere($column, $value, $operator, 'AND');
 return $this;
 }
 public function orWhere($column, $value = null, $operator = '=') {
 $this->setWhere($column, $value, $operator, 'OR');
 return $this;
 }
 public function whereNotNull(string $column, string $type = 'AND') {
 $this->setWhere($column, null, null, $type, 2);
 return $this;
 }
 public function whereNull(string $column, string $type = 'AND') {
 $this->setWhere($column, null, null, $type, 1);
 return $this;
 }
 public function whereIn(string $column, array $value) {
 if ($column == 'id') $column = '_id';
 $val = [];
 foreach ($value as $k => $v) {
 if ($column == '_id') $v = new \MongoDB\BSON\ObjectID($v);
 $val[] = $this->parseWhereValue($v, true);
 }
 if (count($val) == 0) return $this;
 $this->setWhere($column, $val, 'IN', 'AND');
 return $this;
 }
 public function orWhereIn(string $column, array $value) {
 if ($column == 'id') $column = '_id';
 $val = [];
 foreach ($value as $k => $v) {
 $val[] = $this->parseWhereValue($v, true);
 }
 if (count($val) == 0) return $this;
 $this->setWhere($column, $val, 'IN', 'OR');
 return $this;
 }
 public function like($column, $value = null) {
 $isStartWith = false;
 $isEndWith = false;
 if ($value && strpos($value, '%') === 0) {
 $value = substr($value, 1);
 $isStartWith = true;
 }
 if ($value && strpos($value, '%') === strlen($value) - 1) {
 $value = substr($value, 0, strlen($value) - 1);
 $isEndWith = true;
 }
 $lType = 'l1';
 if ($isStartWith && $isEndWith) {
 $lType = 'l1';
 }
 else if ($isStartWith) {
 $lType = 'l2';
 }
 else if ($isEndWith) {
 $lType = 'l3';
 }
 $this->setWhere($column, $value, null, 'AND', null, $lType);
 return $this;
 }
 public function orLike($column, $value = null) {
 $isStartWith = false;
 $isEndWith = false;
 if ($value && strpos($value, '%') === 0) {
 $value = substr($value, 1);
 $isStartWith = true;
 }
 if ($value && strpos($value, '%') === strlen($value) - 1) {
 $value = substr($value, 0, strlen($value) - 1);
 $isEndWith = true;
 }
 $lType = 'l1';
 if ($isStartWith && $isEndWith) {
 $lType = 'l1';
 }
 else if ($isStartWith) {
 $lType = 'l2';
 }
 else if ($isEndWith) {
 $lType = 'l3';
 }
 $this->setWhere($column, $value, null, 'OR', null, $lType);
 return $this;
 }
 private function setWhere($column, $value = null, $operator = '=', $type = 'AND', $null = null, $isLike = null) {
 if (isset($column) == false || $column == null) throw new TException("Vui lòng nhập column khi gọi lệnh where", 500);
 if ($isLike) {
 $operator = $isLike;
 }
 if (is_array($column)) {
 if ($this->_where == null) $this->_where = [];
 if ($value != null && is_bool($value)) {
 $this->_where[] = [ 'group' => true ];
 }
 foreach ($column as $key => $val) {
 if ($key == 'id') $key = '_id';
 if ($key == '_id' && $val) {
 $val = new \MongoDB\BSON\ObjectID($val);
 }
 $this->_where[] = [ 'column' => $key, 'value' => $val, 'type' => $type, 'o' => $operator, 'null' => $null ];
 }
 if ($value != null && is_bool($value)) {
 $this->_where[] = [ 'group' => false ];
 }
 return $this;
 }
 if ($value === null) {
 if ($column instanceof DbRaw || ($null != null && is_integer($null))) {
 }
 else {
 throw new TException("column phải là Db::raw() hoặc array", 500);
 }
 }
 if ($column == 'id') $column = '_id';
 if ($column == '_id' && $value && is_string($value)) {
 $value = new \MongoDB\BSON\ObjectID($value);
 }
 if ($this->_where == null) $this->_where = [];
 $this->_where[] = [ 'column' => $column, 'value' => $value, 'type' => $type, 'o' => $operator, 'null' => $null ];
 }
 public function beginGroup() {
 throw new TException("MongoDB không hỗ trợ group");
 }
 public function endGroup() {
 throw new TException("MongoDB không hỗ trợ group");
 }
 public function join(string $table, string $curTableColumn, string $joinTableColumn) {
 $this->setJoin($table, $curTableColumn, $joinTableColumn, 1);
 return $this;
 }
 public function outerJoin(string $table, string $curTableColumn, string $joinTableColumn) {
 throw new TException("MongoDB không hỗ trợ outer join");
 }
 public function leftJoin(string $table, string $curTableColumn, string $joinTableColumn) {
 throw new TException("MongoDB không hỗ trợ left join");
 }
 public function rightJoin(string $table, string $curTableColumn, string $joinTableColumn) {
 throw new TException("MongoDB không hỗ trợ right join");
 }
 private function setJoin(string $table, string $curTableColumn, string $joinTableColumn, string $joinType) {
 if (!$this->_join) $this->_join = [];
 $this->_join[] = [ 'tb' => $table, 'cc' => $curTableColumn == 'id' ? '_id' : $curTableColumn, 'jc' => $joinTableColumn == 'id' ? '_id' : $joinTableColumn, 't' => $joinType ];
 }
 private function buildFilter($isReleaseData = true) {
 try {
 if (isset($this->_query)) {
 return $this->_query;
 }
 $filters = [];
 if ($this->_where != null && count($this->_where) > 0) {
 $length = count($this->_where);
 if ($length == 1) {
 $filters = $this->buildOneFilter($this->_where[0], []);
 }
 else if ($length == 2) {
 if ($this->_where[1]['type'] == 'AND') {
 $filters['$and'] = [];
 $filters['$and'][] = $this->buildOneFilter($this->_where[0], []);
 $filters['$and'][] = $this->buildOneFilter($this->_where[1], []);
 }
 else {
 $filters['$or'] = [];
 $filters['$or'][] = $this->buildOneFilter($this->_where[0], []);
 $filters['$or'][] = $this->buildOneFilter($this->_where[1], []);
 }
 }
 else if ($length >= 3) {
 $hasOr = false;
 foreach ($this->_where as $kcs => $vcs) {
 if ($vcs['type'] == 'OR') {
 $hasOr = true;
 break;
 }
 }
 if ($hasOr) {
 foreach ($this->_where as $kcs => $vcs) {
 if ($vcs['type'] == 'AND') {
 $filters[] = $this->buildOneFilter($vcs, []);
 }
 }
 $filters['$or'] = [];
 foreach ($this->_where as $kcs => $vcs) {
 if ($vcs['type'] == 'OR') {
 $filters['$or'][] = $this->buildOneFilter($vcs, []);
 }
 }
 }
 else {
 $filters['$and'] = [];
 foreach ($this->_where as $kcs => $vcs) {
 $filters['$and'][] = $this->buildOneFilter($vcs, []);
 }
 }
 }
 }
 if ($this->_isDebug) {
 print_r($filters);
 }
 if ($isReleaseData) {
 $this->releaseData();
 }
 return $filters;
 }
 catch (PDOException $e) {
 throw new TException($e->getMessage(), 500);
 }
 }
 private function buildQueryOption($isBuildLimit = true, $addDola = false) {
 $option = [];
 if ($this->_order !== null) {
 foreach ($this->_order as $k => $o) {
 $option[$addDola ? '$sort' : 'sort'][$k] = $o;
 }
 }
 if ($this->_columns !== null && count($this->_columns) > 0) {
 $option[$addDola ? '$projection' : 'projection'] = [];
 foreach ($this->_columns as $k => $o) {
 $option[$addDola ? '$projection' : 'projection'][$o] = 1;
 }
 }
 if ($isBuildLimit) {
 if ($this->_limit !== null) {
 $option[$addDola ? '$limit' : 'limit'] = $this->_limit;
 }
 if ($this->_offset !== null && $this->_offset !== 0) {
 $option[$addDola ? '$skip' : 'skip'] = $this->_offset;
 }
 }
 if ($this->_isDebug) {
 print_r($option);
 }
 return $option;
 }
 private function buildOneFilter($w, $filters) {
 $cln = $w['column'];
 if ($cln == 'id') $cln = '_id';
 $v = $w['value'];
 if ($v) {
 $v = $this->parseWhereValue($v);
 $o = $w['o'];
 if ($o == '>') {
 $filters[$cln] = ['$gt' => $w['value']];
 }
 else if ($o == '<') {
 $filters[$cln] = ['$lt' => $w['value']];
 }
 else if ($o == '=') {
 $filters[$cln] = $w['value'];
 }
 else if ($o == '<>') {
 $filters[$cln] = ['$ne' => $w['value']];
 }
 else if ($o == 'l1') {
 $filters[$cln] = new MongoDB\BSON\Regex($w['value'], 'iu');
 }
 else if ($o == 'l2') {
 $filters[$cln] = new MongoDB\BSON\Regex($w["value"] . '$', 'iu');
 }
 else if ($o == 'l3') {
 $filters[$cln] = new MongoDB\BSON\Regex('^' . $w["value"], 'iu');
 }
 else if ($o == 'IN') {
 $filters[$cln] = ['$in' => $w['value']];
 }
 else if ($o == '<=') {
 $filters[$cln] = ['$lte' => $w['value']];
 }
 else if ($o == '>=') {
 $filters[$cln] = ['$gte' => $w['value']];
 }
 }
 else {
 if ($w['null'] === 1) {
 $filters[$cln] = null;
 }
 else if ($w['null'] === 2) {
 $filters[$cln] = ['$ne' => null];
 }
 else {
 $filters[$cln] = $w['value'];
 }
 }
 return $filters;
 }
 public function insert($data) {
 if ($data == null) throw new TException("Vui lòng truyền dữ liệu cho lệnh insert", 500);
 $prepareData = [];
 foreach ($data as $k => $v) {
 if ($k == 'id' || $k == '_id') {
 $prepareData['_id'] = new \MongoDB\BSON\ObjectID($v);
 }
 else {
 $prepareData[$k] = $this->parseWhereValue($v);
 }
 }
 $collection = $this->conn->selectCollection($this->_table);
 $ret = $collection->insertOne($prepareData);
 if ($ret->getInsertedCount() > 0) {
 return $ret->getInsertedId() . '';
 }
 return false;
 }
 public function inserts(array $data) {
 if ($data == null) throw new TException("Vui lòng truyền dữ liệu cho lệnh inserts", 500);
 $prepareData = [];
 foreach ($data as $k => $v) {
 $d = [];
 foreach ($v as $kk => $vv) {
 if ($kk == 'id' || $kk == '_id') {
 $d['_id'] = new \MongoDB\BSON\ObjectID($vv);
 }
 else {
 $d[$kk] = $this->parseWhereValue($vv);
 }
 }
 $prepareData[] = $d;
 }
 $collection = $this->conn->selectCollection($this->_table);
 $ret = $collection->insertMany($prepareData);
 if ($ret->getInsertedCount() > 0) {
 $insertIds = [];
 $ret = $ret->getInsertedIds();
 foreach ($ret as $v) {
 $insertIds[] = $v . '';
 }
 return $insertIds;
 }
 return false;
 }
 public function update($data) {
 if ($data == null) throw new TException("Vui lòng truyền dữ liệu cho lệnh update", 500);
 $_prepareData = [];
 $isHasData = false;
 foreach ($data as $k => $v) {
 if ($k == '_id' || $k == 'id') {
 if ($this->_where == null || count($this->_where) == 0) {
 $this->where('_id', new \MongoDB\BSON\ObjectID($v));
 }
 continue;
 }
 $_prepareData[$k] = $this->parseWhereValue($v);
 $isHasData = true;
 }
 if ($isHasData === false) throw new TException("Vui lòng truyền dữ liệu cho lệnh update", 500);
 $filters = $this->buildFilter();
 $collection = $this->conn->selectCollection($this->_table);
 $ret = $collection->updateMany($filters, ['$set' => $_prepareData]);
 return $ret->getMatchedCount() > 0;
 }
 public function delete($id = null, $column = 'id') {
 if ($column == 'id') $column = '_id';
 if ($id) {
 $this->setWhere($column, $id);
 }
 $filters = $this->buildFilter();
 if (!$filters) {
 throw new TException("Vui lòng truyền dữ liệu cho lệnh delete", 500);
 }
 $collection = $this->conn->selectCollection($this->_table);
 $ret = $collection->deleteOne($filters);
 return $ret->getDeletedCount();
 }
 public function deletes(array $ids, $column = 'id') {
 if (count($ids) == 0) throw new TException("Vui lòng truyền dữ liệu cho lệnh deletes", 500);
 if ($column == 'id') $column = '_id';
 $this->whereIn($column, $ids);
 $filters = $this->buildFilter();
 if (!$filters) {
 throw new TException("Vui lòng truyền dữ liệu cho lệnh delete", 500);
 }
 $collection = $this->conn->selectCollection($this->_table);
 $ret = $collection->deleteMany($filters);
 return $ret->getDeletedCount();
 }
 public function beginTransaction() {
 throw new TException("Mongodb không hỗ trợ transaction", 501);
 }
 public function commitTransaction() {
 throw new TException("Mongodb không hỗ trợ transaction", 501);
 }
 public function rollBackTransaction() {
 throw new TException("Mongodb không hỗ trợ transaction", 501);
 }
 public function exec() {
 throw new TException("mongodb không hỗ trợ exec", 500);
 }
 private function releaseData() {
 $this->_columns = null;
 $this->_where = null;
 $this->_order = null;
 $this->_limit = null;
 $this->_join = null;
 $this->_isDistinct = false;
 $this->_isDebug = false;
 if (isset($this->_query)) {
 unset($this->_query);
 unset($this->_prepareData);
 }
 }
 private function parseColumn($column) {
 if (!is_string($column)) {
 if ($column instanceof \DbRaw) return $column . '';
 }
 return $column;
 }
 private function parseWhereValue($value) {
 if (is_string($value)) return $value;
 if (is_numeric($value)) return $value;
 if ($value instanceof \DateTime) {
 return $value->format('Y-m-d H:i:s');
 }
 return $value;
 }
 private function parseWhere(&$query) {
 $prepareData = [];
 if ($this->_where != null && count($this->_where) > 0) {
 $query .= ' WHERE ';
 $isTheFirst = true;
 $isNeedOpenGroup = false;
 foreach ($this->_where as $key => $value) {
 if (array_key_exists('group', $value)) {
 if ($value['group']) {
 if ($isTheFirst) {
 $query .= ' (';
 }
 else {
 $isNeedOpenGroup = true;
 }
 }
 else {
 $query .= ')';
 }
 continue;
 }
 if (is_string($value['column'])) {
 $col = $this->parseColumn($value['column']);
 if ($isNeedOpenGroup) {
 $col = '(' . $col;
 $isNeedOpenGroup = false;
 }
 if ($value['value'] != null) {
 $isIn = $value['o'] == 'IN';
 if ($isTheFirst) {
 $isTheFirst = false;
 }
 else {
 $query .= ' ' . $value['type'] . ' ';
 }
 if ($isIn) {
 $query .= $col . ' IN(';
 foreach ($value['value'] as $kk => $vv) {
 $query .= '?' . ',';
 $prepareData[] = $this->parseWhereValue($vv);
 }
 $query = substr($query, 0, strlen($query) - 1) . ')';
 }
 else {
 $query .= $col . $value['o'] . '?';
 }
 if ($isIn === false) $prepareData[] = $this->parseWhereValue($value['value']);
 }
 else {
 if ($value['null'] != null && is_integer($value['null'])) {
 if ($value['null'] == 1) {
 if ($isTheFirst) {
 $query .= $col . ' IS NULL';
 $isTheFirst = false;
 }
 else {
 $query .= ' ' . $value['type'] . ' ' . $col . ' IS NULL';
 }
 }
 else {
 if ($isTheFirst) {
 $query .= $col . ' IS NOT NULL';
 $isTheFirst = false;
 }
 else {
 $query .= ' ' . $value['type'] . ' ' . $col . ' IS NOT NULL';
 }
 }
 }
 }
 }
 else {
 if ($isTheFirst) {
 $query .= $value['column'];
 $isTheFirst = false;
 }
 else {
 $query .= ' ' . $value['type'] . ' ' . $value['column'];
 }
 }
 }
 }
 return $prepareData;
 }
 }
 