""" LinkSyncServer - Query Executor """ from typing import List, Dict, Any, Optional from sqlalchemy.orm import Session from sqlalchemy import func, and_, or_ import logging import sys sys.path.insert(0, 'models') from base import Bookmark, User logger = logging.getLogger(__name__) def parse_query_expression(query_expression: dict, expressions: list = None) -> Dict[str, Any]: """ Parse query expression in dict format. Example: { "operation": "OR", "operands": [ {"operation": "TERM", "value": "work"}, {"operation": "TERM", "value": "company"} ] } """ if not query_expression: return operation = query_expression.get('operation') operands = query_expression.get('operands', []) if not operands: # Top-level expression (e.g., TERM) if operation == 'TERM': value = query_expression.get('value', '') if value.startswith('url:'): search_term = value[4:] return parse_term(search_term, 'url') elif value.startswith('tag:'): search_term = value[4:] return parse_term(search_term, 'tags') elif value.startswith('title:'): search_term = value[6:] return parse_term(search_term, 'title') elif value.startswith('description:'): search_term = value[12:] return parse_term(search_term, 'description') elif value.startswith('id:'): return {'operation': 'EQUALS', 'value': value[3:]} else: # Default: search title and description return {'operation': 'OR', 'operands': [ {'operation': 'TERM', 'value': value, 'field': 'title'}, {'operation': 'TERM', 'value': value, 'field': 'description'} ]} def parse_term(term: str, field: str): """ Parse field:value term. Returns SQLAlchemy filter clause. """ # Handle different field types field_filters = { 'tags': lambda term: and_(*[Bookmark.tags.ilike(f'%{term}%') for tag in term.split(',')]), 'title': lambda term: Bookmark.title.ilike(f'%{term}%'), 'description': lambda term: Bookmark.description.ilike(f'%{term}%'), 'url': lambda term: Bookmark.url.ilike(f'%{term}%'), 'path': lambda term: Bookmark.path.ilike(f'%{term}%') } # Get filter function filter_fn = field_filters.get(field, lambda term: Bookmark.tags.ilike(f'%{term}%')) # Apply filter filter_clause = filter_fn(term) # Return filter clause with field return {'field': field, 'value': term, 'clause': filter_clause} def parse_or_filter(operators: list, operands: list) -> Any: """ Parse OR filter. Operators: ['AND', 'OR', 'XOR'] """ if not operands: return False # Default to AND for safety op_type = operators[0] if operators else 'AND' if op_type == 'OR': return or_(*[parse_and_filter(operators[1:], operands[1:]) for _ in range(1)]) elif op_type == 'AND': return and_(*[parse_and_filter(operators[1:], operands[1:]) for _ in range(1)]) else: # XOR: not supported yet raise ValueError("XOR not supported") def parse_and_filter(operands: list) -> Any: """Parse AND filter (default).""" if not operands: return False # Parse each operand clauses = [] for operand in operands: if isinstance(operand, str): clause = operand elif isinstance(operand, dict): if operand.get('operation') == 'EQUALS': clause = operand['value'] elif operand.get('operation') == 'TERM': clauses.append(parse_term(operand.get('value', ''), operand.get('field', 'tags'))) # Add other term types as needed else: clauses.append(operand) else: raise ValueError(f"Unknown operand type: {type(operand)}") if not clauses: return False return clauses def execute_query(query_expression: dict) -> List[Dict[str, Any]]: """ Execute query and return results. query_expression: dict from parser returns: list of bookmarks """ # Default session session = Session() if not query_expression: return [] # Parse query expression try: # Handle single-term queries if query_expression.get('operation') == 'TERM': search_term = query_expression.get('value', '') field = query_expression.get('field', 'title') if field == 'tags': tags = search_term.split(',') filters = [Bookmark.tags.contains(tag) for tag in tags] result = session.query(Bookmark).filter(or_(*filters)).all() elif field == 'title': result = session.query(Bookmark).filter(Bookmark.title.contains(search_term)).all() elif field == 'description': result = session.query(Bookmark).filter(Bookmark.description.contains(search_term)).all() elif field == 'url': result = session.query(Bookmark).filter(Bookmark.url.contains(search_term)).all() else: # Default: search title and description filters = [ or_(Bookmark.title.contains(search_term), Bookmark.description.contains(search_term)) ] result = session.query(Bookmark).filter(or_(*filters)).all() elif query_expression.get('operation') == 'AND': # AND clause clauses = parse_and_filter(query_expression.get('operands', [])) if isinstance(clauses, list): result = session.query(Bookmark).filter(and_(*clauses)).all() else: result = session.query(Bookmark).filter(clauses).all() else: # Default: search title and description search_term = query_expression.get('value', '') result = session.query(Bookmark).filter( or_(Bookmark.title.contains(search_term), Bookmark.description.contains(search_term)) ).all() except Exception as e: logger.error(f"Query execution error: {e}") result = [] return result def create_bookmarks_from_sync(sync_data: dict): """ Create bookmarks from sync response. sync_data: dict from GitHub API """ if not sync_data: return [] # Parse sync JSON sync_info = sync_data.get('_links', {}).get('sync', {}).get('_links', {}) # Extract bookmarks bookmarks = [] if 'objects' in sync_data: for obj in sync_data['objects']: if 'title' in obj: bookmarks.append({ 'url': obj.get('url', ''), 'title': obj.get('title', ''), 'description': obj.get('description', ''), 'tags': obj.get('tags', []), 'favicon_url': obj.get('favicon_url', ''), 'path': obj.get('path', ''), 'visit_count': obj.get('visit_count', 0) }) return bookmarks