Valhalla: les nouvelles fonctions de sélection

Hello,

depuis peu, les modifications évoquées dans le billet précédent ont été réalisées. L’API a été modifiée pour toutes les fonctions qui permettent d’effectuer des sélections sur la base de donnée. Ce blog étant aussi en quelque sorte un carnet de laboratoire, je vais donc y expliquer le fonctionnement de ces fonctions ainsi que la manière de les utiliser. A terme, ces informations figureront directement dans la documentation (en anglais) de libvalhalla. Il est judicieux de consulter la documentation Doxygen déjà présente dans l’en-tête public (valhalla.h) qui peut être généré via un paramètre du script “configure”.

Cet article est long et très spécifique à libvalhalla du côté développeur uniquement. Si vous êtes un simple utilisateur d’Enna, vous ne verrez pas vraiment de différence par rapport à avant car les modifications sont majoritairement internes.

Il y a trois fonctions publiques et chacune est complémentaire aux autres.

int valhalla_db_metalist_get (
  valhalla_t *handle,
  valhalla_db_item_t *search,
  valhalla_db_restrict_t *restriction,
  int (*result_cb) (void *data,
                    valhalla_db_metares_t *res),
  void *data);

int valhalla_db_filelist_get (
  valhalla_t *handle,
  valhalla_file_type_t filetype,
  valhalla_db_restrict_t *restriction,
  int (*result_cb) (void *data,
                    valhalla_db_fileres_t *res),
  void *data);

int valhalla_db_file_get (
  valhalla_t *handle,
  int64_t id, const char *path,
  valhalla_db_restrict_t *restriction,
  valhalla_db_filemeta_t **res);

bird

valhalla_db_metalist_get

Utilisation simple

Elle permet de récupérer une liste de metadata. Cela veut dire, par exemple une liste d’albums, d’auteurs, d’artistes, de genres; peu importe le nom, vous pouvez récupérer n’importe quelle liste. Le résultat se présente toujours sous la forme du nom du champ (comme “author”) avec une valeur et un groupe. Sous la forme d’un tableau, cela donnerait quelque chose comme:

meta id meta name data id data value group
2 author 1665 Dido ENTITIES
2 author 456 Ennio Morricone ENTITIES
2 author 1257 John Williams ENTITIES
2 author 1224 Linkin Park ENTITIES

Dans cet exemple, uniquement les auteurs sont présents, mais il est également possible de demander une liste de metadata selon un groupe et non selon un nom de meta ou un id. Vous pouvez typiquement demander la liste de toutes les metadata qui appartiennent au group ENTITIES. De ce fait, vous trouverez la liste de tous les auteurs, artistes, compositeurs, studios, etc,…

Sans rentrer encore dans les détails du code, une telle liste se récupère de cette manière:

valhalla_db_item_t s = VALHALLA_DB_SEARCH_TEXT ("author", ENTITIES);
valhalla_db_metalist_get (handle, &s, NULL, callback, NULL);

Et sans expliquer en détail la fonction, l’argument “handle” correspond au pointeur sur votre instance de valhalla, “s”  (pour search) défini ce que vous cherchez, le troisième argument permet de spécifier des restrictions (qui sont expliquées dans le chapitre suivant), le callback est la fonction dans laquelle chaque ligne du résultat est envoyée et le dernier argument est un pointeur void qui peut être récupéré dans le callback.

Les macros  VALHALLA_DB_SEARCH_XXX sont présentes pour simplifier l’utilisation de libvalhalla. Elles ne sont pas indispensables. En réalité, elle ne font qu’attribuer les valeurs au bon endroit dans la structure valhalla_db_item_t. Actuellement il existe trois macros différentes:

VALHALLA_DB_SEARCH_ID(meta_id, group)
VALHALLA_DB_SEARCH_TEXT(meta_text, group)
VALHALLA_DB_SEARCH_GRP(group)

Vous pouvez donc chercher une liste de meta en fonction de l’id, du texte, ou alors vous pouvez récupérer plusieurs noms de metadata différents dans le cas d’une recherche selon un groupe.

Utilisation étendue

