feat: implement comprehensive query paradigm zoo system (issue #62)
- Created extensible BaseQueryParadigm interface with standardized QueryResult format - Implemented QueryParadigmRegistry for paradigm discovery and management - Added 5 working paradigms: SQL, FTS, GraphQL, JSONPath, Natural Language - Documented 9 additional paradigms: QBE, Batch Manipulation, Visual Query Builder, REST API, NoSQL, UNIX Pipeline, XPath/XQuery, RAG, Data Transformation - Integrated full CLI interface: list, search, show, exec, categories commands - Added comprehensive test suite with 23 test cases covering all components - Auto-registration system enables easy addition of new paradigms - Organized paradigms by category (structural, textual, semantic, visual, procedural, network) and complexity (beginner, intermediate, advanced) 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
419
markitect/query_paradigms/paradigms/graphql_paradigm.py
Normal file
419
markitect/query_paradigms/paradigms/graphql_paradigm.py
Normal file
@@ -0,0 +1,419 @@
|
||||
"""
|
||||
GraphQL Query Paradigm - Flexible graph-based queries.
|
||||
"""
|
||||
|
||||
import time
|
||||
import json
|
||||
from typing import Dict, Any, List, Optional
|
||||
|
||||
from ..base import BaseQueryParadigm, QueryResult
|
||||
|
||||
|
||||
class GraphQLQueryParadigm(BaseQueryParadigm):
|
||||
"""GraphQL query paradigm for flexible, graph-based data access."""
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return "GraphQL"
|
||||
|
||||
@property
|
||||
def description(self) -> str:
|
||||
return "Graph-based queries with precise field selection and nested data relationships"
|
||||
|
||||
@property
|
||||
def category(self) -> str:
|
||||
return "structural"
|
||||
|
||||
@property
|
||||
def complexity(self) -> str:
|
||||
return "intermediate"
|
||||
|
||||
def execute(self, query: str, config: Dict[str, Any] = None) -> QueryResult:
|
||||
"""Execute GraphQL query."""
|
||||
start_time = time.time()
|
||||
|
||||
try:
|
||||
from ...graphql.resolvers import GraphQLResolvers
|
||||
from ...graphql.schema import schema
|
||||
|
||||
# Get database path from config
|
||||
db_path = config.get('db_path') if config else 'markitect.db'
|
||||
|
||||
# Parse variables if provided
|
||||
variables = {}
|
||||
if config and 'variables' in config:
|
||||
if isinstance(config['variables'], str):
|
||||
variables = json.loads(config['variables'])
|
||||
elif isinstance(config['variables'], dict):
|
||||
variables = config['variables']
|
||||
|
||||
# Execute GraphQL query
|
||||
result = schema.execute(query, variable_values=variables, context={'db_path': db_path})
|
||||
|
||||
execution_time = (time.time() - start_time) * 1000
|
||||
|
||||
if result.errors:
|
||||
return QueryResult(
|
||||
paradigm="GraphQL",
|
||||
query=query,
|
||||
execution_time_ms=execution_time,
|
||||
result_count=0,
|
||||
results=[],
|
||||
metadata={"variables": variables},
|
||||
success=False,
|
||||
error_message=str(result.errors[0])
|
||||
)
|
||||
|
||||
# Convert GraphQL result to standard format
|
||||
results = []
|
||||
result_data = result.data or {}
|
||||
|
||||
# Handle different result types
|
||||
for key, value in result_data.items():
|
||||
if isinstance(value, list):
|
||||
for item in value:
|
||||
results.append({
|
||||
"query_field": key,
|
||||
**self._flatten_graphql_result(item)
|
||||
})
|
||||
elif isinstance(value, dict):
|
||||
results.append({
|
||||
"query_field": key,
|
||||
**self._flatten_graphql_result(value)
|
||||
})
|
||||
else:
|
||||
results.append({
|
||||
"query_field": key,
|
||||
"value": value
|
||||
})
|
||||
|
||||
return QueryResult(
|
||||
paradigm="GraphQL",
|
||||
query=query,
|
||||
execution_time_ms=execution_time,
|
||||
result_count=len(results),
|
||||
results=results,
|
||||
metadata={
|
||||
"variables": variables,
|
||||
"query_type": self._detect_query_type(query)
|
||||
},
|
||||
success=True
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
execution_time = (time.time() - start_time) * 1000
|
||||
|
||||
return QueryResult(
|
||||
paradigm="GraphQL",
|
||||
query=query,
|
||||
execution_time_ms=execution_time,
|
||||
result_count=0,
|
||||
results=[],
|
||||
metadata={},
|
||||
success=False,
|
||||
error_message=str(e)
|
||||
)
|
||||
|
||||
def get_examples(self) -> List[Dict[str, str]]:
|
||||
"""Get example GraphQL queries."""
|
||||
return [
|
||||
{
|
||||
"name": "Basic file query",
|
||||
"description": "Get basic information about markdown files",
|
||||
"query": """query {
|
||||
markdownFiles(limit: 5) {
|
||||
id
|
||||
filename
|
||||
wordCount
|
||||
hassFrontMatter
|
||||
}
|
||||
}"""
|
||||
},
|
||||
{
|
||||
"name": "File with front matter",
|
||||
"description": "Get files with their front matter data",
|
||||
"query": """query {
|
||||
markdownFiles(hasFrontMatter: true) {
|
||||
filename
|
||||
frontMatter {
|
||||
key
|
||||
value
|
||||
}
|
||||
createdAt
|
||||
}
|
||||
}"""
|
||||
},
|
||||
{
|
||||
"name": "Schema information",
|
||||
"description": "Get schema details and statistics",
|
||||
"query": """query {
|
||||
schemas {
|
||||
filename
|
||||
title
|
||||
description
|
||||
schemaVersion
|
||||
propertyCount
|
||||
}
|
||||
}"""
|
||||
},
|
||||
{
|
||||
"name": "Search with variables",
|
||||
"description": "Search using variables",
|
||||
"query": """query SearchContent($searchTerm: String!) {
|
||||
search(query: $searchTerm, limit: 10) {
|
||||
type
|
||||
score
|
||||
file {
|
||||
filename
|
||||
wordCount
|
||||
}
|
||||
highlight
|
||||
}
|
||||
}"""
|
||||
},
|
||||
{
|
||||
"name": "Database statistics",
|
||||
"description": "Get overall database statistics",
|
||||
"query": """query {
|
||||
databaseStats {
|
||||
totalFiles
|
||||
totalSchemas
|
||||
totalSizeBytes
|
||||
lastUpdated
|
||||
}
|
||||
}"""
|
||||
},
|
||||
{
|
||||
"name": "Specific file by ID",
|
||||
"description": "Get detailed information about a specific file",
|
||||
"query": """query GetFile($fileId: Int!) {
|
||||
markdownFile(id: $fileId) {
|
||||
filename
|
||||
content
|
||||
frontMatterRaw
|
||||
wordCount
|
||||
lineCount
|
||||
createdAt
|
||||
}
|
||||
}"""
|
||||
}
|
||||
]
|
||||
|
||||
def validate_query(self, query: str) -> tuple[bool, Optional[str]]:
|
||||
"""Validate GraphQL query syntax."""
|
||||
if not query or not query.strip():
|
||||
return False, "Query cannot be empty"
|
||||
|
||||
# Basic GraphQL syntax validation
|
||||
query = query.strip()
|
||||
|
||||
# Should start with query, mutation, or subscription
|
||||
if not any(query.startswith(keyword) for keyword in ['query', 'mutation', 'subscription', '{']):
|
||||
return False, "GraphQL query must start with 'query', 'mutation', 'subscription', or '{'"
|
||||
|
||||
# Check for balanced braces
|
||||
open_braces = query.count('{')
|
||||
close_braces = query.count('}')
|
||||
if open_braces != close_braces:
|
||||
return False, "Unmatched braces in GraphQL query"
|
||||
|
||||
# Check for balanced parentheses
|
||||
open_parens = query.count('(')
|
||||
close_parens = query.count(')')
|
||||
if open_parens != close_parens:
|
||||
return False, "Unmatched parentheses in GraphQL query"
|
||||
|
||||
return True, None
|
||||
|
||||
def get_syntax_help(self) -> str:
|
||||
"""Get GraphQL syntax help."""
|
||||
return """GraphQL Query Syntax:
|
||||
|
||||
Basic Structure:
|
||||
query {
|
||||
fieldName {
|
||||
subfield
|
||||
}
|
||||
}
|
||||
|
||||
Available Root Fields:
|
||||
- markdownFile(id: Int, filename: String)
|
||||
- markdownFiles(limit: Int, offset: Int, hasFrontMatter: Boolean)
|
||||
- schema(id: Int, filename: String)
|
||||
- schemas(limit: Int, offset: Int)
|
||||
- search(query: String!, type: String, limit: Int)
|
||||
- databaseStats
|
||||
- astQuery(fileId: Int, filename: String, jsonpath: String!)
|
||||
|
||||
Field Selection:
|
||||
markdownFiles {
|
||||
id
|
||||
filename
|
||||
wordCount
|
||||
frontMatter {
|
||||
key
|
||||
value
|
||||
}
|
||||
}
|
||||
|
||||
Variables:
|
||||
query GetFile($id: Int!) {
|
||||
markdownFile(id: $id) {
|
||||
filename
|
||||
content
|
||||
}
|
||||
}
|
||||
|
||||
Aliases:
|
||||
query {
|
||||
recent: markdownFiles(limit: 5) { filename }
|
||||
old: markdownFiles(offset: 100, limit: 5) { filename }
|
||||
}
|
||||
|
||||
Fragments:
|
||||
fragment FileInfo on MarkdownFile {
|
||||
id
|
||||
filename
|
||||
wordCount
|
||||
}
|
||||
|
||||
query {
|
||||
markdownFiles {
|
||||
...FileInfo
|
||||
createdAt
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
def _detect_query_type(self, query: str) -> str:
|
||||
"""Detect GraphQL query type."""
|
||||
query_lower = query.lower().strip()
|
||||
|
||||
if query_lower.startswith('mutation'):
|
||||
return "mutation"
|
||||
elif query_lower.startswith('subscription'):
|
||||
return "subscription"
|
||||
elif 'search(' in query_lower:
|
||||
return "search_query"
|
||||
elif 'astquery(' in query_lower:
|
||||
return "ast_query"
|
||||
elif any(field in query_lower for field in ['markdownfiles', 'schemas']):
|
||||
return "list_query"
|
||||
elif any(field in query_lower for field in ['markdownfile', 'schema']):
|
||||
return "single_query"
|
||||
elif 'databasestats' in query_lower:
|
||||
return "stats_query"
|
||||
else:
|
||||
return "query"
|
||||
|
||||
def _flatten_graphql_result(self, item: Any) -> Dict[str, Any]:
|
||||
"""Flatten GraphQL result for standardized output."""
|
||||
if isinstance(item, dict):
|
||||
flattened = {}
|
||||
for key, value in item.items():
|
||||
if isinstance(value, (dict, list)):
|
||||
flattened[key] = json.dumps(value) if isinstance(value, dict) else value
|
||||
else:
|
||||
flattened[key] = value
|
||||
return flattened
|
||||
else:
|
||||
return {"value": item}
|
||||
|
||||
def can_translate_from(self, other_paradigm: str) -> bool:
|
||||
"""Check if we can translate from another paradigm."""
|
||||
return other_paradigm.lower() in ["sql", "natural_language"]
|
||||
|
||||
def translate_query(self, query: str, from_paradigm: str) -> Optional[str]:
|
||||
"""Translate from another paradigm to GraphQL."""
|
||||
if from_paradigm.lower() == "sql":
|
||||
return self._translate_sql_to_graphql(query)
|
||||
elif from_paradigm.lower() == "natural_language":
|
||||
return self._translate_natural_language_to_graphql(query)
|
||||
return None
|
||||
|
||||
def _translate_sql_to_graphql(self, query: str) -> Optional[str]:
|
||||
"""Translate simple SQL to GraphQL."""
|
||||
query_upper = query.upper().strip()
|
||||
|
||||
# Simple translations for common patterns
|
||||
if 'SELECT * FROM markdown_files' in query_upper:
|
||||
return """query {
|
||||
markdownFiles {
|
||||
id
|
||||
filename
|
||||
content
|
||||
createdAt
|
||||
}
|
||||
}"""
|
||||
elif 'SELECT filename FROM markdown_files' in query_upper:
|
||||
return """query {
|
||||
markdownFiles {
|
||||
filename
|
||||
}
|
||||
}"""
|
||||
elif 'SELECT * FROM schemas' in query_upper:
|
||||
return """query {
|
||||
schemas {
|
||||
id
|
||||
filename
|
||||
title
|
||||
description
|
||||
schemaContent
|
||||
}
|
||||
}"""
|
||||
elif 'COUNT(*) FROM markdown_files' in query_upper:
|
||||
return """query {
|
||||
databaseStats {
|
||||
totalFiles
|
||||
}
|
||||
}"""
|
||||
|
||||
return None
|
||||
|
||||
def _translate_natural_language_to_graphql(self, query: str) -> Optional[str]:
|
||||
"""Translate natural language to GraphQL."""
|
||||
query_lower = query.lower()
|
||||
|
||||
if "all files" in query_lower or "list files" in query_lower:
|
||||
return """query {
|
||||
markdownFiles {
|
||||
id
|
||||
filename
|
||||
wordCount
|
||||
createdAt
|
||||
}
|
||||
}"""
|
||||
elif "search for" in query_lower:
|
||||
# Extract search term
|
||||
parts = query_lower.split("search for", 1)
|
||||
if len(parts) > 1:
|
||||
search_term = parts[1].strip().strip('"\'')
|
||||
return f'''query {{
|
||||
search(query: "{search_term}") {{
|
||||
type
|
||||
score
|
||||
file {{
|
||||
filename
|
||||
}}
|
||||
highlight
|
||||
}}
|
||||
}}'''
|
||||
elif "database statistics" in query_lower or "stats" in query_lower:
|
||||
return """query {
|
||||
databaseStats {
|
||||
totalFiles
|
||||
totalSchemas
|
||||
totalSizeBytes
|
||||
lastUpdated
|
||||
}
|
||||
}"""
|
||||
elif "schemas" in query_lower:
|
||||
return """query {
|
||||
schemas {
|
||||
filename
|
||||
title
|
||||
description
|
||||
}
|
||||
}"""
|
||||
|
||||
return None
|
||||
Reference in New Issue
Block a user