Sfoglia il codice sorgente

updated AclQuery, local fields

Piotr Labudda 9 anni fa
parent
commit
8e48ba28ad

+ 44 - 23
SE/se-lib/AclQueryBuilder.php

@@ -66,10 +66,13 @@ class AclQueryBuilder {
     if (empty($propertyName)) return $this; // SKIP empty values, null's, etc.
     if (empty($propertyName)) return $this; // SKIP empty values, null's, etc.
     DBG::log($propertyName, 'array', "AclQueryBuilder->select");
     DBG::log($propertyName, 'array', "AclQueryBuilder->select");
     if (is_array($propertyName)) {
     if (is_array($propertyName)) {
+      $hasWildcard = in_array('*', $propertyName);
       foreach ($propertyName as $k => $v) {
       foreach ($propertyName as $k => $v) {
         if ('rawSelect' === $k) $this->select['__rawSelect__'] = $v;
         if ('rawSelect' === $k) $this->select['__rawSelect__'] = $v;
+        else if ('*' === $v) continue;
         else $this->select[] = $v;
         else $this->select[] = $v;
       }
       }
+      if ($hasWildcard) array_unshift($this->select, '*');
       return $this;
       return $this;
     }
     }
     if (!in_array($propertyName, $this->select)) $this->select[] = $propertyName; // TODO: split by property source
     if (!in_array($propertyName, $this->select)) $this->select[] = $propertyName; // TODO: split by property source
@@ -156,7 +159,7 @@ class AclQueryBuilder {
     if ('__rawSelect__' === $key) return $select;
     if ('__rawSelect__' === $key) return $select;
     $sqlPk = 'ID';
     $sqlPk = 'ID';
     if ($this->from instanceof Core_AclBase) $sqlPk = $this->from->getSqlPrimaryKeyField();
     if ($this->from instanceof Core_AclBase) $sqlPk = $this->from->getSqlPrimaryKeyField();
-    if ('*' === $select) return '*';
+    if ('*' === $select) return "{$this->_fromPrefix}.*";
     if ('@instances' === $select) {
     if ('@instances' === $select) {
       if (!($this->from instanceof Core_AclBase)) throw new Exception("select @instances allowed only for Acl object");
       if (!($this->from instanceof Core_AclBase)) throw new Exception("select @instances allowed only for Acl object");
       $instanceTable = ACL::getInstanceTable($this->from->getNamespace());
       $instanceTable = ACL::getInstanceTable($this->from->getNamespace());
@@ -172,7 +175,9 @@ class AclQueryBuilder {
     if (is_array($select)) {
     if (is_array($select)) {
       // TODO: [ '__backRef' => [ ... ] ]
       // TODO: [ '__backRef' => [ ... ] ]
       DBG::log($select, 'array', "TODO: select array");
       DBG::log($select, 'array', "TODO: select array");
-    } else {
+      return null;
+    }
+    if (is_string($select)) {
       // TODO: only real table field
       // TODO: only real table field
       // TODO: if geometry type then `AsWKT(t.`{$fieldName}`) as {$fieldName}`
       // TODO: if geometry type then `AsWKT(t.`{$fieldName}`) as {$fieldName}`
       try {
       try {
@@ -269,6 +274,11 @@ class AclQueryBuilder {
     return DB::getPDO()->fetchAll($sql);
     return DB::getPDO()->fetchAll($sql);
   }
   }
 
 
+  public function fetchTotal() {
+    $sql = $this->generateSql("count(*) as cnt");
+    return DB::getPDO()->fetchValue($sql);
+  }
+
   public function fetchValue() {
   public function fetchValue() {
     $sql = $this->generateSql();
     $sql = $this->generateSql();
     DBG::log(['sql'=>$sql,'this'=>(array)$this], 'array', "AclQueryBuilder::fetchValue");
     DBG::log(['sql'=>$sql,'this'=>(array)$this], 'array', "AclQueryBuilder::fetchValue");
@@ -281,13 +291,14 @@ class AclQueryBuilder {
     return DB::getPDO()->fetchFirst($sql);
     return DB::getPDO()->fetchFirst($sql);
   }
   }
 
 
-  public function generateSql() {
+  public function generateSql($select = null) {
     if (!$this->from) throw new Exception("Missing FROM");
     if (!$this->from) throw new Exception("Missing FROM");
     $tableName = $this->_getTableName($this->from);
     $tableName = $this->_getTableName($this->from);
     if (!$tableName) throw new Exception("Missing FROM table name");
     if (!$tableName) throw new Exception("Missing FROM table name");
 
 
-    $sqlPk = 'ID';
-    if ($this->from instanceof Core_AclBase) $sqlPk = $this->from->getSqlPrimaryKeyField();
+    $sqlPk = ($this->from instanceof Core_AclBase)
+      ? $this->from->getSqlPrimaryKeyField()
+      : 'ID';
 
 
     $sqlJoin = [];
     $sqlJoin = [];
     DBG::log($this, 'array', '$this');
     DBG::log($this, 'array', '$this');
@@ -349,14 +360,18 @@ class AclQueryBuilder {
     // TODO: $this->_hasSelectRemoteFields = false;
     // TODO: $this->_hasSelectRemoteFields = false;
     // TODO: $this->_hasQueryRemoteFields = false;
     // TODO: $this->_hasQueryRemoteFields = false;
 
 
-    $sqlSelect = array_filter(
-      array_map([$this, '_generateSelectMain'], $this->select, array_keys($this->select)),
-      'is_string'
-    );
-    $sqlSelect = (!empty($sqlSelect))
-      ? implode(",\n\t", $sqlSelect)
-      : "{$this->_fromPrefix}.*"
-    ;
+    if ($select) {
+      $sqlSelect = $select; // used for example fetchTotal -> generateSql("count(*) as cnt")
+    } else {
+      $sqlSelect = array_filter(
+        array_map([$this, '_generateSelectMain'], $this->select, array_keys($this->select)),
+        'is_string'
+      );
+      $sqlSelect = (!empty($sqlSelect))
+        ? implode(",\n\t", $sqlSelect)
+        : "{$this->_fromPrefix}.*"
+      ;
+    }
 
 
     $sqlOrderBy = $this->generateOrderBySql();
     $sqlOrderBy = $this->generateOrderBySql();
     return "
     return "
@@ -376,17 +391,23 @@ class AclQueryBuilder {
     }
     }
 
 
     $fieldType = $this->from->getXsdFieldType($fieldName);
     $fieldType = $this->from->getXsdFieldType($fieldName);
-    if ('xsd:' === substr($fieldType, 0, 4)) {
-      return "{$prefix}.`{$fieldName}`";
-    }
-    switch ($fieldType) {
-      case 'gml:PolygonPropertyType':
-      case 'gml:PointPropertyType':
-      case 'gml:LineStringPropertyType':
-      case 'gml:GeometryPropertyType': return "AsWKT({$prefix}.`{$fieldName}`) as `{$fieldName}`";
-      case 'p5:enum': { // TODO: check if local or remote
-        return "{$prefix}.`{$fieldName}`";
+    list($typePrefix, $typeName, $retTypeName) = explode(':', $fieldType);
+    switch ($typePrefix) {
+      case 'xsd': return "{$prefix}.`{$fieldName}`";
+      // 'gml:PolygonPropertyType':
+      // 'gml:PointPropertyType':
+      // 'gml:LineStringPropertyType':
+      // 'gml:GeometryPropertyType':
+      case 'gml': return "AsWKT({$prefix}.`{$fieldName}`) as `{$fieldName}`";
+      case 'p5': {
+        switch ($typeName) {
+          case 'price': return "{$prefix}.`{$fieldName}`";
+          case 'enum': return "{$prefix}.`{$fieldName}`"; // TODO: check if local or remote
+          case 'www_link': return "{$prefix}.`{$fieldName}`"; // TODO: check if local or remote?
+          default: throw new Exception("Not implemented field type in select '{$fieldType}' (field: '{$fieldName}')");
+        }
       }
       }
+      default: throw new Exception("Not implemented field type in select '{$fieldType}' (field: '{$fieldName}')");
     }
     }
     return null;
     return null;
   }
   }

+ 121 - 17
SE/se-lib/AclQueryFeatures.php

@@ -20,6 +20,8 @@ class AclQueryFeatures {
   public $_query;
   public $_query;
   public $_total;
   public $_total;
   public $_legacyMode;
   public $_legacyMode;
+  public $_selectLocalFields; // TODO: leave here or move to AclQueryBuilder? use join for perf?
+  public $_selectRemote; // TODO: ...
 
 
   public function __construct($acl, $params, $legacyMode = false) {
   public function __construct($acl, $params, $legacyMode = false) {
     $this->_acl = $acl;
     $this->_acl = $acl;
@@ -28,6 +30,8 @@ class AclQueryFeatures {
     $this->_total = null;
     $this->_total = null;
     $this->_legacyMode = $legacyMode;
     $this->_legacyMode = $legacyMode;
     // TODO: _legacyMode = ($from instanceof simple schema or another programmed objects)
     // TODO: _legacyMode = ($from instanceof simple schema or another programmed objects)
+    $this->_selectLocalFields = null;
+    $this->_selectRemote = null;
   }
   }
 
 
   public function parseQueryValue($fieldName, $searchQuery, $fieldType = 'xsd:string') {
   public function parseQueryValue($fieldName, $searchQuery, $fieldType = 'xsd:string') {
@@ -227,7 +231,7 @@ class AclQueryFeatures {
   public function parseSpecialFilterAccess() {
   public function parseSpecialFilterAccess() {
     $userLogin = User::getLogin();
     $userLogin = User::getLogin();
     $usrAclGroups = User::getLdapGroupsNames();
     $usrAclGroups = User::getLdapGroupsNames();
-    DBG::log(['login'=>$userLogin, 'groups'=>$usrAclGroups, 'acl'=>$this->_acl], 'array', "parse SpecialFilter Access");
+    DBG::log(['login'=>$userLogin, 'groups'=>$usrAclGroups, 'hasFieldWrite'=>$this->_acl->hasField('A_ADM_COMPANY'), 'hasFieldRead'=>$this->_acl->hasField('A_CLASSIFIED'), 'acl'=>$this->_acl], 'array', "parse SpecialFilter Access");
     $orWhere = [];
     $orWhere = [];
     if ($this->_acl->hasField('A_ADM_COMPANY')) {
     if ($this->_acl->hasField('A_ADM_COMPANY')) {
       $orWhere[] = ['A_ADM_COMPANY', '=', ''];// TODO: allow empty for everyone?
       $orWhere[] = ['A_ADM_COMPANY', '=', ''];// TODO: allow empty for everyone?
@@ -251,7 +255,7 @@ class AclQueryFeatures {
   }
   }
 
 
   public function getQuery() {
   public function getQuery() {
-    if ($this->_query) return $this->_query;
+    if ($this->_query) return clone($this->_query);
     // $ds = $this->_acl->getDataSource(); // TODO: only for TableAcl // TODO: move _parseSqlWhere to this class
     // $ds = $this->_acl->getDataSource(); // TODO: only for TableAcl // TODO: move _parseSqlWhere to this class
     $filtrIsInstance = []; // $filtrIsInstance = [ $this->_acl->getNamespace() ];
     $filtrIsInstance = []; // $filtrIsInstance = [ $this->_acl->getNamespace() ];
     $filtrIsNotInstance = [];
     $filtrIsNotInstance = [];
@@ -296,6 +300,7 @@ class AclQueryFeatures {
         list($comparisonSign, $value) = $this->parseQueryValue($fieldName, $v, $fieldType);
         list($comparisonSign, $value) = $this->parseQueryValue($fieldName, $v, $fieldType);
         DBG::log([ $fieldName, $comparisonSign, $value, $fieldType ], 'array', "parseQueryValue");
         DBG::log([ $fieldName, $comparisonSign, $value, $fieldType ], 'array', "parseQueryValue");
         $this->_query->where([$fieldName, $comparisonSign, $value]);
         $this->_query->where([$fieldName, $comparisonSign, $value]);
+      } else if ('__backRef' === $k) { // skip - parse below
       } else if ('limit' === $k) {
       } else if ('limit' === $k) {
       } else if ('limitstart' === $k) {
       } else if ('limitstart' === $k) {
       } else if ('order_by' === $k) {
       } else if ('order_by' === $k) {
@@ -308,15 +313,35 @@ class AclQueryFeatures {
     // sf_Access: if 'SHOW' then show all rows, but data with ***
     // sf_Access: if 'SHOW' then show all rows, but data with ***
     if ('SHOW' !== V::get('sf_Access', '', $this->_params)) $this->_query->where($this->parseSpecialFilterAccess());
     if ('SHOW' !== V::get('sf_Access', '', $this->_params)) $this->_query->where($this->parseSpecialFilterAccess());
 
 
-    return $this->_query;
+    if (array_key_exists('__backRef', $this->_params)) {
+      $backRef = $this->_params['__backRef'];
+      if (!is_array($backRef)) throw new Exception("Wrong back ref structure - expected array");
+      if (empty($backRef['namespace'])) throw new Exception("Wrong back ref structure - missing namespace");
+      if (empty($backRef['primaryKey'])) throw new Exception("Wrong back ref structure - missing primaryKey");
+      if (empty($backRef['fieldName'])) throw new Exception("Wrong back ref structure - missing fieldName");
+      // TODO: $this->_query->where([ '__backRef' ]); or  $this->_query->join([ '__backRef' ]);
+      $refAcl = ACL::getAclByNamespace($backRef['namespace']);
+      if ($refAcl->getSourceName() !== $this->_acl->getSourceName()) throw new Exception("Not implemented join with different source");
+      $refTable = ACL::getRefTable($refAcl->getNamespace(), $backRef['fieldName']);
+      // TODO: 'in' operator? // $this->_query->where($pkField, 'in', "");
+      $sqlPk = $this->getAclSqlPrimaryKeyField();
+      $sqlBackRefPk = DB::getPDO()->quote($backRef['primaryKey']);
+      $this->_query->where("
+        t.{$sqlPk} in (
+          select refTable.REMOTE_PRIMARY_KEY
+          from `{$refTable}` refTable
+          where refTable.PRIMARY_KEY = {$sqlBackRefPk}
+        )
+      ");
+    }
+
+    return clone($this->_query);
   }
   }
 
 
   public function getTotal() {
   public function getTotal() {
     if ($this->_legacyMode) return $this->_acl->getTotal($this->_params);
     if ($this->_legacyMode) return $this->_acl->getTotal($this->_params);
     if (null !== $this->_total) return $this->_total;
     if (null !== $this->_total) return $this->_total;
-    $this->_total = $this->getQuery()
-      ->select([ 'rawSelect' => "count(1) as cnt" ]) // TODO: fetchTotal() ? a// TODO: add AclQueryBuilder::fetchTotal()
-      ->fetchValue();
+    $this->_total = $this->getQuery()->fetchTotal();
     return $this->_total;
     return $this->_total;
   }
   }
 
 
@@ -343,6 +368,7 @@ class AclQueryFeatures {
 
 
     $select = $this->prepareSelect();
     $select = $this->prepareSelect();
     DBG::log($select, 'array', "\$select is(TableAcl)=(".($this->_acl instanceof TableAcl).")");
     DBG::log($select, 'array', "\$select is(TableAcl)=(".($this->_acl instanceof TableAcl).")");
+    DBG::log($this->getQuery(), 'array', "\$select is(TableAcl)=(".($this->_acl instanceof TableAcl).") \$this->getQuery()");
 
 
     return $this->fetchRowsRefs(
     return $this->fetchRowsRefs(
       $this->getQuery()
       $this->getQuery()
@@ -366,27 +392,82 @@ class AclQueryFeatures {
     );
     );
   }
   }
 
 
-  public function prepareSelect() {
+  public function prepareSelect() { // TODO: replace with getSelectLocal
     // TODO: select from params: 'cols' => [ fieldName, ... ]
     // TODO: select from params: 'cols' => [ fieldName, ... ]
     // TODO: select from params: '@instances' => 1
     // TODO: select from params: '@instances' => 1
     // TODO: if no fields set, then '*'
     // TODO: if no fields set, then '*'
     // TODO: select must contain primaryKey
     // TODO: select must contain primaryKey
-    $select = [];
-    $select = [
-      'rawSelect' => ($this->_acl instanceof TableAcl)
-        ? $this->_acl->getDataSource()->_getSqlCols()
-        : '*'
-    ];
-    if (!empty($this->_params['@instances'])) $select[] = '@instances';
+    return $this->getSelectLocal();
+  }
+  public function getSelectLocal() { // @returns [ $fieldName, ... ]
+    // TODO: rawSelect!
+    if (null !== $this->_selectLocalFields) return $this->_selectLocalFields;
+    $this->_selectLocalFields = [];
+    $todoFetchAllCols = false; // select t.*
+    if (!empty($this->_params['cols'])) {
+      if (in_array('*', $this->_params['cols'])) $todoFetchAllCols = true;
+      $acl = $this->_acl;
+      $this->_selectLocalFields = array_filter($this->_params['cols'], function ($fieldQuery) use ($acl) {
+        if ('*' === $fieldQuery) return false;
+        list($fieldName, $subFieldQuery) = explode('/', $fieldQuery, 2);
+        if (!empty($subFieldQuery)) return false;
+        return $acl->isLocalField($fieldName);
+      });
+    }
+    if (!empty($this->_params['@instances'])) $this->_selectLocalFields[] = '@instances';
+    if (empty($this->_selectLocalFields)) $todoFetchAllCols = true;
+    if (1 === count($this->_selectLocalFields) && in_array('@instances', $this->_selectLocalFields)) $todoFetchAllCols = true;
+    // if ($this->_acl instanceof TableAcl) {
+    //   $rawSelect = $this->_acl->getDataSource()->_getSqlCols();
+    //   DBG::log($rawSelect, 'string', "DBG raw select");
+    //   if ('*' !== $rawSelect && 't.*' !== $rawSelect) {
+    //     $this->_selectLocalFields['rawSelect'] = $rawSelect;
+    //   }
+    // }
+    if (!empty($this->_params['cols'])) DBG::log($this->_params['cols'], 'array', '$this->_params[cols]');
+
+    if ($todoFetchAllCols) {
+      // $this->_selectLocalFields[] = '*'; // TODO: select all $this->from local fields
+      DBG::log($this->_acl->getLocalFieldList(), 'array', "\$this->_acl->getLocalFieldList()");
+      foreach ($this->_acl->getLocalFieldList() as $localFieldName) {
+        $this->_selectLocalFields[] = $localFieldName;
+      }
+    }
+
+    $primaryKey = $this->getAclSqlPrimaryKeyField();
+    if (!in_array($primaryKey, $this->_selectLocalFields)) {
+      $this->_selectLocalFields[] = $primaryKey;
+    }
+
+    DBG::log($this->_selectLocalFields, 'array', '$this->_selectLocalFields');
+    return $this->_selectLocalFields;
+  }
+  public function getSelectRemote() { // @returns [ $fieldName => [ $fieldName, ... ] ]
+    if (null !== $this->_selectRemote) return $this->_selectRemote;
+    $this->_selectRemote = [];
     if (!empty($this->_params['cols'])) {
     if (!empty($this->_params['cols'])) {
-      foreach ($this->_params['cols'] as $fieldName) {
-        $select[] = $fieldName;
+      $cols = $this->_params['cols'];
+      if (in_array('*', $cols)) {
+        $this->_selectLocalFields[] = '*';
+        $cols = array_filter($cols, function ($fieldQuery) { return '*' !== $fieldQuery; });
+      }
+      $acl = $this->_acl;
+      $cols = array_filter($cols, function ($fieldQuery) use ($acl) {
+        list($fieldName, $subFieldQuery) = explode('/', $fieldQuery, 2);
+        if (empty($subFieldQuery)) return false;
+        return !$acl->isLocalField($fieldName);
+      });
+      foreach ($cols as $fieldQuery) { // group by fieldName
+        list($fieldName, $subFieldQuery) = explode('/', $fieldQuery, 2);
+        if (!array_key_exists($fieldName, $this->_selectRemote)) $this->_selectRemote[$fieldName] = [];
+        $this->_selectRemote[$fieldName][] = $subFieldQuery;
       }
       }
     }
     }
-    return $select;
+    return $this->_selectRemote;
   }
   }
 
 
   public function fetchRowsRefs($rows) {
   public function fetchRowsRefs($rows) {
+    return array_map([ $this, 'fetchRowRefs' ], $rows);
     // if (!empty($rows) && !empty($rows[0])) {
     // if (!empty($rows) && !empty($rows[0])) {
     //   $rows[0]['default_db__x3A__CRM_WSKAZNIK:CRM_WSKAZNIK'] = [
     //   $rows[0]['default_db__x3A__CRM_WSKAZNIK:CRM_WSKAZNIK'] = [
     //     [ 'xlink' => 'default_db__x3A__CRM_WSKAZNIK:CRM_WSKAZNIK.999' ],
     //     [ 'xlink' => 'default_db__x3A__CRM_WSKAZNIK:CRM_WSKAZNIK.999' ],
@@ -398,7 +479,30 @@ class AclQueryFeatures {
 
 
   public function fetchRowRefs($row) {
   public function fetchRowRefs($row) {
     if (!$row) return $row;
     if (!$row) return $row;
+    $sqlPk = $this->getAclSqlPrimaryKeyField();
+    $primaryKey = $row[$sqlPk];
+    DBG::log($row, 'array', "DBG primaryKey '{$primaryKey}'");
+    if (!$primaryKey) throw new Exception("Missing primaryKey");
+    foreach ($this->getSelectRemote() as $fieldName => $cols) {
+      DBG::log($cols, 'array', "add select remote '{$fieldName}' \$cols");
+      $items = ACL::getAclByTypeName($fieldName)->buildQuery([
+        'cols' => $cols,
+        '__backRef' => [
+          'namespace' => $this->_acl->getNamespace(),
+          'primaryKey' => $primaryKey,
+          'fieldName' => $fieldName,
+        ]
+      ])->getItems();
+      DBG::log($items, 'array', "TODO: add remote items '{$fieldName}' \$items");
+      $row[$fieldName] = $items;
+    }
     return $row;
     return $row;
   }
   }
 
 
+  public function getAclSqlPrimaryKeyField() {
+    return ($this->_acl instanceof Core_AclBase)
+      ? $this->_acl->getSqlPrimaryKeyField()
+      : 'ID';
+  }
+
 }
 }

+ 9 - 1
SE/se-lib/AntAclBase.php

@@ -93,7 +93,7 @@ class AntAclBase extends Core_AclBase {
 	// }
 	// }
 
 
   public function isGeomField($fieldName) {
   public function isGeomField($fieldName) {
-    return ('the_geom' === $fieldName); // TODO: ...
+    return ('the_geom' === $fieldName); // TODO: check by xsdType
   }
   }
   public function isEnumerationField($fieldName) {
   public function isEnumerationField($fieldName) {
     return false; // TODO: ...
     return false; // TODO: ...
@@ -229,6 +229,14 @@ class AntAclBase extends Core_AclBase {
     );
     );
   }
   }
 
 
+  public function getLocalFieldList() {
+    return array_map(function ($field) {
+      return $field['fieldNamespace'];
+    }, array_filter($this->getFields(), function ($field) {
+      return ($field['isLocal']);
+    }));
+  }
+
   public function isLocalField($fieldName) {
   public function isLocalField($fieldName) {
     return $this->_getField($fieldName)['isLocal'];
     return $this->_getField($fieldName)['isLocal'];
   }
   }

+ 1 - 0
SE/se-lib/Core/AclBase.php

@@ -62,6 +62,7 @@ class Core_AclBase {
     return $fieldName;
     return $fieldName;
 	}
 	}
   public function getFieldListByIdZasob() { throw new HttpException("Acl function " . __FUNCTION__ . " Not implemented", 501); }// TODO: RMME - one field list function
   public function getFieldListByIdZasob() { throw new HttpException("Acl function " . __FUNCTION__ . " Not implemented", 501); }// TODO: RMME - one field list function
+  public function getLocalFieldList() { throw new HttpException("Acl function " . __FUNCTION__ . " Not implemented", 501); }// TODO: RMME - one field list function
   public function getRealFieldListByIdZasob() { throw new HttpException("Acl function " . __FUNCTION__ . " Not implemented", 501); }// TODO: RMME - one field list function
   public function getRealFieldListByIdZasob() { throw new HttpException("Acl function " . __FUNCTION__ . " Not implemented", 501); }// TODO: RMME - one field list function
   public function getVirtualFieldListByIdZasob() { throw new HttpException("Acl function " . __FUNCTION__ . " Not implemented", 501); }// TODO: RMME - one field list function
   public function getVirtualFieldListByIdZasob() { throw new HttpException("Acl function " . __FUNCTION__ . " Not implemented", 501); }// TODO: RMME - one field list function
   // TODO: public function getFieldList(); // @retuns array of field names
   // TODO: public function getFieldList(); // @retuns array of field names