""" 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