From aa71d2af816b2e86da4dc43e18df86047af0d94b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc=20Boull=C3=A9?= Date: Tue, 9 Jul 2024 18:00:09 +0200 Subject: [PATCH] Implement coexistence of created tables with and without keys KWDRRelationCreationRule - clarification des comemntaire sur l'utilisation des cles et de Root pour les dictionnaire des tables creees par des regles En resume: - un dictionnaire peut ne pas avoir de cle s'il est cree - verification a priori lors de la lecture d'un fichier dictionnaire - verification des cles pour les variables natives (non calculee) de type relation - contrainte uniquement si le dictionnaire utilisant a une cle - cree une contrainte sur la table calculee - peut ne pas avoir de cle - si elle une cle, ses sous-table doivent egalement en avoir - verification a posteriori sur l'usage d'une flocon via le choix d'une dictionaire d'analyse - ok si coherence des cle, permet la lecture a partir de fichier - ko si des variable natives ne peuvent etre lues en rasion d'un manque de cle - gestion des tables externes (Root) - interdiction de creer une table associee a un dictionnaire externe (Root) - sinon, incoherence de la gestion des references au objets externes, non rpesents dans des fichiers - attention, lors de l'analyse recursive d'une dictuionnaire d'analyse - on se limite aux table natives pour en deduire le flcon - il faut par contre parcourir entierement les tables calculees ou non pour en deduire les tables externes a utliser Impacts - interdiction de creer une table Root - KWDRRelationCreationRule::CheckOperandsCompleteness - correction pour prendre en compte les regles associees a des blocs d'attributs - KWAttribute::GetReference - tolerance sur la verification des cles - KWAttribute::Check - amelioration des messages d'erreur et de warning - KWAttribute::Check - KWClass:CheckClassComposition - extension en prenant en compte les tables externes references depuis des attributs table creees par des regles - KWClass::ComputeOverallNativeRelationAttributeNumber - KWMTDatabase::CreateMapping --- src/Learning/KWData/KWAttribute.cpp | 17 ++-- src/Learning/KWData/KWAttribute.h | 8 +- src/Learning/KWData/KWClass.cpp | 89 +++++++++++++++---- src/Learning/KWData/KWMTDatabase.cpp | 76 ++++++++++++---- src/Learning/KWData/KWMTDatabase.h | 6 +- .../KWData/KWRelationCreationRule.cpp | 8 ++ src/Learning/KWData/KWRelationCreationRule.h | 16 +++- src/Learning/MODL/MODL.cpp | 5 +- 8 files changed, 179 insertions(+), 46 deletions(-) diff --git a/src/Learning/KWData/KWAttribute.cpp b/src/Learning/KWData/KWAttribute.cpp index dd0b252e..418db36a 100644 --- a/src/Learning/KWData/KWAttribute.cpp +++ b/src/Learning/KWData/KWAttribute.cpp @@ -293,18 +293,23 @@ boolean KWAttribute::Check() const } // La classe utilisee doit avoir une cle dans le cas d'un attribut natif + // si la classe utilisante a elle meme une cle + // Exception dans le cas d'une classe sans-cle pouvant etre issue d'une regle de creation de table, + // auquel cas les cles ne sont pas necessaire y compris dans le cas d'un flocon creer par des regles if (GetAnyDerivationRule() == NULL and attributeClass != NULL and - attributeClass->GetKeyAttributeNumber() == 0) + attributeClass->GetKeyAttributeNumber() == 0 and GetParentClass() != NULL and + GetParentClass()->GetKeyAttributeNumber() > 0) { - AddError("The used dictionary " + attributeClass->GetName() + " should have a key"); + AddError("The used dictionary " + attributeClass->GetName() + + " should have a key as the parent dictionary " + parentClass->GetName() + + " has a key"); bOk = false; } - // La cle de la classe utilisee doit etre au moins aussi longue que // celle de la classe utilisante dans le cas d'un lien de composition - if (not GetReference() and GetAnyDerivationRule() == NULL and attributeClass != NULL and - parentClass != NULL and not attributeClass->GetRoot() and - attributeClass->GetKeyAttributeNumber() < parentClass->GetKeyAttributeNumber()) + else if (not GetReference() and GetAnyDerivationRule() == NULL and attributeClass != NULL and + parentClass != NULL and not attributeClass->GetRoot() and + attributeClass->GetKeyAttributeNumber() < parentClass->GetKeyAttributeNumber()) { AddError("In used dictionary (" + attributeClass->GetName() + "), the length of the key (" + IntToString(attributeClass->GetKeyAttributeNumber()) + diff --git a/src/Learning/KWData/KWAttribute.h b/src/Learning/KWData/KWAttribute.h index dc51ae35..c72bf35e 100644 --- a/src/Learning/KWData/KWAttribute.h +++ b/src/Learning/KWData/KWAttribute.h @@ -322,11 +322,15 @@ inline void KWAttribute::SetStructureName(const ALString& sValue) inline boolean KWAttribute::GetReference() const { + KWDerivationRule* kwdrAnyRule; + require(KWType::IsRelation(GetType())); - if (kwdrRule == NULL) + + kwdrAnyRule = GetAnyDerivationRule(); + if (kwdrAnyRule == NULL) return false; else - return kwdrRule->GetReference(); + return kwdrAnyRule->GetReference(); } inline void KWAttribute::SetDerivationRule(KWDerivationRule* kwdrValue) diff --git a/src/Learning/KWData/KWClass.cpp b/src/Learning/KWData/KWClass.cpp index 1cf7b1d6..c57a0f09 100644 --- a/src/Learning/KWData/KWClass.cpp +++ b/src/Learning/KWData/KWClass.cpp @@ -611,10 +611,15 @@ int KWClass::ComputeOverallNativeRelationAttributeNumber(boolean bIncludingRefer int nOverallNativeRelationAttributeNumber; ObjectDictionary odReferenceClasses; ObjectArray oaImpactedClasses; + ObjectDictionary odAnalysedCreatedClasses; KWClass* kwcImpactedClass; KWClass* kwcRefClass; int nClass; KWAttribute* attribute; + KWClass* kwcTargetClass; + ObjectArray oaUsedClass; + KWClass* kwcUsedClass; + int nUsedClass; // On part de la classe de depart kwcImpactedClass = cast(KWClass*, this); @@ -648,6 +653,52 @@ int KWClass::ComputeOverallNativeRelationAttributeNumber(boolean bIncludingRefer // Ajout de la classe a analyser oaImpactedClasses.Add(kwcRefClass); } + // Cas d'un attribut issue d'une regle de creation de table, pour rechercher + // les classes referencees depuis les tables creees par des regles + else if (bIncludingReferences and not attribute->GetReference()) + { + assert(attribute->GetDerivationRule() != NULL); + + // Recherche de la classe cible + kwcTargetClass = GetDomain()->LookupClass( + attribute->GetDerivationRule()->GetObjectClassName()); + assert(kwcTargetClass != NULL); + + // Analyse uniquement si la classe cible na pas deja ete analysees + if (odAnalysedCreatedClasses.Lookup(kwcTargetClass->GetName()) == NULL) + { + // Memorisation de la classe cible + odAnalysedCreatedClasses.SetAt(kwcTargetClass->GetName(), + kwcTargetClass); + + // Recherche de toutes les classe utilisee recursivement + kwcTargetClass->BuildAllUsedClasses(&oaUsedClass); + + // Recherches des classes externes + for (nUsedClass = 0; nUsedClass < oaUsedClass.GetSize(); + nUsedClass++) + { + kwcUsedClass = + cast(KWClass*, oaUsedClass.GetAt(nUsedClass)); + + // Ajout de la classe a analyser si elle ne l'a pas deja ete + if (kwcUsedClass->GetRoot()) + { + if (odReferenceClasses.Lookup( + kwcUsedClass->GetName()) == NULL) + { + nOverallNativeRelationAttributeNumber++; + + // Memorisation de la classe externe pour ne pas faire l'analyse plusieurs fois + odReferenceClasses.SetAt( + kwcUsedClass->GetName(), + kwcUsedClass); + oaImpactedClasses.Add(kwcUsedClass); + } + } + } + } + } // Prise en compte dans le cas d'une classe referencee else if (bIncludingReferences and attribute->GetAnyDerivationRule()->GetName() == @@ -659,8 +710,7 @@ int KWClass::ComputeOverallNativeRelationAttributeNumber(boolean bIncludingRefer { nOverallNativeRelationAttributeNumber++; - // Memorisation de la classe externe pour ne pas faire l'analyse - // plusieurs fois + // Memorisation de la classe externe pour ne pas faire l'analyse plusieurs fois odReferenceClasses.SetAt(kwcRefClass->GetName(), kwcRefClass); oaImpactedClasses.Add(kwcRefClass); } @@ -2561,6 +2611,7 @@ boolean KWClass::CheckClassComposition(KWAttribute* parentAttribute, NumericKeyD KWAttribute* attribute; KWClass* parentClass; KWClass* attributeClass; + ALString sTmp; require(nkdComponentClasses != NULL); @@ -2598,25 +2649,27 @@ boolean KWClass::CheckClassComposition(KWAttribute* parentAttribute, NumericKeyD break; } - // La cle de la classe utilisee doit etre strictement plus longue que - // celle de la classe utilisante dans le cas d'un lien de composition multiple a trois niveau - // (ou plus) + // La cle de la classe utilisee doit etre strictement plus longue que celle de la classe utilisante + // dans le cas d'un lien de composition multiple a trois niveau (ou plus) + // Exception dans le cas d'une classe sans-cle pouvant etre issue d'une regle de creation de table, + // auquel cas les cles ne sont pas necessaire y compris dans le cas d'un flocon creer par des regles assert(parentClass == NULL or parentClass->GetKeyAttributeNumber() <= GetKeyAttributeNumber()); - assert(GetKeyAttributeNumber() <= attributeClass->GetKeyAttributeNumber()); if (parentClass != NULL and parentAttribute->GetType() == KWType::ObjectArray and - parentClass->GetKeyAttributeNumber() == GetKeyAttributeNumber()) + parentClass->GetKeyAttributeNumber() > 0 and + parentClass->GetKeyAttributeNumber() >= GetKeyAttributeNumber()) { - AddError("The length of a key in a dictionary used as a Table, having a sub-" + - KWType::ToString(attribute->GetType()) + - " in its composition, must be strictly greater than that of its parent " - "Dictionary;\n " + - "in dictionary " + parentClass->GetName() + " (key length=" + - IntToString(parentClass->GetKeyAttributeNumber()) + "), dictionary " + - GetName() + "(key length = " + IntToString(GetKeyAttributeNumber()) + - ") is used as a Table in variable " + parentAttribute->GetName() + - ", and uses dictionary " + attributeClass->GetName() + " with variable " + - attribute->GetName() + " as a sub-" + KWType::ToString(attribute->GetType()) + - " in its composition."); + AddError(sTmp + "As dictionary " + parentClass->GetName() + " owns variable " + + parentAttribute->GetName() + " of type " + + KWType::ToString(parentAttribute->GetType()) + "(" + + parentAttribute->GetClass()->GetName() + ") and dictionary " + + parentAttribute->GetClass()->GetName() + " itself owns variable " + + attribute->GetName() + " of type " + KWType::ToString(attribute->GetType()) + + "(" + attribute->GetClass()->GetName() + "), the key length in dictionary " + + GetName() + " (key length = " + IntToString(GetKeyAttributeNumber()) + + ") must be strictly greater than that of its parent " + "dictionary " + + parentClass->GetName() + + " (key length = " + IntToString(parentClass->GetKeyAttributeNumber()) + ")"); bOk = false; } } diff --git a/src/Learning/KWData/KWMTDatabase.cpp b/src/Learning/KWData/KWMTDatabase.cpp index cddf3faa..218f2e7e 100644 --- a/src/Learning/KWData/KWMTDatabase.cpp +++ b/src/Learning/KWData/KWMTDatabase.cpp @@ -255,6 +255,7 @@ void KWMTDatabase::UpdateMultiTableMappings() ObjectArray oaPreviousMultiTableMappings; ObjectDictionary odReferenceClasses; ObjectArray oaRankedReferenceClasses; + ObjectDictionary odAnalysedCreatedClasses; KWClass* referenceClass; KWMTDatabaseMapping* mapping; KWMTDatabaseMapping* previousMapping; @@ -297,8 +298,9 @@ void KWMTDatabase::UpdateMultiTableMappings() oaRootRefTableMappings.SetSize(0); // Creation du mapping de la table principale - rootMultiTableMapping = CreateMapping(&odReferenceClasses, &oaRankedReferenceClasses, mainClass, - mainClass->GetName(), "", &oaMultiTableMappings); + rootMultiTableMapping = + CreateMapping(&odReferenceClasses, &oaRankedReferenceClasses, &odAnalysedCreatedClasses, mainClass, + mainClass->GetName(), "", &oaMultiTableMappings); // Parcours des classes referencee pour creer leur mapping // Ce mapping des classes referencees n'est pas effectuee dans le cas d'une base en ecriture @@ -316,7 +318,8 @@ void KWMTDatabase::UpdateMultiTableMappings() oaAllMainCreatedMappings.Add(oaCreatedMappings); // Creation du mapping et memorisation de tous les mapping des sous-classes - mapping = CreateMapping(&odReferenceClasses, &oaRankedReferenceClasses, referenceClass, + mapping = CreateMapping(&odReferenceClasses, &oaRankedReferenceClasses, + &odAnalysedCreatedClasses, referenceClass, referenceClass->GetName(), "", oaCreatedMappings); } @@ -528,7 +531,7 @@ boolean KWMTDatabase::CheckPartially(boolean bWriteOnly) const " variables)"); } - // Passage a la classe suivante dans la path + // Passage a la classe suivante dans le path if (bOk) pathClass = attribute->GetClass(); @@ -1321,7 +1324,8 @@ boolean KWMTDatabase::IsPhysicalObjectSelected(KWObject* kwoPhysicalObject) } KWMTDatabaseMapping* KWMTDatabase::CreateMapping(ObjectDictionary* odReferenceClasses, - ObjectArray* oaRankedReferenceClasses, KWClass* mappedClass, + ObjectArray* oaRankedReferenceClasses, + ObjectDictionary* odAnalysedCreatedClasses, KWClass* mappedClass, const ALString& sDataPathClassName, const ALString& sDataPathAttributeNames, ObjectArray* oaCreatedMappings) @@ -1330,9 +1334,14 @@ KWMTDatabaseMapping* KWMTDatabase::CreateMapping(ObjectDictionary* odReferenceCl KWMTDatabaseMapping* mapping; KWMTDatabaseMapping* subMapping; KWAttribute* attribute; + KWClass* kwcTargetClass; + ObjectArray oaUsedClass; + KWClass* kwcUsedClass; + int nUsedClass; require(odReferenceClasses != NULL); require(oaRankedReferenceClasses != NULL); + require(odAnalysedCreatedClasses != NULL); require(odReferenceClasses->GetCount() == oaRankedReferenceClasses->GetSize()); require(mappedClass != NULL); require(sDataPathClassName != ""); @@ -1360,25 +1369,62 @@ KWMTDatabaseMapping* KWMTDatabase::CreateMapping(ObjectDictionary* odReferenceCl if (KWType::IsRelation(attribute->GetType()) and attribute->GetClass() != NULL) { // Cas d'un attribut natif de la composition (sans regle de derivation) - if (not attribute->GetReference() and attribute->GetAnyDerivationRule() == NULL) + if (attribute->GetAnyDerivationRule() == NULL) { // Creation du mapping dans une nouvelle table de mapping temporaire if (sDataPathAttributeNames != "") - subMapping = CreateMapping(odReferenceClasses, oaRankedReferenceClasses, - attribute->GetClass(), sDataPathClassName, - sDataPathAttributeNames + '`' + attribute->GetName(), - oaCreatedMappings); + subMapping = CreateMapping( + odReferenceClasses, oaRankedReferenceClasses, odAnalysedCreatedClasses, + attribute->GetClass(), sDataPathClassName, + sDataPathAttributeNames + '`' + attribute->GetName(), oaCreatedMappings); else - subMapping = CreateMapping(odReferenceClasses, oaRankedReferenceClasses, - attribute->GetClass(), sDataPathClassName, - attribute->GetName(), oaCreatedMappings); + subMapping = + CreateMapping(odReferenceClasses, oaRankedReferenceClasses, + odAnalysedCreatedClasses, attribute->GetClass(), + sDataPathClassName, attribute->GetName(), oaCreatedMappings); // Chainage du sous-mapping mapping->GetComponentTableMappings()->Add(subMapping); } + // Cas d'un attribut issue d'une regle de creation de table, pour rechercher + // les classes referencees depuis les tables creees par des regles + else if (not attribute->GetReference()) + { + assert(attribute->GetAnyDerivationRule() != NULL); + + // Recherche de la classe cible + kwcTargetClass = mappedClass->GetDomain()->LookupClass( + attribute->GetDerivationRule()->GetObjectClassName()); + assert(kwcTargetClass != NULL); + + // Analyse uniquement si la classe cible na pas deja ete analysees + if (odAnalysedCreatedClasses->Lookup(kwcTargetClass->GetName()) == NULL) + { + // Memorisation de la classe cible + odAnalysedCreatedClasses->SetAt(kwcTargetClass->GetName(), kwcTargetClass); + // Recherche de toutes les classe utilisee recursivement + kwcTargetClass->BuildAllUsedClasses(&oaUsedClass); + + // Recherches des classes externes + for (nUsedClass = 0; nUsedClass < oaUsedClass.GetSize(); nUsedClass++) + { + kwcUsedClass = cast(KWClass*, oaUsedClass.GetAt(nUsedClass)); + + // Memorisation des mapping a traiter dans le cas de classe externes + if (kwcUsedClass->GetRoot()) + { + if (odReferenceClasses->Lookup(kwcUsedClass->GetName()) == NULL) + { + odReferenceClasses->SetAt(kwcUsedClass->GetName(), + kwcUsedClass); + oaRankedReferenceClasses->Add(kwcUsedClass); + } + } + } + } + } // Cas d'un attribut natif reference (avec regle de derivation predefinie) - else if (attribute->GetReference() and attribute->GetDerivationRule() != NULL and - attribute->GetDerivationRule()->GetName() == referenceRule.GetName()) + else if (attribute->GetAnyDerivationRule()->GetName() == referenceRule.GetName()) { // Memorisation du mapping a traiter if (odReferenceClasses->Lookup(attribute->GetClass()->GetName()) == NULL) diff --git a/src/Learning/KWData/KWMTDatabase.h b/src/Learning/KWData/KWMTDatabase.h index 87a77cde..77e8ce8f 100644 --- a/src/Learning/KWData/KWMTDatabase.h +++ b/src/Learning/KWData/KWMTDatabase.h @@ -147,9 +147,11 @@ class KWMTDatabase : public KWDatabase // Le tableaux mapping exhaustifs est egalement egalement mis a jour // Les classes referencees sont memorisees dans un dictionnaire, pour gerer les mappings externes a creer // ulterieurement Les mappings crees recursivement sont memorises dans un tableau + // Les classes crees analysees sont egalement memorisees dans un dictionnaire, pour eviter des analyse multiples KWMTDatabaseMapping* CreateMapping(ObjectDictionary* odReferenceClasses, ObjectArray* oaRankedReferenceClasses, - KWClass* mappedClass, const ALString& sDataPathClassName, - const ALString& sDataPathAttributeNames, ObjectArray* oaCreatedMappings); + ObjectDictionary* odAnalysedCreatedClasses, KWClass* mappedClass, + const ALString& sDataPathClassName, const ALString& sDataPathAttributeNames, + ObjectArray* oaCreatedMappings); ///////////////////////////////////////////////////////////////////////////////// // Gestion des objets natifs references diff --git a/src/Learning/KWData/KWRelationCreationRule.cpp b/src/Learning/KWData/KWRelationCreationRule.cpp index e7f89518..76f29c39 100644 --- a/src/Learning/KWData/KWRelationCreationRule.cpp +++ b/src/Learning/KWData/KWRelationCreationRule.cpp @@ -499,6 +499,14 @@ boolean KWDRRelationCreationRule::CheckOperandsCompleteness(const KWClass* kwcOw kwcTargetClass = kwcOwnerClass->GetDomain()->LookupClass(GetObjectClassName()); assert(kwcTargetClass != NULL); + // La class cible ne doit pas etre Root + if (kwcTargetClass->GetRoot()) + { + AddError(sTmp + "Invalid output dictionary " + kwcTargetClass->GetName() + + " that cannot be a root dictionary, dedicated to managing external tables"); + bOk = false; + } + // Gestion de l'alimentation de type calcul via les operandes en sortie if (bOk and GetOutputOperandNumber() > 0) { diff --git a/src/Learning/KWData/KWRelationCreationRule.h b/src/Learning/KWData/KWRelationCreationRule.h index 1f035d42..398aa03f 100644 --- a/src/Learning/KWData/KWRelationCreationRule.h +++ b/src/Learning/KWData/KWRelationCreationRule.h @@ -29,9 +29,23 @@ class KWDRTableCreationRule; // - alimentation de type calcul // - le dictionnaire en sortie peut avoir ses variable utilisees ou non (Used), // et des variable calculee si besoin -// - un dictionnaire en sortie n'est pas specifique a aux regle de creation de Table: +// - un dictionnaire en sortie n'est pas specifique aux regles de creation de Table: // a priori, un meme dictionnaire pourrait etre utilise pour une variable native alimentee // depuis un fichier, et pour une variable calculee alimentee par une regle de creation +// - quelques restrictions neanmoins vis a vis des cles qui servent a lire les donnees a partir de fichiers +// - un dictionnaire en sortie ne peut etre Root: ce cas est reserve aux tables externes, et cela ne pourrait +// pas etre traite correctement si des objet de type Root etaient cree au fil de l'eau +// - un dictionnaire en sortie peut par contre avoir des cles ou non +// - par exemple, si on construit un flocon de donnees a partir d'un champs json (base NoSql), les +// entites et tables correspondantes n'ont pas de cle +// - consequence: si un dictionnaire en sortie est une tete de flocon, il ne pourra pas etre utilise en +// dictionnaire d'analyse, car on ne saurait pas l'alimenter a partir d'une base multi-table de fichiers +// - cela declenchera une erreur non pas a la lecture du dictionnaire (flocon autorise pour des tabkes construite) +// mais a l'utilisation suite au choix d'un dictionnaire d'analyse, par exemple pour un apprentissage +// - contrainte: si une dictionnaire a une cle, ses sous-dictionnaires de flocon doivent egalement avoir des cles +// - cela limite un peu l'expressivite de la creation de table +// - mais cela permet d'avoir le maximum de detection d'erreur des la lecture d'un fichier dictionnaire, +// en reduisant le nombre d'erreurs diagnostiquees au moment de l'utilisation d'un dictionnaire d'analyse // // Alimentation de type vue: // - par defaut, le premier operande de ce type de regle est de type relation, ce qui permet diff --git a/src/Learning/MODL/MODL.cpp b/src/Learning/MODL/MODL.cpp index edfa4199..5fce36b6 100644 --- a/src/Learning/MODL/MODL.cpp +++ b/src/Learning/MODL/MODL.cpp @@ -15,7 +15,7 @@ void SetWindowsDebugDir(const ALString& sDatasetFamily, const ALString& sDataset // A parametrer pour chaque utilisateur // Devra etre fait plus proprement quand tout l'equipe sera sur git, par exemple via une variable // d'environnement et quelques commentaires clairs - sUserRootPath = "C:/Applications/boullema/LearningTest.V10.5.0-b.1/TestKhiops/"; + sUserRootPath = "C:/Applications/boullema/LearningTest.V10.5.1-b.0/TestKhiops/"; // Pour permettre de continuer a utiliser LearningTest, on ne fait rien s'il y a deja un fichier test.prm // dans le repertoire courante @@ -38,7 +38,8 @@ int main(int argc, char** argv) // Choix du repertoire de lancement pour le debugage sous Windows (a commenter apres fin du debug) // SetWindowsDebugDir("Standard", "IrisLight"); // SetWindowsDebugDir("Standard", "IrisU2D"); - SetWindowsDebugDir("z_TableCreationRules", "EntityCreatedWithoutKey"); + // SetWindowsDebugDir("z_TableCreationRules", "NoKeyInCreatedSnowflake"); + SetWindowsDebugDir("z_TableCreationRules", "TableNoKey"); // Parametrage des logs memoires depuis les variables d'environnement, pris en compte dans KWLearningProject // KhiopsMemStatsLogFileName, KhiopsMemStatsLogFrequency, KhiopsMemStatsLogToCollect