Back to flin
flin

Filtrage et tri temporels

Comment nous avons ajouté le filtrage et le tri aux requêtes d'historique temporel de FLIN -- de la décision de conception d'éviter les lambdas à l'implémentation VM de ListFilterField et ListOrderBy.

Juste A. Gnimavo (Thales) & Claude | March 26, 2026 4 min flin
EN/ FR/ ES
flintemporalfilteringorderingqueries

Avoir un historique complet des versions pour chaque entité est puissant, mais la puissance brute sans précision n'est que du bruit. Quand un produit a cinquante changements de prix sur six mois, on ne veut pas les cinquante versions -- on veut les versions où le prix dépassait un seuil, triées par date.

La session 082 a complété la catégorie TEMP-4 (requêtes d'historique) à cent pour cent en implémentant les trois dernières tâches : filtrage par valeurs de champs, combinaison de filtres et tri des résultats.

La question des lambdas

Nous avons considéré la syntaxe à base de closures pour le filtrage. Nous l'avons rejetée pour trois raisons.

Premièrement, FLIN n'avait pas le support des lambdas. Implémenter des closures aurait été un détour de plusieurs sessions.

Deuxièmement, le filtrage par champs couvre quatre-vingt-quinze pour cent des cas d'usage. Quand les développeurs filtrent les historiques de versions, ils filtrent presque toujours par valeur de champ.

Troisièmement, une syntaxe plus simple est meilleure pour le public cible de FLIN. Une méthode qui prend un nom de champ, un opérateur et une valeur est plus immédiatement lisible qu'une expression lambda.

La décision : implémenter .where_field(field, operator, value) et .order_by(field, direction) comme méthodes dédiées.

La conception de l'API

flin// Filtering
history.where_field("price", ">", 15.0)
history.where_field("status", "==", "active")

// Ordering
history.order_by("price", "asc")
history.order_by("created_at", "desc")

// Chaining
history.where_field("price", ">=", 12.0).order_by("price", "desc")

Six opérateurs de comparaison : ==, !=, <, <=, >, >=. Deux directions de tri : "asc" et "desc".

Implémentation : deux nouveaux opcodes

ListFilterField (0xF4)

L'opcode ListFilterField dépile quatre valeurs de la pile : la liste, le nom du champ, la chaîne d'opérateur et la valeur de comparaison. Il itère sur la liste, extrait le champ nommé de chaque élément, applique la comparaison et empile une nouvelle liste contenant uniquement les éléments correspondants.

rustOpCode::ListFilterField => {
    let value_val = self.pop()?;
    let op_val = self.pop()?;
    let field_val = self.pop()?;
    let list_val = self.pop()?;

    // Iterate list, filter by field comparison
    let mut filtered = Vec::new();
    for item in list_items {
        let field_value = extract_field(&item, &field_name);
        let matches = match operator.as_str() {
            "==" => values_equal(&field_value, &value_val),
            "!=" => !values_equal(&field_value, &value_val),
            "<"  => compare_numeric_values(&field_value, &value_val) == Less,
            "<=" => compare_numeric_values(&field_value, &value_val) != Greater,
            ">"  => compare_numeric_values(&field_value, &value_val) == Greater,
            ">=" => compare_numeric_values(&field_value, &value_val) != Less,
            _    => false,
        };
        if matches {
            filtered.push(item);
        }
    }

    self.push(create_list(filtered));
}

ListOrderBy (0xF5)

L'implémentation a dû contourner le borrow checker de Rust. La solution a été de pré-extraire toutes les valeurs de champ avant le tri :

rustOpCode::ListOrderBy => {
    // Pre-extract field values to avoid borrow conflicts
    let mut pairs: Vec<(Value, Value)> = Vec::new();
    for item in &list_items {
        let field_value = extract_field(item, &field_name);
        pairs.push((item.clone(), field_value));
    }

    // Sort by extracted field values
    pairs.sort_by(|(_, val_a), (_, val_b)| {
        let cmp = compare_numeric_values(val_a, val_b);
        if direction == "desc" { cmp.reverse() } else { cmp }
    });
}

Pourquoi c'est important pour les applications

Le filtrage et le tri transforment le modèle temporel de « nous stockons l'historique » en « nous pouvons répondre à des questions sur l'historique ».

Suivi des prix :

flinexpensive_history = product.history
    .where_field("price", ">", 100)
    .order_by("created_at", "asc")

Conformité d'audit :

flinrecent_changes = document.history
    .where_field("created_at", ">", last_month)
    .order_by("created_at", "desc")

Détection d'anomalies :

flinlow_stock_events = inventory.history
    .where_field("quantity", "<", safety_threshold)

Chacun de ces cas nécessiterait une requête SQL personnalisée, une table de reporting dédiée ou un pipeline d'analyse dans une application traditionnelle. Dans FLIN, ce sont des lignes uniques qui se composent naturellement avec le reste de l'API temporelle.

Deux cent cinquante lignes de code d'implémentation. Deux nouveaux opcodes. Deux nouvelles signatures de types. Et les requêtes d'historique de FLIN sont devenues prêtes pour la production.


Ceci est la partie 5 de la série sur le modèle temporel de « How We Built FLIN », documentant le système de filtrage et de tri pour les requêtes d'historique temporel.

Navigation de la série : - [046] Every Entity Remembers Everything: The Temporal Model - [047] Version History and Time Travel Queries - [048] Temporal Integration: From Bugs to 100% Test Coverage - [049] Destroy and Restore: Soft Deletes Done Right - [050] Temporal Filtering and Ordering (vous êtes ici)

Share this article:

Responses

Write a response
0/2000
Loading responses...

Related Articles

Thales & Claude deblo

Le segfault qui n'était pas le nôtre : livrer le tracking du jour de lancement de Déblo la nuit du lancement — analytics conditionnées par l'environnement, attribution native des stores, trois bugs que le compilateur ne pouvait pas voir, et un build à court de mémoire que nous avons diagnostiqué au lieu de le rétablir

Le 1er juillet 2026 — jour de lancement — le risque n'a jamais été le texte. C'était les campagnes payantes qui partaient à l'aveugle. Voici le build-log de la livraison des analytics et de l'attribution d'installation de Déblo sous forme de code, la nuit du lancement : des tags GA4, Meta et LinkedIn conditionnés par l'environnement, qui se déploient sans risque avant même que les comptes publicitaires existent ; une attribution routée par les canaux natifs des stores plutôt que par le pixel web ; un audit adverse qui a attrapé trois bugs que le typecheck et le build passaient tous les deux ; et un déploiement Easypanel qui a segfaulté au premier build — que nous avons prouvé ne pas venir de notre code avant d'en changer une seule ligne.

18 min Jul 1, 2026
deblolaunch-dayclaude-opus-4.8claude-code +26
Thales & Claude thales

Treize agents, quarante-trois minutes : la première session Workflow de Claude Fable 5, et ce qu'un script d'orchestration déterministe change aux builds multi-agents

Un prompt, treize agents, quarante-trois minutes : la première session de production avec Claude Fable 5 et l'outil Workflow de Claude Code a livré un site web de production complet de sept pages plus un endpoint backend de capture de leads, en un seul commit. Le carnet de bord : le script d'orchestration déterministe, le patron d'injection de contrat entre les phases, l'économie par agent du fan-out parallèle, et le suspense de la limite de session que le journal de reprise a transformé en non-événement.

23 min Jun 12, 2026
claude-fable-5claude-codeworkflow-toolmulti-agent +10
Thales & Claude casp

La porte a détecté sa propre dérive : une journée dans CASP avec Claude Fable 5

Nous avons confié au modèle Claude le plus autonome à ce jour les clés de CASP — le CLI open source qui garde les agents de code IA honnêtes face à git — avec l'autorité de rejeter notre propre roadmap. Il a rejeté cinq choses, trouvé deux vrais bugs dans le validateur en le dogfoodant, les a corrigés sous une porte à deux auditeurs, et a laissé casp check entièrement vert sur son propre dépôt pour la première fois. CASP 0.3.0 en est le résultat.

16 min Jun 10, 2026
caspzerosuiteworkflowai-cto +9