HOWTO développement Sashipa

 

 Fonctionnalités avancées des selectQueryBuilder en Sashipa.

 

Vue générale

Un élément selectQueryBuilder est le générateur d'une requête SQL. Ce même élément peut définir complètement une requête, ou être utilisé pour compléter une requête déjà créée par un composant. Il n'est donc pas toujours complet : par exemple on ne spécifie jamais de clause select dans la requête d'un listForm : c'est le listForm qui s'en charge.

Voici le squelette d'une requête selectQueryBuilder :

  <selectQueryBuilder distinctRequired='no' type='list'>
    <castFilterSet>
      ...
    </castFilterSet>
    <selectStatementBuilder>
      ...
    </selectStatementBuilder>
    <fromStatementBuilder>
      ...
    </fromStatementBuilder>
    <whereStatementBuilder>
      ...
    </whereStatementBuilder>
  </selectQueryBuilder>

Le détail des éléments contenus est dans la suite du document. On remarque d'ores et déjà les attributs généraux :

  • distinctRequired : Par défaut il est à 'no'. Le mettre à 'yes' provoquera l'ajout d'un DISTINCT dans la clause Select générée, ou bien la création d'une clause GROUP BY si besoin. On remarque que même si la valeur est à 'no', le simple ajout d'une colonne d'agrégat dans la clause select provoquera la génération d'une requête d'agrégat quand même.

  • type : Trois types de requêtes : list pour les requêtes renvoyant plusieurs lignes, card pour une requête de sélection des valeurs d'un cardForm (une seule ligne), et agregat qui renvoie une unique ligne aussi, mais en reprenant des caractéristiques d'une requête de liste (notamment au sujet du paramètrage par défaut des castFilters). Cet attribut peut la plupart du temps être déduit par défaut lors de la compilation, en fonction du contexte.

 

Les castFilters

Une requête est filtrée (ou non) par le filtre de l'écran qui la contient. Un filtre étant une valeur de clef primaire. Un castFilter est un critère qui viendra s'ajouter dans la clause Where de la requête SQL générée. Ce critère est de type 'colonnes_clef_primaire = filtre' ou bien 'colonnes_clef_etrangere = filtre', ou bien encore une composition de sous-castFilters.

On spécifie un ensemble de castFilters pour la requête. Lorsque la requête reçoit un filtre, elle tente de l'appliquer à chacun jusqu'à ce que l'un d'entre eux accepte le filtre. Elle prend le critère du premier qui l'accepte.

Voyons un exemple :

  <castFilterSet filterSensitive='yes' autoCastFilter='yes' 
                 stuckWhenNoFilter='no'>
    <fkCastFilter>
      <instanceFk schemaFk='fk_tblVoyage_tblPersonne' />
    </fkCastFilter>
    <pkCastFilter>
      <instanceTable schemaTable='tblVoyage' />
    </pkCastFilter>
  </castFilterSet>

Les attributs de l'élément castFilterSet prennent leur valeur par défaut si on ne les précise pas. Je les ai mis ici avec leur valeur par défaut.

  • filterSensitive : Si l'on met cet attribut à 'no', alors les castFilters spécifiés ici ne seront plus pris en compte.

  • autoCastFilter : Par défaut (sauf en mode 'card'), chaque clef étrangère et primaire de chaque table de la clause from ajoute ses castFilters pour la requête. On peut annuler ce comportement gràce à cet attribut.

  • stuckWhenNoFilter : Lorsque aucun filtre n'est disponible pour la requête (cas d'une fiche en mode ajout ou dans un écran accessible à partir d'un menu non filtré), cet attribut permet de bloquer l'exécution de la requête (et donc de renvoyer une liste vide de résultat).

 

Clause Select

Une clause select est composée d'instances de colonnes ou de groupes d'instances de colonnes. Voici une clause select qui contient un élément de chaque type disponible. Elle est basée sur une clause from fictive dont la mainInstanceTable est la table 'tblPersonne'.

  <selectStatementBuilder autoPrimaryKey='yes'>
    <instanceColumnList>
      <instanceColumn schemaColumn='tblPersonne_Nom' />
      <referencedUserKeyColumns join='inner'>
        <instanceFk schemaFk='fk_tblCongresparticipant_tblCongres' />
      </referencedUserKeyColumns>
      <agregatInstanceColumn type='count' schemaFk='fk_tblVoyage_tblPersonne' 
                             letterCount='6' sort='desc'>
        <title>Voyages</title>
      </agregatInstanceColumn>
      <instanceFk schemaFk='fk_tblPersonne_tblGenre' />
      <manualColumn>                            <!-- Syntaxe specifique MySQL -->
        <schemaColumnRef schemaColumn='tblPersonne_NumeroParAnnee' />
        <contentOfExpression>ifnull(max(NumeroParAnnee), 0)+1</contentOfExpression>
      </manualColumn>
    </instanceColumnList>
  </selectStatementBuilder>

