select = []; $this->from = null; // (ACL | tableName) $this->where = []; $this->orderBy = null; $this->groupBy = null; $this->limit = null; $this->offset = null; $this->_fromPrefix = 't'; // prefix for this->from - default 't' $this->_joinPrefix = []; // prefix => from (Acl | tableName) $this->_joinParams = []; // prefx => params $this->isInstances = []; $this->isNotInstances = []; } public function from($from, $prefix = 't') { DBG::log([ 'from instanceof Core_AclBase' => ($from instanceof Core_AclBase), 'from instanceof AntAclBase' => ($from instanceof AntAclBase), 'from instanceof TableAcl' => ($from instanceof TableAcl), ], 'array', "\$from class(".get_class($from).")"); if ($this->from) throw new Exception("Duplicate FROM"); $this->from = $from; $this->_fromPrefix = $prefix; return $this; } public function select($propertyName) { // TODO: ogc:propertyName, *, xpath, @instances, etc... if (empty($propertyName)) return $this; // SKIP empty values, null's, etc. DBG::log($propertyName, 'array', "AclQueryBuilder->select"); if (is_array($propertyName) && !empty($propertyName['rawSelect'])) { $this->select['__rawSelect__'] = $propertyName['rawSelect']; return $this; } if (!in_array($propertyName, $this->select)) $this->select[] = $propertyName; // TODO: split by property source return $this; } public function where($fieldName, $comparisonSign = null, $value = null) { if (!in_array($comparisonSign, [ '=', '>=', '<=', '<>', '!=' , 'like', 'not like' , 'is not null', 'is null' , 'Intersects', 'GeometryType' ])) { throw new Exception("Not implemented comparisonSign '{$comparisonSign}'"); } $sqlFieldName = $fieldName; // TODO: getSqlFieldName switch ($comparisonSign) { case 'is not null': $this->where[] = "{$this->_fromPrefix}.{$sqlFieldName} is not null"; break; case 'is null': $this->where[] = "{$this->_fromPrefix}.{$sqlFieldName} is null"; break; case 'Intersects': $this->where[] = "Intersects(GeomFromText('{$value}'), {$this->_fromPrefix}.`{$sqlFieldName}`)=1"; break; case 'GeometryType': $this->where[] = "GeometryType({$this->_fromPrefix}.`{$sqlFieldName}`)='{$value}'"; break; default: $this->where[] = "{$this->_fromPrefix}.{$sqlFieldName} {$comparisonSign} " . DB::getPDO()->quote($value); } return $this; } public function whereIsNotNull($fieldName) { $this->where[] = "{$this->_fromPrefix}.{$fieldName} is not null"; return $this; } public function whereRaw($rawWhere) { $this->where[] = $rawWhere; return $this; } // public function whereFunction($column, $operator = null, $value = null) { public function whereFunction($fieldName, $comparisonSign = null, $value = null) { throw new Exception("whereFunction not supported"); $this->where[] = ''; return $this; } public function join($join, $prefix, $params) { if (array_key_exists($prefix, $this->_joinPrefix)) throw new Exception("Prefix already used!"); $this->_joinPrefix[$prefix] = $join; $this->_joinParams[$prefix] = $params; return $this; } public function orderBy($orderBy) { // TODO: ogc: order by ... if (null !== $this->orderBy) throw new Exception("OrderBy already set!"); $this->orderBy = []; if (!$orderBy) return $this; // ID A,COL_X D,COL_Y A,... $sortByEx = array_map('trim', explode(',', $orderBy)); $sortByEx = array_filter($sortByEx, function ($part) { return !empty($part); }); foreach ($sortByEx as $sortPart) { $sortPartEx = explode(' ', $sortPart); if (count($sortPartEx) > 2) throw new Exception("SortBy parse error #" . __LINE__); $fieldName = trim($sortPartEx[0]); if (!$this->isFieldAllowedToOrderBy($fieldName)) throw new Exception("SortBy parse error for field '{$fieldName}' #" . __LINE__); $colSortDir = 'ASC'; if (count($sortPartEx) == 2) { if ('A' == strtoupper($sortPartEx[1]) || 'ASC' == strtoupper($sortPartEx[1])) { } else if ('D' == strtoupper($sortPartEx[1]) || 'DESC' == strtoupper($sortPartEx[1])) { $colSortDir = 'DESC'; } else throw new Exception("SortBy parse error - unknown sort order '{$sortPartEx[1]}' #" . __LINE__); } $this->orderBy[] = [$fieldName, $colSortDir]; } return $this; } public function isFieldAllowedToOrderBy($fieldName) { return true; } public function generateOrderBySql() { if (empty($this->orderBy)) return ''; $sortByList = []; foreach ($this->orderBy as $orderBy) { $sortByList[] = "t.`{$orderBy[0]}` {$orderBy[1]}"; } return (!empty($sortByList)) ? "order by " . implode(", ", $sortByList) : ''; } public function limit($limit) { $this->limit = (int)$limit; return $this; } public function offset($offset) { $this->offset = (int)$offset; return $this; } public function isInstance($instances) { $this->isInstances = (is_array($instances)) ? $instances : [ $instances ]; return $this; } public function isNotInstance($instances) { $this->isNotInstances = (is_array($instances)) ? $instances : [ $instances ]; return $this; } public function _parseJoinParams($params) { if (array_key_exists('rawJoin', $params)) return $params['rawJoin']; throw new Exception("Not implemented JOIN params '".json_encode($params)."'"); } public function _getTableName($source) { if (is_scalar($source)) return $source; if ($source instanceof Core_AclBase) return $source->getRootTableName(); throw new Exception("Not implemented FROM type '".get_class($source)."'"); } public function execute() { return $this->fetchAll(); } public function fetchAll() { $sql = $this->generateSql(); DBG::log((array)$this, 'array', "AclQueryBuilder::fetchAll"); return DB::getPDO()->fetchAll($sql); } public function fetchValue() { $sql = $this->generateSql(); DBG::log((array)$this, 'array', "AclQueryBuilder::fetchValue"); return DB::getPDO()->fetchValue($sql); } public function generateSql() { if (!$this->from) throw new Exception("Missing FROM"); $tableName = $this->_getTableName($this->from); if (!$tableName) throw new Exception("Missing FROM table name"); $sqlPk = 'ID'; if ($this->from instanceof Core_AclBase) $sqlPk = $this->from->getSqlPrimaryKeyField(); $sqlJoin = []; DBG::log($this, 'array', '$this'); foreach ($this->isInstances as $k => $ns) { $idInstance = ACL::getInstanceId($ns); $instanceTable = ACL::getInstanceTable($ns); // ->join($instanceTable, 'i', [ 'rawJoin' => "i.pk = t.{$sqlPk} and i.idInstance = {$idInstance}" ]) $prefix = "is_inst_{$k}"; // $sqlJoin[] = " inner join `{$joinName}` {$prefix} on ( // {$prefix}.pk = {$this->_fromPrefix}.{$sqlPk} // and {$prefix}.idInstance = {$idInstance} // )"; $this->where[] = "{$this->_fromPrefix}.{$sqlPk} in ( select {$prefix}.pk from `{$instanceTable}` {$prefix} where {$prefix}.idInstance = {$idInstance} )"; DBG::log("{$this->_fromPrefix}.{$sqlPk} in ( select {$prefix}.pk from `{$instanceTable}` {$prefix} where {$prefix}.idInstance = {$idInstance} )", 'string', "\$this->where[] ="); } foreach ($this->isNotInstances as $k => $ns) { $idInstance = ACL::getInstanceId($ns); $instanceTable = ACL::getInstanceTable($ns); // ->join($instanceTable, 'i', [ 'rawJoin' => "i.pk = t.{$sqlPk} and i.idInstance = {$idInstance}" ]) $prefix = "is_inst_{$k}"; // $sqlJoin[] = " inner join `{$joinName}` {$prefix} on ( // {$prefix}.pk = {$this->_fromPrefix}.{$sqlPk} // and {$prefix}.idInstance = {$idInstance} // )"; $this->where[] = "{$this->_fromPrefix}.{$sqlPk} not in ( select {$prefix}.pk from `{$instanceTable}` {$prefix} where {$prefix}.idInstance = {$idInstance} )"; } // join `{$instanceTable}` i on(i.pk = t.{$sqlPk} and i.idInstance = {$idInstance}) foreach ($this->_joinPrefix as $prefix => $join) { $joinName = $this->_getTableName($join); $sqlJoin[] = " join `{$joinName}` {$prefix} on(" . $this->_parseJoinParams($this->_joinParams[$prefix]) . ")"; } $sqlJoin = (!empty($sqlJoin)) ? implode("\n\t", $sqlJoin) : ""; $sqlWhere = []; foreach ($this->where as $where) { $sqlWhere[] = $where; } $sqlWhere = (!empty($sqlWhere)) ? "where " . implode("\n\t and ", $sqlWhere) : ''; $limit = ($this->limit < 0) ? 0 : $this->limit; $offset = ($this->offset < 0) ? 0 : $this->offset; $sqlLimit = ($limit > 0) ? "limit {$limit} offset {$offset}" : ''; $sqlSelect = []; if (!empty($this->select['__rawSelect__'])) { $sqlSelect[] = $this->select['__rawSelect__']; } else { $sqlSelect[] = "{$this->_fromPrefix}.{$sqlPk}"; if (in_array('*', $this->select)) $sqlSelect[] = "{$this->_fromPrefix}.*"; } if (in_array('@instances', $this->select)) { if (!($this->from instanceof Core_AclBase)) throw new Exception("select @instances allowed only for Acl object"); $instanceTable = ACL::getInstanceTable($this->from->getNamespace()); $sqlSelect[] = " ( select GROUP_CONCAT(inst_conf.namespace) from `{$instanceTable}` inst_tbl join `CRM_INSTANCE_CONFIG` inst_conf on (inst_conf.id = inst_tbl.idInstance) where inst_tbl.pk = {$this->_fromPrefix}.{$sqlPk} ) as `@instances` "; } $sqlOrderBy = $this->generateOrderBySql(); $sqlSelect = (!empty($sqlSelect)) ? implode(", ", $sqlSelect) : "{$this->_fromPrefix}.*"; return " select {$sqlSelect} from `{$tableName}` {$this->_fromPrefix} {$sqlJoin} {$sqlWhere} {$sqlOrderBy} {$sqlLimit} "; } }