Récupérer une liste comme expliqué dans le chapitre précédent est la fonctionnalité la plus simple offerte par cette fonction. Il va de soit qu’il faut pouvoir donner quelques restrictions sur la liste retournée. Il est courant de ne pas vouloir une liste complète. Un exemple parlant concerne la metadata “album”. Il est intéressant de pouvoir récupérer tous les albums d’un auteur en particulier. Et c’est ici qu’interviennent les restrictions. Elles permettent d’inclure ou d’exclure certains résultats. D’un point de vue SQL, se sont des sous-requêtes qui limitent les résultats à leur ensemble, ou alors à tous les résultats différents de leur ensemble.

Voici un premier exemple. Vous voulez tous les albums de Dido:

valhalla_db_item_t s = VALHALLA_DB_SEARCH_TEXT ("album", TITLES);
valhalla_db_restrict_t r1 = VALHALLA_DB_RESTRICT_STR (IN, "author", "Dido");
valhalla_db_metalist_get (handle, &s, &r1, callback, NULL);

Le résultat sera un tableau de la même forme que vue précédemment, mais qui contiendra uniquement les albums de Dido.

meta id meta name data id data value group
3 album 1666 Life For Rent TITLES
3 album 1669 No Angel TITLES
3 album 1725 Safe Trip Home TITLES

Cette requête est déjà bien plus sophistiquée, car vous pouvez imaginer de donner ce genre de restriction pour n’importe quel metadata. Un autre exemple, vous pourriez lister tous les albums d’une certaine année. Il n’y a aucune limite sur les choix des restrictions. Mais ceci n’est pas suffisant, et il y a donc des possibilités un peu plus complexes afin d’avoir des résultats plus spécifiques.

Tout d’abord, dans l’exemple précédent, la macro VALHALLA_DB_RESTRICT est utilisée avec un premier argument “IN”. Il est possible d’inverser la logique en y inscrivant “NOTIN” à la place. Le résultat deviendrait alors la liste de tous les albums qui existent dans la base de donnée, sauf ceux de Dido.

Utilisation avancée

L’utilisation étendue permet déjà d’effectuer de nombreuse recherches intéressantes. Néanmoins pour les cas les plus précis, elle ne suffit pas. Il est nécessaire parfois de cumuler les restrictions pour affiner le résultats, ou tout simplement, pour avoir des résultats un peu plus exotiques. Ainsi les restrictions sont des listes chaînées, et il est possible de les lier très facilement. Commençons par un exemple, nous désirons la liste de tous les albums de Dido qui ne sont pas de 2003.

valhalla_db_item_t s = VALHALLA_DB_SEARCH_TEXT ("album", TITLES);
valhalla_db_restrict_t r1 = VALHALLA_DB_RESTRICT_STR (IN, "author", "Dido");
valhalla_db_restrict_t r2 = VALHALLA_DB_RESTRICT_STR (NOTIN, "year", "2003");
VALHALLA_DB_RESTRICT_LINK (r2, r1);
valhalla_db_metalist_get (handle, &s, &r1, callback, NULL);

Une nouvelle macro est donc introduite. Elle permet de lier très simplement les restrictions ensemble. La macro doit être comprise de cette manière. “link from r2 to r1”, soit lier de r2 à r1. Ainsi c’est r1 qui doit être spécifié dans la fonction valhalla_db_metalist_get(). Il n’y a aucune limite sur le nombre de liens possibles (mise à part qu’une requête ne doit pas dépasser 4096 caractères), néanmoins il ne faut pas tenter de lier toutes les restrictions sur la même car seul le dernier lien sera considéré. Avec trois restrictions, la bonne manière de procéder est donc:

valhalla_db_item_t s = VALHALLA_DB_SEARCH_TEXT ("album", TITLES);
valhalla_db_restrict_t r1 = VALHALLA_DB_RESTRICT_STR (IN, "author", "Linkin Park");
valhalla_db_restrict_t r2 = VALHALLA_DB_RESTRICT_STR (NOTIN, "year", "2003");
valhalla_db_restrict_t r3 = VALHALLA_DB_RESTRICT_STR (NOTIN, "genre", "Alternative");
VALHALLA_DB_RESTRICT_LINK (r3, r2);
VALHALLA_DB_RESTRICT_LINK (r2, r1);
valhalla_db_metalist_get (handle, &s, &r1, callback, NULL);

Ici, la liste concernera donc tous les albums de Linkin Park, qui ne sont pas de 2003 et qui ne sont pas du rock/metal alternatif. Okay, la liste ne risque pas d’être bien longue avec cet exemple :-P, mais ce qui compte c’est de montrer le principe. Les restrictions sont liées de la manière suivante: de r3 à r2 et de r2 à r1. En code ça donnerait donc:

r2.next = &r3;
r1.next = &r2;

Le code ci-dessus peut très bien remplacer la macro si vous préférez.

Il y a encore une macro que je n’ai pas vraiment présenté. Pour l’argument de recherche de la fonction, il est possible d’indiquer uniquement un groupe (comme je l’ai mentionné plus haut dans ce billet). Si le groupe est mit à NIL, il y aura toutes les metadata qui seront listées, autrement il n’y aura que les metadata liées au groupe.

Un dernier exemple qui retourne absolument toutes les metadata liées à Dido pour son album Life For Rent.

valhalla_db_item_t s = VALHALLA_DB_SEARCH_GRP (NIL);
valhalla_db_restrict_t r1 = VALHALLA_DB_RESTRICT_STR (IN, "author", "Dido");
valhalla_db_restrict_t r2 = VALHALLA_DB_RESTRICT_STR (IN, "album", "Life For Rent");
VALHALLA_DB_RESTRICT_LINK (r2, r1);
valhalla_db_metalist_get (handle, &s, &r1, callback, NULL);
meta id meta name data id data value group
6 track 19 1 ORGANIZATIONAL
6 track 225 10 ORGANIZATIONAL
6 track 229 11 ORGANIZATIONAL
6 track 242 12 ORGANIZATIONAL
6 track 92 2 ORGANIZATIONAL
5 year 50 2003 TEMPORAL
6 track 202 3 ORGANIZATIONAL
6 track 101 4 ORGANIZATIONAL
6 track 223 5 ORGANIZATIONAL
6 track 96 6 ORGANIZATIONAL
6 track 231 7 ORGANIZATIONAL
6 track 196 8 ORGANIZATIONAL
6 track 187 9 ORGANIZATIONAL
1 title 1673 BonusTrack TITLES
2 author 1665 Dido ENTITIES
1 title 1668 Do You Have A Little Time TITLES
1 title 1672 Dont Leave Home TITLES
1 title 1666 Life For Rent TITLES
3 album 1666 Life For Rent TITLES
1 title 1678 Marys In India TITLES
4 genre 104 Pop CLASSIFICATION
1 title 1670 Sand In My Shoes TITLES
1 title 1669 See The Sun TITLES
1 title 1674 See You When Youre 40 TITLES
1 title 1675 Stoned TITLES
1 title 1671 This Land Is Mine TITLES
1 title 1664 White Flag TITLES
1 title 1667 Who Makes You Feel TITLES

Et d’un point de vue SQL

La requête SQL qui effectue les sélections est créée en fonction des arguments de la fonction. Elle se construit comme des lego qui s’imbriquent les uns dans les autres. D’une manière général, sa forme est:

SELECT meta.meta_id, data.data_id, meta.meta_name, data.data_value
FROM (
 data INNER JOIN assoc_file_metadata AS assoc
 ON data.data_id = assoc.data_id
) INNER JOIN meta
ON assoc.meta_id = meta.meta_id
-- Les conditions sont optionnelles
WHERE
  -- Une sous-requête apparait par le biais des restrictions.
  -- Il y a autant de sous-requêtes que de restrictions du type IN, NOT IN.
  assoc.file_id <IN|NOT IN> (
    SELECT assoc.file_id
    FROM (
      data INNER JOIN assoc_file_metadata AS assoc
      ON data.data_id = assoc.data_id
    ) INNER JOIN meta
    ON assoc.meta_id = meta.meta_id
    -- A choix, il peut y avoir un ID ou du texte.
    WHERE meta.<meta_id|meta_name> = <ID|"TEXT"> AND data.<data_id|data_value> = <ID|"TEXT">
  )
  -- L'argument search indique quel meta et/ou quel groupe doit être listé
  AND meta.<meta_id|meta_name> = <ID|"TEXT">
  AND assoc._grp_id = <ID>
GROUP BY assoc.meta_id, assoc.data_id
ORDER BY data.data_value;

Sa longueur varie fortement selon le nombre de conditions. Il est possible de visualiser les requêtes générées par Valhalla en utilisant la verbosité maximale de la bibliothèque.

bird

valhalla_db_filelist_get

Utilisation simple