Par défaut dans les requêtes de type 'list', les colonnes de la clef primaire de la table principale de la clause From, sont incluses dans la clause Select. On peut annuler ce comportement en positionnant l'attribut autoPrimaryKey à 'no'.

Une instanceColumn est une colonne d'une instanceTable. Elle contient donc l'instance de table qu'elle référence (son surnom doit correspondre à une instance de table de la clause from). Si l'on ne spécifie pas d'instance de table, c'est celle par défaut de la schemaTable qui est utilisée.

L'élément referencedUserKeyColumns est un raccourci d'écriture. Il rajoute les instances de colonnes qui composent la userKey de la table référencée ET il ajoute la jointure dans la clause from. Sans qu'il soit nécessaire de le faire.

L'élément agregatInstanceColumn crée une colonne calculée. Il en existe deux sortes : celles de type 'count' qui dénombre simplement des enregistrements liés, et celles opérant des calculs sur des colonnes. Dans ce dernier cas, il est nécessaire de décrire la jointure de la table dépendante manuellement dans la clause from (cf exemple ici). Sinon ce n'est pas nécessaire.

L'élément instanceFk ajoute les instance de colonnes qui composent une clef étrangère.

On peut enfin écrire manuellement une expression SQL gràce à l'élément manualColumn. Dans le cas d'une requête de sélection qui renvoie la valeur par défaut d'une colonne pour le mode ajout, on spécifie la schemaColumn qui est concernée, puis le contenu de l'expression. Attention : l'emploi de cet élément peut empècher la compatibilité de votre code Sashipa avec divers SGBD.

 

Clause From

Voyons la syntaxe d'une clause from. Une clause from est composée d'un élément mainInstanceTable et d'une liste de fkJoin pour les jointures.

  <fromStatementBuilder>
    <mainInstanceTable schemaTable='tblPersonne' />

    <fkJoin join='inner'>
      <instanceFk schemaFk='fk_tblPersonne_tblGenre' />
    </fkJoin>

    <fkJoin join='left'>
      <instanceFk schemaFk='fk_tblPersonne_tblPays_Residence'>
        <startInstanceTable schemaTable='tblPersonne' />
      </instanceFk>
      <referencedInstanceTable schemaTable='tblPays' nickName='pays_residence' />
      <XxxCriteriaXxx> ... </XxxCriteriaXxx>
    </fkJoin>

    <fkJoin join='inner'>
      <instanceFk schemaFk='fk_tblPersonne_tblPays_Nationalite'>
        <startInstanceTable schemaTable='tblPersonne' />
      </instanceFk>
      <referencedInstanceTable schemaTable='tblPays' nickName='pays_naissance' />
    </fkJoin>
  </fromStatementBuilder>

Chaque instance de table (éléments mainInstanceTable, startInstanceTable, referencedInstanceTable) ont un attribut obligatoire schemaTable, et un surnom éventuel dans la requête SQL.

Les jointures (éléments fkJoin) ont un attribut précisant le type (inner, left, right, full, nojoin). On utilisera le plus souvent 'left' si la clef étrangère peut être nulle, et 'inner' si la clef étrangère est obligatoirement renseignée.

Les instances de tables sont optionnelles, car elles peuvent être déduites par défaut des clefs étrangères. On les précise si l'on souhaite les renommer dans la requête SQL. Ici on précise un startInstanceTable non renommé uniquement à titre d'exemple, pour montrer à quel endroit on le met.

Le critère est optionnel aussi. S'il existe, il sera ajouté au critère de jointure avec un ET logique (le AND en SQL). Les critères sont décrits plus bas dans ce document, avec la clause Where.

 

Clause Where

Une clause where contient une suite de constantCriteria, manualCriteria, et/ou de criteriaBuilder. Le séparateur logique est obligatoirement le AND. Si vous désirez ajouter des critères avec des OR, il faut les mettre dans un criteriaBuilder dont le mode est 'or'.

 

Clause Where : les critères constantCriteria

Vous pouvez spécifier un critère SQL de façon semi-manuelle, par exemple :

  <whereStatementBuilder>
    <constantCriteria>
      <instanceColumn schemaColumn='adr_ContactAdresseLabel' />
      <endOfCriteria>is not null</endOfCriteria>
    </constantCriteria>
  </whereStatementBuilder>

Le critère généré est la concaténation de l'instance de colonne et du contenu de l'élément endOfCriteria.

