Skip to content

Commit

Permalink
Merge pull request #494 from KhiopsML/490-implement-coexistence-of-cr…
Browse files Browse the repository at this point in the history
…eated-table-with-and-without-keys

490 implement coexistence of created table with and without keys
  • Loading branch information
marcboulle authored Jan 2, 2025
2 parents f7c42b5 + 110b87f commit 0b7eecc
Show file tree
Hide file tree
Showing 31 changed files with 397 additions and 173 deletions.
2 changes: 1 addition & 1 deletion src/Learning/KWData/JSONFile.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ class JSONFile : public TextService
// potentielles entres les caracteres ansi initiaux recodes en utf8 avec windows1252/iso8859-1
// et les caracteres utf8 initiaux presents dans windows1252/iso8859-1.
// En cas de collision, les deux champs supplementaires sont ajoutes dans le json pour aide
// au diagnostique:
// au diagnostic:
// . ansi_chars: tableau des caracteres ansi utilises
// . colliding_utf8_chars: tableau des caracteres utf8 initiaux utilises dans windows1252/iso8859-1.
boolean Close();
Expand Down
27 changes: 0 additions & 27 deletions src/Learning/KWData/KWAttribute.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -282,33 +282,6 @@ boolean KWAttribute::Check() const
KWType::ToString(GetType()) + " should not be a root dictionary");
bOk = false;
}

// 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 and GetParentClass() != NULL and
GetParentClass()->GetKeyAttributeNumber() > 0)
{
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
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()) +
" variables) should not be inferior to that of the parent dictionary (" +
parentClass->GetName() + " with a key of " +
IntToString(parentClass->GetKeyAttributeNumber()) + " variables)");
bOk = false;
}
}

// Nom de structure si type Structure
Expand Down
158 changes: 114 additions & 44 deletions src/Learning/KWData/KWClass.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ KWClass::KWClass()
bRoot = false;
bForceUnique = false;
bIsUnique = false;
bIsKeyBasedStorable = false;
lClassHashValue = 0;
nHashFreshness = 0;
nFreshness = 0;
Expand Down Expand Up @@ -371,7 +372,10 @@ void KWClass::IndexClass()
ivUsedDenseAttributeNumbers.Initialize();
ivUsedSparseAttributeNumbers.Initialize();

// A priori, il y a unicite dans le cas d'une classe racine, ou si l'unicite est forcee (cf methode SetForceUnique)
// Calcul de la capacite a etre stocke, en verifiant la coherence des cle en mode non verbeux
bIsKeyBasedStorable = CheckNativeComposition(true, false);

// Il y a unicite dans le cas d'une classe racine, ou si l'unicite est forcee
bIsUnique = bRoot or bForceUnique;

// Indexage des tableaux d'attributs par parcours de la liste
Expand Down Expand Up @@ -607,6 +611,7 @@ void KWClass::IndexClass()
cout << " Root\t" << BooleanToString(GetRoot()) << "\n";
cout << " ForceUnique\t" << BooleanToString(GetForceUnique()) << "\n";
cout << " IsUnique\t" << BooleanToString(IsUnique()) << "\n";
cout << " IsKeyBasedStorable\t" << BooleanToString(IsKeyBasedStorable()) << "\n";
WriteAttributes(" Used attributes", &oaUsedAttributes, cout);
WriteAttributes(" Loaded attributes", &oaLoadedAttributes, cout);
WriteAttributes(" Loaded dense attributes", &oaLoadedDenseAttributes, cout);
Expand Down Expand Up @@ -1532,6 +1537,7 @@ void KWClass::CopyFrom(const KWClass* aSource)
bRoot = aSource->bRoot;
bForceUnique = aSource->bForceUnique;
bIsUnique = aSource->bIsUnique;
bIsKeyBasedStorable = aSource->bIsKeyBasedStorable;

// Duplication des meta-donnees
metaData.CopyFrom(&aSource->metaData);
Expand Down Expand Up @@ -1598,7 +1604,6 @@ boolean KWClass::Check() const
KWAttribute* attribute;
int i;
NumericKeyDictionary nkdKeyAttributes;
NumericKeyDictionary nkdComponentClasses;
ALString sTmp;

assert(odAttributes.GetCount() == olAttributes.GetCount());
Expand Down Expand Up @@ -1643,13 +1648,6 @@ boolean KWClass::Check() const
GetNextAttribute(attribute);
}