La récupération d’une liste de fichiers ressemble à celle pour les metadata. La différence vient principalement du deuxième argument qui fait référence au type de fichier (audio, video, etc, …). Ce qui limite ensuite la liste se sont les restrictions. L’utilisation étant exactement la même, je vais aller droit au but avec des exemples.

Pour lister la totalité des fichiers référencés dans la base de donnée, rien de plus simple:

valhalla_db_filelist_get (handle, VALHALLA_FILE_TYPE_NULL, NULL, callback, NULL);

Le fait d’indiquer un type de fichier NULL, permet d’ignorer le type. Le résultat de cette requête correspond à un tableau de ce genre qui contiendrait la totalité de l’arborescence:

id path filetype
101 /home/foo/bar/dido/life_for_rent/sand_in_my_shoes.m4a AUDIO
102 /home/foo/bar/dido/life_for_rent/white_flag.m4a AUDIO
103 /home/foo/bar/dido/life_for_rent/who_makes_you_feel.m4a AUDIO

Avec les restrictions

Il est ensuite facile de faire du tri avec les restrictions, de la même manière que pour la fonction précédente. Par exemple, pour lister tous les fichiers d’Ennio Morricone qui ne sont référencés dans aucun album.

valhalla_db_restrict_t r1 = VALHALLA_DB_RESTRICT_STR (IN, "author", "Ennio Morricone");
valhalla_db_restrict_t r2 = VALHALLA_DB_RESTRICT_STR (NOTIN, "album", NULL);
VALHALLA_DB_RESTRICT_LINK (r2, r1);
valhalla_db_filelist_get (handle, VALHALLA_FILE_TYPE_NULL, &r1, callback, NULL);

Et ci-dessous, un autre exemple qui correspond à la catégorie “Non Classé” dans Enna. Tous les fichiers qui ne sont ni dans un album, ni lié à un author et dont le genre est indéfini.

valhalla_db_restrict_t r1 = VALHALLA_DB_RESTRICT_STR (NOTIN, "album", NULL);
valhalla_db_restrict_t r2 = VALHALLA_DB_RESTRICT_STR (NOTIN, "author", NULL);
valhalla_db_restrict_t r3 = VALHALLA_DB_RESTRICT_STR (NOTIN, "genre", NULL);
VALHALLA_DB_RESTRICT_LINK (r3, r2);
VALHALLA_DB_RESTRICT_LINK (r2, r1);
valhalla_db_filelist_get (handle, VALHALLA_FILE_TYPE_NULL, &r1, callback, NULL);

Facile, non?

A noter que ce n’est pas encore totalement optimal, car le groupe ne peut pas être donné avec les restrictions. Ainsi n’importe quel metadata de n’importe quel groupe est considérée dans l’inclusion comme l’exclusion.

Et d’un point de vue SQL

La aussi, la requête ressemble beaucoup à la précédente avec des différences au niveau de ce qui est sélectionné et dans quelle table.

SELECT file_id, file_path, _type_id
FROM file AS assoc
-- Les conditions sont optionnelles
WHERE
  -- Une sous-requête apparait par le biais des restrictions.
  -- Il y a autant de sous-requêtes que de restrictions du type IN, NOT IN.
  assoc.file_id <IN|NOT IN> (
    SELECT assoc.file_id
    FROM (
      data INNER JOIN assoc_file_metadata AS assoc
      ON data.data_id = assoc.data_id
    ) INNER JOIN meta
    ON assoc.meta_id = meta.meta_id
    -- A choix, il peut y avoir un ID ou du texte.
    WHERE meta.<meta_id|meta_name> = <ID|"TEXT"> AND data.<data_id|data_value> = <ID|"TEXT">
  )
  -- Le type n'est pas inscrit dans la requête si la valeur est NULL.
  AND _type_id = <ID>
ORDER BY file_id;

Si dans la clause FROM, la table “file” créer un alias “assoc”, c’est uniquement pour une question de factorisation dans le code par rapport à la sous-requête.

bird

valhalla_db_file_get

Maintenant que l’on a vu comment récupérer des listes de metadata ainsi que des listes de fichiers, il est important de pouvoir aussi récupérer des metadata en fonction d’un fichier spécifique. Cette possibilité n’existait pas avec l’ancien modèle car les fichiers étaient toujours retournés dans une liste. Ici il est question d’un seul fichier à la fois. Il faut également être conscient que cette fonction est liée uniquement à la base de donnée, si un fichier non référencé dans la base mais existant quelque part est donné en argument, aucun résultat ne sera retourné (cette possibilité existera quand j’implémenterai le “scan on-demand”).