Le contenu de endOfCriteria est libre, vous pouvez y mettre le critère SQL que vous voulez. Attention cependant si vous souhaitez changer ultérieurement de SGBD : ce critère peut provoquer des incompatibilités.

 

Clause Where : les critères manualCriteria

Vous pouvez spécifier un critère SQL de façon complètement manuelle, par exemple :

  <whereStatementBuilder>
    <manualCriteria>
      <contentOfCriteria>ContactAdresseLabel is not null</contentOfCriteria>
      <instanceColumnList>
        <instanceColumn schemaColumn='adr_ContactAdresseLabel' />
      </instanceColumnList>
    </manualCriteria>
  </whereStatementBuilder>

Remarques :

  • Le contenu de l'élément contentOfCriteria est inséré tel quel dans la requête SQL.

  • Le contenu de contentOfCriteria est libre, vous pouvez y mettre le critère SQL que vous voulez. Attention cependant si vous souhaitez changer ultérieurement de SGBD : ce critère peut provoquer des incompatibilités.

  • La liste des instanceColumn utilisée doit correspondre aux colonnes utilisées dans l'élément contentOfCriteria. Cette liste sert à savoir si la requête est concernée ou non par un rafraîchissement des données.

 

Clause Where : les critères criteriaBuilder

Un criteriaBuilder est un critère composé d'un ensemble de sous-critères. Ces sous-critères peuvent être des constantCriteria, des manualCriteria, des researchCriteria (voir explication plus bas) ou même d'autres criteriaBuilder.

  <whereStatementBuilder>
    <criteriaBuilder mode='and' emptyAction='stuck'
                     defaultSubEmptyAction='ignore'>

      <!-- ... sous-criteres ... -->

    </criteriaBuilder>
  </whereStatementBuilder>

Voyons les attributs :

  • mode peut prendre la valeur 'or' ou 'and'. C'est le séparateur SQL des sous-attributs.

  • emptyAction peut prendre les valeurs 'ignore' ou 'stuck'. C'est le comportement du criteriaBuilder lorsqu'il est vide : il peut bloquer (stuck) la requête (qui renverra alors une liste vide sans s'exécuter) ou annuler (ignore) le critère.

  • defaultSubEmptyAction définit la valeur par défaut de l'emptyAction des sous-critères de type researchCriteria. Il peut prendre les valeurs 'ignore', 'stuck' ou 'accepte'. Cette dernière valeur signifie que le sous-researchCriteria se construit même avec une valeur indéfinie. Ce qui permet d'effectuer des recherches SQL de type 'colonne_prenom is null'.

 

Clause Where : les critères researchCriteria

Un researchCriteria est un critère dynamique qui se modifie en fonction d'un champ. Pour d'obscures raisons historiques, il ne peut être contenu directement par la clause Where. On le met donc dans un élément criteriaBuilder. Je montre ici deux researchCriteria, le premier va prendre une valeur dans un fkField, l'autre dans un champ simple :

  <whereStatementBuilder>
    <criteriaBuilder mode='and' emptyAction='stuck'
                     defaultSubEmptyAction='ignore'>
      
      <researchCriteria emptyAction='stuck'>
        <castSchemaValue>
          <fkCastFilter>
            <instanceFk schemaFk='fk_tblPersonne_tblGenre' />
          </fkCastFilter>
        </castSchemaValue>
        <fieldRef field='fieldFkGenre' />
      </researchCriteria>

      <researchCriteria>
        <castSchemaValue>
          <instanceColumn schemaColumn='tblPersonne_Nom' />
        </castSchemaValue>
        <fieldRef field='fieldNom' />
      </researchCriteria>
      
    </criteriaBuilder>
  </whereStatementBuilder>

Un élément researchCriteria est composé d'un castSchemaValue et d'une référence à un champ du même écran (screen). Ce champ doit être défini et nommé auparavant (dans un researchForm ou un cardForm par exemple).

Un élément castSchemaValue peut réceptionner des valeurs de clefs étrangères ou primaires (au moyen de castFilter) ou bien des valeurs de colonnes directement (avec une instanceColumn).

Dans cet exemple, le premier researchCriteria demande une valeur (emptyAction='stuck') et bloquera le contenu entier du criteriaBuilder si le champ 'fieldFkGenre' est vide. De plus, le criteriaBuilder bloquera la requête (ou son criteriaBuilder parent) s'il est vide. En revanche, le deuxième researchCriteria n'apparaîtra dans la requête que si le champ 'fieldNom' contient une valeur. Mais il sera ignoré sinon et la requête sera tout de même générée.

 

© Copyright 2003 Sashipa-Melba Team. Ce document de la technologie Sashipa-Melba est sous licence GNU FDL Vous pouvez le copier et modifier librement les copies tant que cette mention apparaît clairement.