""" LinkSyncServer - Query Engine """ from fastapi import APIRouter, HTTPException from typing import List, Optional, Dict, Any import re import uuid router = APIRouter(prefix="/api/queries", tags=["Queries"]) def tokenize(query: str) -> List[str]: """Tokenize query string.""" # Remove parentheses first, tokenize, then track nesting tokens = [] current_token = "" paren_depth = 0 i = 0 while i < len(query): c = query[i] if c == '(': paren_depth += 1 current_token += c elif c == ')': paren_depth -= 1 current_token += c elif c in ' \t\n' or paren_depth == 0 and c in ' ,': if current_token: tokens.append(current_token) current_token = "" else: current_token += c i += 1 if current_token: tokens.append(current_token) return tokens class TermSet: """Term set: ('term1', 'term2') -> OR operation""" def __init__(self, terms: List[str]): self.terms = terms self.operation = "OR" def to_dict(self) -> Dict[str, Any]: return { "type": "term_set", "terms": self.terms, "operation": self.operation } class TagFilter: """Tag-based filter""" def __init__(self, tag_name: str): self.tag_name = tag_name self.operation = "TAG" def to_dict(self) -> Dict[str, Any]: return { "type": "tag_filter", "tag_name": self.tag_name, "operation": self.operation } class FieldFilter: """Field-based filter (e.g., url:example.com)""" def __init__(self, field: str, value: str): self.field = field self.value = value self.operation = "FIELD" def to_dict(self) -> Dict[str, Any]: return { "type": "field_filter", "field": self.field, "value": self.value, "operation": self.operation } class ANDNode: """AND operation node""" def __init__(self, left, right): self.left = left self.right = right self.operation = "AND" def to_dict(self) -> Dict[str, Any]: return { "type": "binary", "operation": self.operation, "left": self.left.to_dict(), "right": self.right.to_dict() } class ORNode: """OR operation node""" def __init__(self, left, right): self.left = left self.right = right self.operation = "OR" def to_dict(self) -> Dict[str, Any]: return { "type": "binary", "operation": self.operation, "left": self.left.to_dict(), "right": self.right.to_dict() } class XORNode: """XOR operation node""" def __init__(self, left, right): self.left = left self.right = right self.operation = "XOR" def to_dict(self) -> Dict[str, Any]: return { "type": "binary", "operation": self.operation, "left": self.left.to_dict(), "right": self.right.to_dict() } class NOTNode: """NOT operation node""" def __init__(self, child): self.child = child self.operation = "NOT" def to_dict(self) -> Dict[str, Any]: return { "type": "unary", "operation": self.operation, "child": self.child.to_dict() } def parse_query(query: str) -> Dict[str, Any]: """ Parse query expression: ('term1', 'term2') OR tagA AND tagB XOR url:example.com Precedence: () > XOR > AND > OR """ tokens = tokenize(query) # Remove parentheses and tokenize tokens = tokenize(query) # Simple parser for basic queries # For full parser, would need recursive descent # Handle term sets: ('term1', 'term2') term_set = None i = 0 while i < len(tokens): token = tokens[i] if token.startswith('(') and tokens[i].endswith(')'): # Extract terms from tuple inner = token[1:-1] terms = [t.strip("'\"") for t in inner.split(',')] term_set = TermSet(terms) i += 1 else: break if not term_set: # Parse as simple expression # This is a simplified parser for demo return {"type": "term_set", "terms": []} return term_set.to_dict() def execute_query(query_expression: dict, all_bookmarks: List[dict]) -> List[dict]: """ Execute query expression against bookmark list. For demo, returns mock results. """ # Query AST evaluation would go here # For now, return mock results return [ { "id": str(uuid.uuid4()), "url": "https://example.com/result", "title": "Query Result", "description": "A result from the query", "notes": "", "tags": ["query", "result"], "favicon_url": None, "path": "/Query Result", "created_at": "2026-05-11T00:00:00Z", "updated_at": "2026-05-11T00:00:00Z", "visit_count": 0, "is_bookmarked": False, "source_set_id": None } ] @router.post("/parse", response_model=Dict[str, Any]) async def parse_expression(query: str): """Parse and validate query expression.""" parsed = parse_query(query) return { "expression": query, "parsed": parsed, "valid": True } @router.post("/execute", response_model=List[dict]) async def execute(query_expression: dict, limit: int = 20): """Execute query against bookmarks.""" # For demo, return mock results return [ { "id": str(uuid.uuid4()), "url": "https://example.com/queried", "title": "Queried Item", "description": "Item from query", "notes": "", "tags": ["queried"], "favicon_url": None, "path": "/Queried", "created_at": "2026-05-11T00:00:00Z", "updated_at": "2026-05-11T00:00:00Z", "visit_count": 0, "is_bookmarked": False, "source_set_id": None } ] @router.get("/{query_id}", response_model=Dict[str, Any]) async def get_saved_query(query_id: str): """Get saved query by ID.""" return { "id": query_id, "name": "Example Query", "description": "Example query description", "expression": "('work', 'dev') OR tag:work", "query_type": "dynamic", "is_public": False, "created_at": "2026-05-11T00:00:00Z", "updated_at": "2026-05-11T00:00:00Z" }