// Verification de la coherence entre statut racine/composant et presence de cle
if (bResult and GetRoot() and GetKeyAttributeNumber() == 0)
{
bResult = false;
AddError(sTmp + "Root dictionary should have a key");
}

// Verification de la cle
if (bResult and GetKeyAttributeNumber() > 0)
{
Expand Down Expand Up @@ -1703,9 +1701,10 @@ boolean KWClass::Check() const
}
}

// Verification de l'absence de cycle dans la composition
// Verification de l'absence de cycle dans la composition, et de la validite des cle
// dans le cas d'une classe racine
if (bResult)
bResult = CheckClassComposition(NULL, &nkdComponentClasses);
bResult = CheckNativeComposition(GetRoot(), true);

return bResult;
}
Expand Down Expand Up @@ -2660,9 +2659,20 @@ void KWClass::InitializeRandomRuleParameters(KWDerivationRule* rule, const ALStr
}
}

boolean KWClass::CheckClassComposition(KWAttribute* parentAttribute, NumericKeyDictionary* nkdComponentClasses) const
boolean KWClass::CheckNativeComposition(boolean bCheckKeys, boolean bVerboseCheckKeys) const
{
NumericKeyDictionary nkdComponentClasses;

// On passe en parametre le dictionnaire nkdComponentClasses de classes traitees, pour detecter les cycles
return InternalCheckNativeComposition(bCheckKeys, bVerboseCheckKeys, NULL, &nkdComponentClasses);
}

