05_02_08 - Query Interface¶
📋 Descripción General¶
Interfaz de consultas expresiva tipo GraphQL/Cypher para interrogar el grafo de dependencias sin necesidad de algoritmos complejos. Permite consultas declarativas en C++ con sintaxis fluida.
🎯 Objetivos¶
- API Fluida para consultas legibles tipo SQL
- Consultas complejas sin escribir algoritmos de grafos
- Performance O(V+E) para consultas típicas
- Type-safe en tiempo de compilación
- Composición de consultas reutilizables
🏗️ Arquitectura¶
QueryEngine
├── GraphQuery (fluent API builder)
├── QueryExecutor (execution engine)
├── Traversal Algorithms
│ ├── BreadthFirstTraversal
│ ├── DepthFirstTraversal
│ └── BidirectionalTraversal
├── Predicates
│ ├── NodePredicate
│ └── EdgePredicate
└── Result Aggregators
├── NodeSet
├── PathSet
└── Statistics
🔍 Sintaxis de Consultas¶
Consultas Básicas¶
#include "query_engine.hpp"
using namespace audio_lab::dependency_graph;
Graph graph = /* ... */;
// ¿Qué depende de este módulo? (dependents)
auto dependents = GraphQuery(graph)
.start_from("filter_biquad")
.traverse_upstream()
.get_nodes();
// ¿De qué depende este módulo? (dependencies)
auto dependencies = GraphQuery(graph)
.start_from("voice_mono")
.traverse_downstream()
.get_nodes();
// ¿Todos los módulos L3_ENGINE?
auto engines = GraphQuery(graph)
.select_all()
.where_level(L3_ENGINE)
.get_nodes();
Consultas con Filtros¶
// Filtros que consumen más de 100 CPU cycles
auto expensive_filters = GraphQuery(graph)
.select_all()
.where_category("FILTER")
.where_cpu_greater_than(100)
.get_nodes();
// Módulos en estado EXPERIMENTAL
auto experimental = GraphQuery(graph)
.select_all()
.where_status(ModuleStatus::EXPERIMENTAL)
.get_nodes();
// Módulos con alta conectividad (degree > 5)
auto hubs = GraphQuery(graph)
.select_all()
.where([](const NodePtr& n) {
return n->total_degree() > 5;
})
.get_nodes();
Consultas de Caminos¶
// ¿Camino más corto entre dos módulos?
auto path = GraphQuery(graph)
.start_from("synth_engine")
.find_path_to("osc_sine")
.get_shortest_path();
// ¿Todos los caminos posibles?
auto all_paths = GraphQuery(graph)
.start_from("synth_engine")
.find_path_to("osc_sine")
.get_all_paths();
// ¿Camino crítico por CPU?
auto critical = GraphQuery(graph)
.start_from("synth_engine")
.find_critical_path([](const NodePtr& n) {
return n->cpu_cycles;
})
.get_path();
Consultas de Alcance¶
// ¿Todos los dependientes a cualquier profundidad?
auto all_dependents = GraphQuery(graph)
.start_from("filter_biquad")
.traverse_upstream()
.recursive() // Transitive closure
.get_nodes();
// ¿Dependencias directas solamente?
auto direct_deps = GraphQuery(graph)
.start_from("voice_mono")
.traverse_downstream()
.depth(1) // Solo nivel 1
.get_nodes();
// ¿Dependencias hasta nivel L1_ATOM?
auto deps_to_atoms = GraphQuery(graph)
.start_from("synth_engine")
.traverse_downstream()
.until_level(L1_ATOM)
.get_nodes();
Consultas de Análisis¶
// ¿Módulos que están en el camino crítico de X a Y?
auto critical_nodes = GraphQuery(graph)
.start_from("synth_engine")
.find_path_to("osc_sine")
.get_critical_nodes();
// ¿Quién sería afectado si elimino este módulo?
auto impact = GraphQuery(graph)
.start_from("filter_biquad")
.traverse_upstream()
.recursive()
.get_impact_analysis();
// ¿Módulos aislados (sin conexiones)?
auto isolated = GraphQuery(graph)
.select_all()
.where([](const NodePtr& n) {
return n->total_degree() == 0;
})
.get_nodes();
Consultas Compuestas¶
// Combinar múltiples condiciones
auto result = GraphQuery(graph)
.select_all()
.where_level(L2_CELL)
.where_category("VOICE")
.where_cpu_greater_than(150)
.where([](const NodePtr& n) {
return n->status == ModuleStatus::STABLE;
})
.get_nodes();
// Intersección de conjuntos
auto deps_of_A = GraphQuery(graph)
.start_from("module_A")
.traverse_downstream()
.get_nodes();
auto deps_of_B = GraphQuery(graph)
.start_from("module_B")
.traverse_downstream()
.get_nodes();
// Dependencias comunes
std::vector<NodePtr> common_deps;
std::set_intersection(
deps_of_A.begin(), deps_of_A.end(),
deps_of_B.begin(), deps_of_B.end(),
std::back_inserter(common_deps)
);
📊 Casos de Uso Prácticos¶
1. Análisis de Impacto de Cambios¶
Pregunta: Si modifico filter_biquad, ¿qué módulos se verán afectados?
auto impact_report = GraphQuery(graph)
.start_from("filter_biquad")
.traverse_upstream()
.recursive()
.get_impact_analysis();
std::cout << "Módulos afectados: " << impact_report.affected_count << "\n";
std::cout << "CPU total afectado: " << impact_report.total_cpu_cycles << "\n";
std::cout << "Jerarquías afectadas:\n";
for (const auto& [level, count] : impact_report.by_hierarchy) {
std::cout << " " << to_string(level) << ": " << count << " módulos\n";
}
2. Detección de Cuellos de Botella¶
Pregunta: ¿Qué módulos tienen más dependientes (son cuellos de botella)?
auto bottlenecks = GraphQuery(graph)
.select_all()
.where([](const NodePtr& n) {
return n->in_degree > 10; // Más de 10 dependientes
})
.order_by([](const NodePtr& a, const NodePtr& b) {
return a->in_degree > b->in_degree;
})
.limit(10)
.get_nodes();
std::cout << "Top 10 cuellos de botella:\n";
for (const auto& node : bottlenecks) {
std::cout << " " << node->label << " (";
std::cout << node->in_degree << " dependientes)\n";
}
3. Verificación de Dependencias Cíclicas¶
Pregunta: ¿Hay ciclos en las dependencias?
auto cycles = GraphQuery(graph)
.select_all()
.detect_cycles()
.get_cycles();
if (!cycles.empty()) {
std::cout << "⚠ Encontrados " << cycles.size() << " ciclos:\n";
for (const auto& cycle : cycles) {
std::cout << " Ciclo: ";
for (const auto& node_id : cycle.path) {
std::cout << node_id << " → ";
}
std::cout << cycle.path.front() << "\n";
}
} else {
std::cout << "✓ No hay ciclos (DAG válido)\n";
}
4. Búsqueda de Alternativas¶
Pregunta: ¿Qué otros filtros puedo usar en lugar de filter_biquad?
auto biquad = graph.get_node("filter_biquad");
auto alternatives = GraphQuery(graph)
.select_all()
.where_category(biquad->category) // Mismo tipo
.where_level(biquad->level) // Mismo nivel jerárquico
.where([&](const NodePtr& n) {
// CPU similar (±20%)
return std::abs(int(n->cpu_cycles) - int(biquad->cpu_cycles))
< biquad->cpu_cycles * 0.2;
})
.where([&](const NodePtr& n) {
return n->id != biquad->id; // Excluir el mismo
})
.get_nodes();
std::cout << "Alternativas a " << biquad->label << ":\n";
for (const auto& alt : alternatives) {
std::cout << " - " << alt->label;
std::cout << " (CPU: " << alt->cpu_cycles << ")\n";
}
5. Optimización de Build Order¶
Pregunta: ¿En qué orden debo compilar los módulos?
auto build_order = GraphQuery(graph)
.select_all()
.topological_sort()
.get_nodes();
std::cout << "Orden de compilación:\n";
for (size_t i = 0; i < build_order.size(); ++i) {
std::cout << i+1 << ". " << build_order[i]->label << "\n";
}
6. Análisis de Modularidad¶
Pregunta: ¿Qué módulos L3 dependen de módulos L0 directamente (saltando L1/L2)?
// Encontrar violaciones de jerarquía
auto violations = GraphQuery(graph)
.select_all()
.where_level(L3_ENGINE)
.where([&](const NodePtr& n) {
auto deps = graph.get_dependencies(n->id);
for (const auto& dep_id : deps) {
auto dep = graph.get_node(dep_id);
if (dep && dep->level == L0_KERNEL) {
return true; // L3 → L0 directo (violación)
}
}
return false;
})
.get_nodes();
if (!violations.empty()) {
std::cout << "⚠ Violaciones de jerarquía:\n";
for (const auto& node : violations) {
std::cout << " " << node->label << " (L3) depende directamente de L0\n";
}
}
🎨 API Fluida Completa¶
Métodos de Inicio¶
select_all()- Seleccionar todos los nodosstart_from(id)- Comenzar desde un nodo específicostart_from_multiple(ids)- Comenzar desde múltiples nodos
Métodos de Filtrado¶
where(predicate)- Filtro personalizadowhere_level(level)- Filtrar por nivel jerárquicowhere_category(category)- Filtrar por categoríawhere_status(status)- Filtrar por estadowhere_cpu_greater_than(cpu)- CPU > valorwhere_cpu_less_than(cpu)- CPU < valorwhere_latency_greater_than(lat)- Latencia > valorwhere_version(version)- Versión específica
Métodos de Traversal¶
traverse_upstream()- Hacia dependientes (quien usa esto)traverse_downstream()- Hacia dependencias (qué usa esto)traverse_both()- Ambas direccionesdepth(n)- Limitar profundidadrecursive()- Transitive closure (sin límite)until_level(level)- Hasta alcanzar nivel jerárquicountil(predicate)- Hasta cumplir condición
Métodos de Búsqueda de Caminos¶
find_path_to(target)- Buscar caminosget_shortest_path()- Camino más cortoget_all_paths()- Todos los caminosget_critical_path()- Camino crítico (por peso)
Métodos de Análisis¶
detect_cycles()- Detectar ciclostopological_sort()- Ordenamiento topológicoget_impact_analysis()- Análisis de impactoget_statistics()- Estadísticas del resultado
Métodos de Ordenamiento¶
order_by(comparator)- Ordenar por funciónorder_by_cpu()- Ordenar por CPUorder_by_degree()- Ordenar por conectividadlimit(n)- Limitar resultados
Métodos de Resultado¶
get_nodes()- Obtener nodosget_edges()- Obtener aristasget_paths()- Obtener caminosget_count()- Contar resultadosget_first()- Primer resultadoexists()- ¿Existe algún resultado?
📐 Predicados Predefinidos¶
namespace Predicates {
// Predicados de nivel
auto is_kernel = [](const NodePtr& n) {
return n->level == L0_KERNEL;
};
auto is_atom = [](const NodePtr& n) {
return n->level == L1_ATOM;
};
auto is_cell = [](const NodePtr& n) {
return n->level == L2_CELL;
};
auto is_engine = [](const NodePtr& n) {
return n->level == L3_ENGINE;
};
// Predicados de estado
auto is_stable = [](const NodePtr& n) {
return n->status == ModuleStatus::STABLE;
};
auto is_experimental = [](const NodePtr& n) {
return n->status == ModuleStatus::EXPERIMENTAL;
};
// Predicados de performance
auto is_expensive(uint32_t threshold) {
return [threshold](const NodePtr& n) {
return n->cpu_cycles > threshold;
};
}
auto has_latency = [](const NodePtr& n) {
return n->latency_samples > 0;
};
// Predicados de conectividad
auto is_hub(uint32_t min_degree) {
return [min_degree](const NodePtr& n) {
return n->total_degree() >= min_degree;
};
}
auto is_source = [](const NodePtr& n) {
return n->in_degree == 0;
};
auto is_sink = [](const NodePtr& n) {
return n->out_degree == 0;
};
}
🚀 Performance¶
Complejidad de Consultas¶
| Operación | Complejidad | Notas |
|---|---|---|
select_all() |
O(1) | Referencia, no copia |
where(predicate) |
O(V) | Escaneo lineal |
traverse(depth=1) |
O(V) | Adyacencia directa |
traverse(recursive) |
O(V+E) | BFS/DFS completo |
find_shortest_path |
O(V+E) | BFS |
topological_sort |
O(V+E) | DFS |
detect_cycles |
O(V+E) | DFS con colores |
Optimizaciones¶
- Lazy Evaluation: Las consultas se ejecutan solo al llamar
get_*() - Early Termination: Filtros aplicados durante traversal
- Index Lookup: Uso de índices bidireccionales O(1)
- Result Caching: Resultados cacheados para reutilización
- Move Semantics: Evita copias innecesarias
🔗 Integración con Otros Módulos¶
Con Filtros (TAREA 6)¶
// Combinar FilterChain con Query
auto filter = FilterChain()
.by_level({L2_CELL, L3_ENGINE})
.cpu_greater_than(100);
Graph filtered = filter.apply(graph);
auto result = GraphQuery(filtered)
.select_all()
.topological_sort()
.get_nodes();
Con Path Analysis (TAREA 3)¶
// Query devuelve nodos, Path Analyzer calcula métricas
auto critical_nodes = GraphQuery(graph)
.start_from("synth_engine")
.find_critical_path()
.get_nodes();
CriticalPathAnalyzer analyzer(graph);
auto metrics = analyzer.calculate_path_metrics(critical_nodes);
Con Metrics (TAREA 5)¶
// Query + Metrics para encontrar nodos importantes
MetricsEngine metrics(graph);
metrics.compute_all();
auto important = GraphQuery(graph)
.select_all()
.where([&](const NodePtr& n) {
return n->betweenness > 0.5; // Alta centralidad
})
.order_by([](const NodePtr& a, const NodePtr& b) {
return a->betweenness > b->betweenness;
})
.get_nodes();
📚 Referencias¶
- GraphQL: Inspiración para API declarativa
- Cypher (Neo4j): Lenguaje de consulta de grafos
- LINQ (C#): Fluent API para consultas
- jQuery: Encadenamiento de métodos
- Range-v3: Composición de operaciones en C++
Parte de: 05_02_DEPENDENCY_GRAPH - Dependency Graph Visualizer
Requiere: graph.hpp
Exporta: query_engine.hpp