/** * Short description of method unReferenceClass * * @access public * @author Joel Bout, <*****@*****.**> * @param Class class * @return boolean */ public function unReferenceClass(\core_kernel_classes_Class $class) { $returnValue = (bool) false; if ($this->isClassReferenced($class)) { $tableName = '_' . Utils::getShortName($class); //need to instanciate table manager before unreferencing otherwise, the "remove table" will fail $tm = new TableManager($tableName); // Delete reference of the class in classs_to_table, resource_has_class, resource_to_table $dbWrapper = \core_kernel_classes_DbWrapper::singleton(); // Remove references of the resources in the resource has class table $queries = array(); $queries[] = 'DELETE FROM "resource_has_class" WHERE "resource_has_class"."resource_id" IN (SELECT "resource_to_table"."id" FROM "resource_to_table" WHERE "resource_to_table"."table" = \'' . $tableName . '\' );'; // Remove reference of the class in the additional properties tables $queries[] = 'DELETE FROM "class_additional_properties" WHERE "class_id" IN (SELECT "class_to_table"."id" FROM "class_to_table" WHERE "class_to_table"."table" = \'' . $tableName . '\' );'; // Remove resferences of the resources int resource to table table $queries[] = 'DELETE FROM "resource_to_table" WHERE "resource_to_table"."table" = \'' . $tableName . '\';'; // Remove reference of the class in the class to table table $queries[] = 'DELETE FROM "class_to_table" WHERE "class_to_table"."table" = \'' . $tableName . '\';'; $returnValue = true; try { foreach ($queries as $query) { $result = $dbWrapper->exec($query); if ($result === false) { $returnValue = false; } } if ($returnValue !== false) { // delete table associated to the class $tm->remove(); // remove class from the cache if ($this->cacheModes['class'] == self::CACHE_MEMORY && is_array(self::$_classes)) { foreach (self::$_classes as $index => $aClass) { if ($aClass['uri'] == $class->getUri()) { unset(self::$_classes[$index]); } } } } ClassProxy::$ressourcesDelegatedTo = array(); ResourceProxy::$ressourcesDelegatedTo = array(); PropertyProxy::$ressourcesDelegatedTo = array(); } catch (\PDOException $e) { throw new Exception("Unable to unreference class {$class->getUri()} : " . $e->getMessage()); } } return (bool) $returnValue; }
/** * Short description of method getRdfTriples * * @access public * @author Joel Bout, <*****@*****.**> * @param Resource resource * @return \core_kernel_classes_ContainerCollection */ public function getRdfTriples(\core_kernel_classes_Resource $resource) { $returnValue = null; $returnValue = new \core_kernel_classes_ContainerCollection(new \common_Object(__METHOD__)); $referencer = ResourceReferencer::singleton(); $tableName = $referencer->resourceLocation($resource); if (!empty($tableName)) { try { $tblmgr = new TableManager($tableName); $propertiesTableName = $tblmgr->getPropertiesTable(); $dbWrapper = \core_kernel_classes_DbWrapper::singleton(); // We get the triples for cardinality = multiple or lg dependent properties // as usual... $quotedUri = $dbWrapper->quote($resource->getUri()); $propsQuery = 'SELECT "b"."id", "b"."uri", "p"."property_uri" AS "property_uri", COALESCE("p"."property_value", "p"."property_foreign_uri") as "property_value", "p"."l_language" FROM "' . $tableName . '" "b" '; $propsQuery .= 'INNER JOIN "' . $propertiesTableName . '" "p" ON ("b"."id" = "p"."instance_id") WHERE "b"."uri" = ' . $quotedUri; $propertyColumns = $tblmgr->getPropertyColumns(); $baseQuery = ''; if (!empty($propertyColumns)) { // But if we have properties as columns in the 'base table' we // have to be crafty... $baseQueries = array(); foreach ($propertyColumns as $k => $pC) { $quotedPropUri = $dbWrapper->quote($pC); $baseQueries[] = 'SELECT "b"."id", "b"."uri", ' . $quotedPropUri . ' AS "property_uri", "b"."' . $k . '" AS "property_value", \'\' AS "l_language" FROM "' . $tableName . '" "b" WHERE "b"."uri" = ' . $quotedUri . ' AND "b"."' . $k . '" IS NOT NULL'; } $baseQuery = implode(' UNION ', $baseQueries); } $query = $propsQuery . ' UNION ' . $baseQuery . ' ORDER BY "property_uri"'; try { $result = $dbWrapper->query($query); while ($row = $result->fetch()) { if ($row['property_value'] != null) { $triple = new \core_kernel_classes_Triple(); $triple->subject = $row['uri']; $triple->predicate = $row['property_uri']; $triple->object = $row['property_value']; $triple->lg = $row['l_language']; $returnValue->add($triple); } } // In hard mode, the rdf:type given to resources is defined by // 'the table' their are belonging to. In this case, we need to // manually add these triples to the end result. $types = $resource->getTypes(); foreach ($types as $class) { $triple = new \core_kernel_classes_Triple(); $triple->subject = $resource->getUri(); $triple->predicate = RDF_TYPE; $triple->object = $class->getUri(); $triple->lg = ''; $returnValue->add($triple); } } catch (\PDOException $e) { $uri = $resource->getUri(); throw new Exception("Unable to retrieve RDF triples of resource '{$uri}': " . $e->getMessage()); } } catch (HardapiException $e) { throw new Exception("Unable to access data from table '{$tableName}: " . $e->getMessage()); } } return $returnValue; }
/** * Calling this method will transfer all instances of $class from the statements table * to specific optimized relational tables. * * During optimization, the current user has all privileges on the persistent memory. At * the end of the process, the old privileges will be set back. * * The $options array can contain the following key => values (all booleans): * * - recursive: compile the target class and its subclasses (default: false). * - append: append data to the existing optimized table if it already exists (default: false). * - rmSources: remove the triples in the statement table after transfer (default: true). * * @access public * @author Bertrand Chevrier, <*****@*****.**> * @param \core_kernel_classes_Class class * @param array options * @return boolean Will return true if it succeeds, false otherwise. */ public function hardify(\core_kernel_classes_Class $class, $options = array()) { $returnValue = (bool) false; $oldUpdatableModels = core_kernel_persistence_smoothsql_SmoothModel::getUpdatableModelIds(); try { // Give access to all models during hardification. core_kernel_persistence_smoothsql_SmoothModel::forceUpdatableModelIds(self::getAllModelIds()); $classLabel = $class->getLabel(); \common_Logger::i("Hardifying class {$classLabel}", array("GENERIS")); if (defined("DEBUG_PERSISTENCE") && DEBUG_PERSISTENCE) { if (in_array($class->getUri(), self::$debug_tables)) { return; } \common_Logger::d('hardify ' . $class->getUri()); self::$debug_tables[] = $class->getUri(); $countStatement = $this->countStatements(); } if (in_array($class->getUri(), self::$blackList)) { return $returnValue; } // ENTER IN SMOOTH SQL MODE PersistenceProxy::forceMode(PERSISTENCE_SMOOTH); //recursive will hardify the class and it's subclasses in the same table! isset($options['recursive']) ? $recursive = $options['recursive'] : ($recursive = false); //createForeigns will hardify the class that are range of the properties isset($options['createForeigns']) ? $createForeigns = $options['createForeigns'] : ($createForeigns = false); //check if we append the data in case the hard table exists or truncate the table and add the new rows isset($options['append']) ? $append = $options['append'] : ($append = false); //if true, the instances of the class will be removed from the statements table! isset($options['rmSources']) ? $rmSources = (bool) $options['rmSources'] : ($rmSources = false); //if defined, we took all the properties of the class and it's parents till the topclass isset($options['topclass']) ? $topclass = $options['topclass'] : ($topclass = new \core_kernel_classes_Class(RDFS_RESOURCE)); //if defined, compile the additional properties isset($options['additionalProperties']) ? $additionalProperties = $options['additionalProperties'] : ($additionalProperties = array()); //if defined, reference the additional class to the table isset($options['referencesAllTypes']) ? $referencesAllTypes = $options['referencesAllTypes'] : ($referencesAllTypes = false); $tableName = '_' . Utils::getShortName($class); $myTableMgr = new TableManager($tableName); $referencer = ResourceReferencer::singleton(); //get the table columns from the class properties $columns = array(); $ps = new PropertySwitcher($class); $properties = $ps->getProperties($additionalProperties); $columns = $ps->getTableColumns($additionalProperties, self::$blackList); //init the count value in hardened classes: if (isset($this->hardenedClasses[$class->getUri()])) { PersistenceProxy::restoreImplementation(); return true; //already being compiled } else { $this->hardenedClasses[$class->getUri()] = 0; } if (!$append || $append && !$myTableMgr->exists()) { //create the table if ($myTableMgr->exists()) { $myTableMgr->remove(); } $myTableMgr->create($columns); //reference the class $referencer->referenceClass($class, array("topclass" => $topclass, "additionalProperties" => $additionalProperties)); if ($referencesAllTypes) { $referencer->referenceInstanceTypes($class); } } //insert the resources $startIndex = 0; $instancePackSize = 100; $instances = $class->getInstances(false, array('offset' => $startIndex, 'limit' => $instancePackSize)); $count = count($instances); $notDeletedInstances = array(); do { //reset timeout: //set_time_limit(30); \helpers_TimeOutHelper::setTimeOutLimit(\helpers_TimeOutHelper::MEDIUM); $rows = array(); foreach ($instances as $index => $resource) { if ($referencer->isResourceReferenced($resource)) { PersistenceProxy::forceMode(PERSISTENCE_HARD); $resource->delete(); PersistenceProxy::restoreImplementation(); } $row = array('uri' => $resource->getUri()); foreach ($properties as $property) { $propValue = $resource->getOnePropertyValue($property); $row[Utils::getShortName($property)] = $propValue; } $rows[] = $row; } $rowMgr = new RowManager($tableName, $columns); $rowMgr->insertRows($rows); foreach ($instances as $resource) { $referencer->referenceResource($resource, $tableName, null, true); if ($rmSources) { //remove exported resources in smooth sql, if required: // Be carefull, the resource can still exist even if // delete returns true. Indeed, modelIds can be mixed between // multiple models and only a part of the triples that consitute // the resource might have been deleted. if (!$resource->delete() || $resource->exists()) { //@TODO : modified resource::delete() because resource not in local modelId cannot be deleted $notDeletedInstances[] = $resource->getUri(); $startIndex++; } } } if (!$rmSources) { //increment start index only if not removed $startIndex += $instancePackSize; } //record hardened instances number if (isset($this->hardenedClasses[$class->getUri()])) { $this->hardenedClasses[$class->getUri()] += $count; } else { $this->hardenedClasses[$class->getUri()] = $count; } //update instance array and count value $instances = $class->getInstances(false, array('offset' => $startIndex, 'limit' => $instancePackSize)); foreach ($notDeletedInstances as $uri) { unset($instances[$uri]); } $count = count($instances); \helpers_TimeOutHelper::reset(); } while ($count > 0); $returnValue = true; // Treat subclasses of the current class if ($recursive) { foreach ($class->getSubClasses(true) as $subClass) { $returnValue = $this->hardify($subClass, array_merge($options, array('recursive' => false, 'append' => true))); } } //reset cache: $referencer->clearCaches(); // EXIT SMOOTH SQL MODE PersistenceProxy::restoreImplementation(); if (defined("DEBUG_PERSISTENCE") && DEBUG_PERSISTENCE) { $this->unhardify($class, array_merge($options, array('recursive' => false, 'removeForeigns' => false))); \common_Logger::d('unhardened result statements ' . $this->countStatements() . ' / ' . $countStatement); } // Give the normal rights on models to the session. core_kernel_persistence_smoothsql_SmoothModel::forceUpdatableModelIds($oldUpdatableModels); } catch (Exception $e) { \common_Logger::e('An error occured during hardification: ' . $e->getMessage()); core_kernel_persistence_smoothsql_SmoothModel::forceUpdatableModelIds($oldUpdatableModels); } return (bool) $returnValue; }
/** * (non-PHPdoc) * @see core_kernel_persistence_ClassInterface::createProperty() */ public function createProperty(\core_kernel_classes_Class $resource, $label = '', $comment = '', $isLgDependent = false) { $returnValue = null; // First we reference the property in smooth mode because meta models always remain there. $smoothReturnValue = \core_kernel_persistence_smoothsql_Class::singleton()->createProperty($resource, $label, $comment, $isLgDependent); if ($smoothReturnValue) { $property = new \core_kernel_classes_Property($resource->getUri()); $column = array('name' => HardapiUtils::getShortName($smoothReturnValue)); $column['multi'] = $isLgDependent ? true : false; // If no range set, we assume it is a literal. $column['foreign'] = false; // We do not know the range yet. $referencer = ResourceReferencer::singleton(); $classLocations = $referencer->classLocations($resource); foreach ($classLocations as $loc) { $tblmgr = new TableManager($loc['table']); $tblmgr->addColumn($column); } $returnValue = $smoothReturnValue; $referencer->clearCaches(); } else { $uri = $resource->getUri(); throw new Exception("An error occured when creating property in smooth mode '{$uri}' before handling the hard sql aspect."); } return $returnValue; }
/** * Test the referencer on properties, using the file caching mode * (it's the default caching mode for the properties) * @see oat\generisHard\models\hardapi\ResourceReferencer */ public function testPropertyReferencer() { $referencer = ResourceReferencer::singleton(); $this->assertIsA($referencer, 'oat\\generisHard\\models\\hardapi\\ResourceReferencer'); $referencer->setPropertyCache(ResourceReferencer::CACHE_FILE); $referencer->clearCaches(); $class = new core_kernel_classes_Class(CLASS_GENERIS_USER); $table = '_' . Utils::getShortName($class); // this part simulates a hardifying of the Userclass $myUserTblMgr = new TableManager($table); $this->assertFalse($myUserTblMgr->exists()); $this->assertTrue($myUserTblMgr->create(array(array('name' => '05label'), array('name' => '05comment'), array('name' => '07login'), array('name' => '07password'), array('name' => '07userMail'), array('name' => '07userFirstName'), array('name' => '07userLastName')))); $this->assertTrue($myUserTblMgr->exists()); $referencer->referenceClass($class); $this->assertTrue($referencer->isClassReferenced($class)); // test start on the cache containing the simulated data // in case of a fallback to the real sata (class_to_table) the tests fail $labelProperty = new core_kernel_classes_Property(RDFS_LABEL); $this->assertTrue($referencer->isPropertyReferenced($labelProperty)); $commentProperty = new core_kernel_classes_Property(RDFS_COMMENT); $this->assertTrue($referencer->isPropertyReferenced($commentProperty)); $loginProperty = new core_kernel_classes_Property(PROPERTY_USER_LOGIN); $this->assertTrue($referencer->isPropertyReferenced($loginProperty)); $passwordProperty = new core_kernel_classes_Property(PROPERTY_USER_PASSWORD); $this->assertTrue($referencer->isPropertyReferenced($passwordProperty)); $firstNameProperty = new core_kernel_classes_Property(PROPERTY_USER_FIRSTNAME); foreach ($referencer->propertyLocation($firstNameProperty) as $foundTable) { $this->assertEquals($foundTable, $table); } $this->assertTrue($myUserTblMgr->exists()); $referencer->unReferenceClass($class); $this->assertFalse($referencer->isClassReferenced($class)); $this->assertFalse($myUserTblMgr->exists()); // Testing the cache... $cache = common_cache_FileCache::singleton(); $serial = 'hard-api-property'; $this->assertTrue($cache->has($serial)); try { $cacheContent = $cache->get($serial); $this->assertTrue(is_array($cacheContent)); $this->assertTrue(count($cacheContent) > 0); $this->assertTrue(array_key_exists(RDFS_LABEL, $cacheContent)); $this->assertTrue(array_key_exists(PROPERTY_USER_LOGIN, $cacheContent)); } catch (common_cache_Exception $e) { $this->fail('Cannot access hard-api-property cache.'); } //clear the cache $cache->remove($serial); $this->assertFalse($cache->has($serial)); }
/** * Change a multi-valued property to a single-valued one. * * @access public * @author Jerome Bogaerts, <*****@*****.**> * @param Resource property The property to modifiy. * @param int batchSize Data must be transfered from the properties table to a given column. This parameter indicates the size of each pack of data transfered from the properties table to the column. * @return void */ public static function multipleToScalar(\core_kernel_classes_Resource $property, $batchSize = 100) { $referencer = ResourceReferencer::singleton(); $dbWrapper = \core_kernel_classes_DbWrapper::singleton(); $propertyDescription = self::propertyDescriptor($property); $propertyLocations = $referencer->propertyLocation($property); $propName = $propertyDescription['name']; $propUri = $property->getUri(); $propRanges = array(); foreach ($propertyDescription['range'] as $range) { // If no range provided, we assume it is a Literal. $propRanges[] = !empty($range) ? $range->getUri() : RDFS_LITERAL; } $offset = 0; foreach ($propertyLocations as $tblname) { $tblmgr = new TableManager($tblname); if ($tblmgr->exists()) { // Reset offset. $offset = 0; try { // We go from multiple to single. $toDelete = array(); // will contain ids of rows to delete in the 'properties table' after data transfer. // Add a column to the base table to receive single value. $baseTableName = str_replace('props', '', $tblname); $tblmgr->setName($baseTableName); $shortName = self::getShortName($property); $columnAdded = $tblmgr->addColumn(array('name' => $shortName, 'multi' => false)); if ($columnAdded == true) { // Now get the values in the props table. Group by instance ID in order to get only // one value to put in the target column. do { $hasResult = false; $retrievePropertyValue = empty($propRanges) || in_array(RDFS_LITERAL, $propRanges) ? true : false; $sql = 'SELECT "a"."id", "a"."instance_id", "a"."property_value", "a"."property_foreign_uri" FROM "' . $tblname . '" "a" '; $sql .= 'RIGHT JOIN (SELECT "instance_id", MIN("id") AS "id" FROM "' . $tblname . '" WHERE "property_uri" = ? '; $sql .= 'GROUP BY "instance_id") AS "b" ON ("a"."id" = "b"."id")'; $sql = $dbWrapper->limitStatement($sql, $batchSize, $offset); $result = $dbWrapper->query($sql, array($propUri)); // prepare the update statement. $sql = 'UPDATE "' . $baseTableName . '" SET "' . $shortName . '" = ? WHERE "id" = ?'; while ($row = $result->fetch()) { // Transfer to the 'base table'. $hasResult = true; $propertyValue = $retrievePropertyValue == true ? $row['property_value'] : $row['property_foreign_uri']; $dbWrapper->exec($sql, array($propertyValue, $row['instance_id'])); $toDelete[] = $row['id']; } $offset += $batchSize; } while ($hasResult === true); $inData = implode(',', $toDelete); $sql = 'DELETE FROM "' . $tblname . '" WHERE "id" IN (' . $inData . ')'; if ($dbWrapper->exec($sql) == 0) { // If an error occured or no rows removed, we // have a problem. $msg = "Cannot set multiplicity of Property '{$propUri}' because data transfered to the 'base table' could not be deleted"; throw new Exception($msg); } } else { $msg = "Cannot set multiplicity of Property '{$propUri}' because the corresponding 'base table' column could not be created."; throw new Exception($msg); } } catch (\PDOException $e) { $msg = "Cannot set multiplicity of Property '{$propUri}': " . $e->getMessage(); throw new Exception($msg); } } else { $msg = "Cannot set multiplicity of Property '{$propUri}' because the corresponding database location '{$tblname}' does not exist."; throw new Exception($msg); } } $referencer->clearCaches(); }