Les arguments sont donc l’id du fichier, le chemin du fichier, des restrictions et le pointeur sur la structure de résultat. Il n’y a pas de callback pour l’utilisateur car le résultat est limité à un seul fichier (en réalité il y a un callback interne qui récupère toutes les metadata car le vrai résultat est sur plusieurs lignes).

Les metadata sont disponibles dans une structure chaînée. Je dois également introduire une nouvelle possibilité des restrictions. Mais tout d’abord, voici un exemple:

valhalla_db_filemeta_t *metadata = NULL;
valhalla_db_restrict_t r1 = VALHALLA_DB_RESTRICT_STR (EQUAL, "track", NULL);
valhalla_db_restrict_t r2 = VALHALLA_DB_RESTRICT_STR (EQUAL, "title", NULL);
VALHALLA_DB_RESTRICT_LINK (r2, r1);
valhalla_db_file_get (handle, 0, "/home/foo/bar/dido/life_for_rent/white_flag.m4a", &r1, &metadata);
/* ... */
VALHALLA_DB_FILEMETA_FREE (metadata);

L’argument EQUAL dans la macro VALHALLA_DB_RESTRICT sert à limiter les résultats à des metadata précises. Sans les restrictions, la structure metadata contiendrait absolument toutes les metadata. Mais dans cet exemple, la structure contient uniquement le track et le titre. A noter qu’utiliser IN ou NOTIN ici ça n’a aucun sens. De même qu’utiliser EQUAL pour les deux fonctions précédentes (voir la conclusion).

Il faut être conscient que plusieurs tracks et plusieurs titres peuvent être retournés pour un même fichier. Car rien n’exclus que certains noms de metadata n’existent pas dans différents groupes. Et comme je l’ai dis plus haut, il n’est pour le moment pas possible de limiter une restriction à un groupe. Néanmoins le groupe est un champ de la structure valhalla_db_filemeta_t, il est donc possible de faire un tri à postériori.

Après utilisation des metadata, il ne faut pas oublier de les libérer avec VALHALLA_DB_FILEMETA_FREE().

Et d’un point de vue SQL

SELECT file.file_id, assoc._grp_id, meta.meta_id, data.data_id, meta.meta_name, data.data_value
FROM ((
    file INNER JOIN assoc_file_metadata AS assoc
    ON file.file_id = assoc.file_id
  ) INNER JOIN data
  ON data.data_id = assoc.data_id
) INNER JOIN meta
ON assoc.meta_id = meta.meta_id
-- Les conditions sont optionnelles
WHERE
  -- Une condition apparait par le biais des restrictions.
  -- Il y a autant de conditions que de restrictions du type EQUAL.
  -- A choix, il peut y avoir un ID ou du texte. Chaque restriction
  -- est séparée par un OR.
  (
    (meta.<meta_id|meta_name> = <ID|"TEXT"> AND data.<data_id|data_value> = <ID|"TEXT">)
    <OR> ...
  )
  -- Il faut l'id d'un fichier ou un path
  AND file.<file_id|file_path> = <ID|"PATH">
;

Cette requête retourne autant de lignes qu’il y a de metadata. Mais pour l’utilisateur, tout est transparent car les résultats sont tous retournés dans une structure chaînée.

En conclusion

Les possibilités de ce nouveau système sont très larges par rapport à l’ancien. Bien qu’il y ait encore quelques manques pour pouvoir spécifier des groupes avec les restrictions, il est possible de retrouver les données avec peu de lignes de code et simplement, tout en ayant des requêtes relativement complexes qui se créer en interne. Il y aura forcément des modifications à l’avenir, je tâcherais de garder à jour cet article.

Et pour en revenir aux restrictions et à l’argument qui peut prendre la valeur IN, NOTIN ou EQUAL, il faut comprendre que c’est avant tout une question de factorisation en interne. IN et NOTIN devraient être utilisés uniquement avec les deux premières fonctions présentées qui récupèrent des listes, et EQUAL devrait être utilisé uniquement pour la récupération des metadata d’un fichier. Je vais continuer à travailler sur ces éléments dans les jours à venir afin d’éviter des confusions.

A bientôt,

Mathieu SCHROETER

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s