boolean KWClass::InternalCheckNativeComposition(boolean bCheckKeys, boolean bVerboseCheckKeys,
KWAttribute* parentAttribute,
NumericKeyDictionary* nkdComponentClasses) const
{
boolean bOk = true;
boolean bClassKeyCheckedOnce;
KWAttribute* attribute;
KWClass* parentClass;
KWClass* attributeClass;
Expand All @@ -2676,59 +2686,111 @@ boolean KWClass::CheckClassComposition(KWAttribute* parentAttribute, NumericKeyD
parentClass = parentAttribute->GetParentClass();

// Ajout de la classe courante dans le dictionnaire des classes utilisees
// pour l'analyse de l'utilisation recursive
nkdComponentClasses->SetAt(this, (Object*)this);

// Verification de la coherence entre statut racine/composant et presence de cle
bClassKeyCheckedOnce = false;
if (bOk and bCheckKeys and GetRoot() and GetKeyAttributeNumber() == 0)
{
if (bVerboseCheckKeys)
AddError(sTmp + "Root dictionary must a key");
bClassKeyCheckedOnce = true;
bOk = false;
}

// Parcours des attributs de la classe
attribute = GetHeadAttribute();
while (attribute != NULL)
{
// Traitement des attributs de composition
if (KWType::IsRelation(attribute->GetType()) and not attribute->GetReference() and
attribute->GetAnyDerivationRule() == NULL and attribute->GetClass() != NULL)
// Traitement des attributs de composition native
if (KWType::IsRelation(attribute->GetType()) and attribute->GetAnyDerivationRule() == NULL and
attribute->GetClass() != NULL)
{
attributeClass = attribute->GetClass();

// Test si necessaire de la presence de cle sur la classe courante si c'est la classe principale
if (bOk and bCheckKeys and parentClass == NULL and not bClassKeyCheckedOnce and
GetKeyAttributeNumber() == 0)
{
if (bVerboseCheckKeys)
AddError(sTmp +
"Dictionary must have a key, as it is composed of relation "
"variables, for example " +
attribute->GetName() + " of type " + RelationTypeToString(attribute));
bClassKeyCheckedOnce = true;
bOk = false;
}

// Test si classe deja utilisee
if (nkdComponentClasses->Lookup(attributeClass) == attributeClass)
if (bOk and nkdComponentClasses->Lookup(attributeClass) == attributeClass)
{
AddError("Existing composition cycle caused by the recursive use of dictionary " +
attributeClass->GetName() + " by variable " + attribute->GetName());
bOk = false;
break;
}

// Propagation du test si ok
else
{
bOk = attributeClass->CheckClassComposition(attribute, nkdComponentClasses);
if (not bOk)
break;
}
if (bOk)
bOk = attributeClass->InternalCheckNativeComposition(bCheckKeys, bVerboseCheckKeys,
attribute, nkdComponentClasses);

// 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());
if (parentClass != NULL and parentAttribute->GetType() == KWType::ObjectArray and
parentClass->GetKeyAttributeNumber() > 0 and
parentClass->GetKeyAttributeNumber() >= GetKeyAttributeNumber())
// Verification des cle si demande
if (bOk and bCheckKeys)
{
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;
// La classe utilisee doit avoir une cle
if (attributeClass->GetKeyAttributeNumber() == 0)
{
if (bVerboseCheckKeys)
AddError(sTmp + "The dictionary related to variable " +
attribute->GetName() + " of type " +
RelationTypeToString(attribute) + " must have 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
else if (attributeClass->GetKeyAttributeNumber() < GetKeyAttributeNumber())
{
if (bVerboseCheckKeys)
AddError(sTmp + "The key length (" +
IntToString(attributeClass->GetKeyAttributeNumber()) +
") of the dictionary related to variable " +
attribute->GetName() + " of type " +
RelationTypeToString(attribute) +
" must not be less than that of its parent dictionary " +
GetName() + " (" + IntToString(GetKeyAttributeNumber()) + ")");
bOk = false;
}
// 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 necessaires y compris dans le cas d'un flocon cree par des regles
else if (parentClass != NULL and parentAttribute->GetType() == KWType::ObjectArray and
parentClass->GetKeyAttributeNumber() > 0 and
parentClass->GetKeyAttributeNumber() >= GetKeyAttributeNumber())
{
if (bVerboseCheckKeys)
AddError(sTmp + "As dictionary " + parentClass->GetName() +
" has a variable " + parentAttribute->GetName() + " of type " +
RelationTypeToString(parentAttribute) + " and dictionary " +
parentAttribute->GetClass()->GetName() +
" itself has a variable " + attribute->GetName() +
" of type " + RelationTypeToString(attribute) +
", the key length (" + IntToString(GetKeyAttributeNumber()) +
") in dictionary " + GetName() +
" must be strictly greater than that of its parent "
"dictionary " +
parentClass->GetName() + " (" +
IntToString(parentClass->GetKeyAttributeNumber()) + ")");
bOk = false;
}
}
}

// Arret des la premiere erreur
if (not bOk)
break;

// Attribut suivant
GetNextAttribute(attribute);
}
Expand All @@ -2738,6 +2800,14 @@ boolean KWClass::CheckClassComposition(KWAttribute* parentAttribute, NumericKeyD
return bOk;
}

const ALString KWClass::RelationTypeToString(const KWAttribute* attribute) const
{
require(attribute != NULL);
require(KWType::IsRelation(attribute->GetType()));
require(attribute->GetClass() != NULL);
return KWType::ToString(attribute->GetType()) + '(' + attribute->GetClass()->GetName() + ')';
}

boolean KWClass::CheckTypeAtLoadIndex(KWLoadIndex liIndex, int nType) const
{
boolean bOk;
Expand Down
59 changes: 52 additions & 7 deletions src/Learning/KWData/KWClass.h
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,14 @@ class KWClass : public Object
// Cette caracteristique est calculee au moment de l'indexation de la classe
boolean IsUnique() const;

// Capacite a etre stocke sur un systeme de fichiers multi-tables a l'aide de cles
// Dans le cas de classes construites via des regles de derivation, on a pas necessairement des cles,
// et on ne peut charger en memoire les instances correspondante via des fichiers de donnees
boolean IsKeyBasedStorable() const;

// Verification de la capacite a etre stocke, pour afficher les erreurs si necessaire
boolean CheckKeyBasedStorability() const;

/////////////////////////////////////////////////////////////
// Specification des attributs de la cle de la classe
// Facultatif: utile dans pour les chainage entre classes dans le cas
Expand Down Expand Up @@ -524,13 +532,32 @@ class KWClass : public Object
void InitializeRandomRuleParameters(KWDerivationRule* rule, const ALString& sAttributeName,
int& nRuleRankInAttribute);

// Verification de l'integrite de la classe en ce qui concerne sa composition
// Il ne doit pas y avoir de cycle dans le graphe des utilisation entre classes par composition
// La taille des cles doit etre croissante avec la profondeur d'utilisation dans la composition
// L'attribut parent en parametre (potentiellement NULL) fournit des informations sur la profondeur
// dans l'arbre de composition, ce qui permet de tester la coherence de longueur des cle
// Le dictionnaire de classes en parametre permet de detecter les cycles
boolean CheckClassComposition(KWAttribute* parentAttribute, NumericKeyDictionary* nkdComponentClasses) const;
// Verification de l'integrite de la classe en ce qui concerne sa composition native,
// c'est a dire de sa hierarchie induite par l'ensemble des attributs relations non calcules,
// et si demande la coherence de son utilisation des cles dans la hierarchie pour permettre
// de rendre les donnees stockable sur une base multi-table a base de fichiers
//
// Il ne doit pas y avoir de cycle dans le graphe des utilisations entre classes par composition
// Le parametre de verification des cles, avec ou sans message d'erreur, permet de verifier qu'un
// dictionnaire est stockable sur disque via un mapping multi-fichier, au moyen de cles coherentes.
// - le dictionnaire doit avoir une cle s'il contient des sous-tables
// - la taille des cles doit etre croissante avec la profondeur d'utilisation dans la composition
// Note que l'on peut avoir des dictionnaires non stockage dans le cas de regles de derivation de creation
// de table, qui peuvent exploiter des dictionnaire quelconques (non Root), avec ou sans cle
boolean CheckNativeComposition(boolean bCheckKeys, boolean bVerboseCheckKeys) const;

// Methode interne utilisee par CheckNativeComposition, avec des parametres techniques supplementaire
// permettant son implementation de facon recursive
// - parentAttribute: correspond a l'attribut utilisant la classe (NULL pour l'appel initial),
// ce qui fournit des informations sur la profondeur dans l'arbre de composition, et qui
// permet de tester la coherence de longueur des cles
// - nkdComponentClasses: dictionnaire de classes traitees permettant de detecter les cycles
boolean InternalCheckNativeComposition(boolean bCheckKeys, boolean bVerboseCheckKeys,
KWAttribute* parentAttribute,
NumericKeyDictionary* nkdComponentClasses) const;

// Type d'une variable relationnelles complet sous forme d'une chaine de caracteres
const ALString RelationTypeToString(const KWAttribute* attribute) const;

// Attributs Symbol charges en memoire, pour optimiser leur destruction et mutation
// Attention, seuls les attributs Symbol dense sont concernes. Les attributs faisant
Expand Down Expand Up @@ -654,6 +681,9 @@ class KWClass : public Object
ObjectArray oaDatabaseDataItemsToCompute;
ObjectArray oaDatabaseTemporayDataItemsToComputeAndClean;

// Capacite a etre stocke sur un systeme de fichiers multi-tables a l'aide de cles
boolean bIsKeyBasedStorable;

// Valeur de hash de la classe, bufferise avec une fraicheur
// Ces variables sont mutable, car modifiee par ComputeHashValue()
mutable longint lClassHashValue;
Expand Down Expand Up @@ -723,6 +753,21 @@ inline boolean KWClass::IsUnique() const
return bIsUnique;
}

inline boolean KWClass::IsKeyBasedStorable() const
{
require(IsIndexed());
return bIsKeyBasedStorable;
}

inline boolean KWClass::CheckKeyBasedStorability() const
{
boolean bOk;
require(IsIndexed());
bOk = CheckNativeComposition(true, true);
ensure(bOk == bIsKeyBasedStorable);
return bIsKeyBasedStorable;
}

inline int KWClass::GetKeyAttributeNumber() const
{
return svKeyAttributeNames.GetSize();
Expand Down
2 changes: 1 addition & 1 deletion src/Learning/KWData/KWContinuous.h
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ class KWContinuous : public Object
// Test de validite du numero d'erreur
static boolean CheckError(int nValue);

// Libelle d'erreur (en anglais) associe a un diagnostique de conversion
// Libelle d'erreur (en anglais) associe a un diagnostic de conversion
static const ALString ErrorLabel(int nError);

// Methode utilitaire pour tranformer le separateur decimal ',' en '.'
Expand Down
2 changes: 1 addition & 1 deletion src/Learning/KWData/KWDerivationRuleOperand.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -359,7 +359,7 @@ boolean KWDerivationRuleOperand::CheckCompleteness(const KWClass* kwcOwnerClass)
}

// Verification eventuelle de l'attribut si la classe de scope est correcte
// On effectue cette verification meme en cas d'erreur, pour avoir un diagnostique potentiellement plus precis
// On effectue cette verification meme en cas d'erreur, pour avoir un diagnostic potentiellement plus precis
if (bResult and GetOrigin() == OriginAttribute)
{
assert((GetScopeLevel() == 0 and scopeClass == kwcOwnerClass) or
Expand Down
Loading

0 comments on commit 0b7eecc

Please sign in to comment.