File size: 6,707 Bytes
0a65f9d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
from typing import Dict, Any
import ast

from typing import Any, Dict

def clean_modified_dict(modified_dict: Dict[str, Any]) -> Dict[str, Any]:
    """
    Cleans the modified dictionary by removing only values that are:
    - None
    - empty list []
    - empty dict {}
    - empty string ''
    But keeps values like 0, False, etc.
    """
    def is_meaningfully_empty(value):
        return value in (None, '', []) or (isinstance(value, dict) and not value)

    return {k: v for k, v in modified_dict.items() if not is_meaningfully_empty(v)}



def convert_to_lines(query_dict):
    lines = []
    for field, condition in query_dict.items():
        if isinstance(condition, dict):
            for operator, value in condition.items():
                # Special handling for $ne with '' or []
                if operator in ['$ne', 'ne']:
                    if value == '':
                        value_str = "''"
                    elif value == []:
                        value_str = '[]'
                    elif isinstance(value, list):
                        value_str = ','.join(map(str, value))
                    elif isinstance(value, str):
                        value_str = f"'{value}'"
                    else:
                        value_str = str(int(value) if isinstance(value, float) and value.is_integer() else value)
                elif isinstance(value, list):
                    # Output lists as valid Python lists for complex cases
                    value_str = repr(value)
                elif isinstance(value, str):
                    value_str = f"'{value}'"
                else:
                    value_str = str(int(value) if isinstance(value, float) and value.is_integer() else value)
                lines.append(f"{field} {operator} {value_str}")
        else:
            if isinstance(condition, str):
                condition_str = f"'{condition}'"
            else:
                condition_str = str(condition)
            lines.append(f"{field} = {condition_str}")
    return '\n'.join(lines)


def parse_line_based_query(lines):
    query = {}
    for line in lines.strip().split('\n'):
        if not line.strip():
            continue
        parts = line.split(maxsplit=2)
        if len(parts) < 3:
            # If operator is present but value is empty, set value to empty string
            if len(parts) == 2:
                field, operator = parts
                value = ''
            else:
                continue  # Skip invalid lines
        else:
            field, operator, value = parts

        # Special handling for sort, limit, skip, etc.
        if field in {"sort", "order_by"}:
            # Handle both 'sort field value' and 'sort = {field: value}'
            if operator == "=":
                query[field] = _convert_value(value)
            else:
                if field not in query:
                    query[field] = {}
                query[field][operator] = _convert_value(value)
            continue
        if field in {"limit", "skip", "offset"}:
            query[field] = _convert_value(value)
            continue
        # Special handling for _original_numbers (parse value as string if quoted, else as number)
        if field == "_original_numbers":
            if field not in query:
                query[field] = {}
            v = value.strip()
            if (v.startswith("'") and v.endswith("'")) or (v.startswith('"') and v.endswith('"')):
                query[field][operator] = v[1:-1]
            else:
                try:
                    # Try to parse as int or float
                    query[field][operator] = int(v)
                except ValueError:
                    try:
                        query[field][operator] = float(v)
                    except ValueError:
                        query[field][operator] = v
            continue

        # Handle equality operator
        if operator == "=":
            query[field] = _convert_value(value)
            continue

        # Handle other operators
        # If operator is $in, $nin, $all and value is empty, use []
        empty_list_ops = {'in', '$in', 'nin', '$nin', 'all', '$all'}
        op_key = operator if operator.startswith('$') else f'${operator}'
        if operator in empty_list_ops and value == '':
            value_obj = []
        elif operator in {'ne', '$ne'}:
            if value.strip() == '[]':
                value_obj = []
            elif value.strip() == "''" or value.strip() == '""':
                value_obj = ''
            elif value == '':
                value_obj = []
            else:
                value_obj = _convert_value(value, operator)
        else:
            value_obj = _convert_value(value, operator)
        if field in query:
            if isinstance(query[field], dict):
                query[field][op_key] = value_obj
            else:
                raise ValueError(f"Conflict in {field}: direct value and operator")
        else:
            query[field] = {op_key: value_obj}
    return query

def _convert_value(value_str, operator=None):
    """Convert string values to appropriate types"""
    # Handle lists for $in and $all operators
    if operator in ('in', '$in', 'all', '$all'):
        s = value_str.strip()
        if s.startswith('[') and s.endswith(']'):
            try:
                return ast.literal_eval(s)
            except Exception:
                pass
        if ',' in value_str:
            return [_parse_single_value(v) for v in value_str.split(',')]
    
    # Handle regex flags (e.g., "pattern i" → "pattern" with $options: 'i')
    if operator == 'regex' and ' ' in value_str:
        pattern, *flags = value_str.split()
        return {'$regex': pattern, '$options': ''.join(flags)}
    
    return _parse_single_value(value_str)

def _parse_single_value(s):
    """Convert individual values to int/float/string/dict/bool"""
    s = s.strip()
    # Remove surrounding quotes if present
    if (s.startswith("'") and s.endswith("'")) or (s.startswith('"') and s.endswith('"')):
        return s[1:-1].strip()  # Always return as string if quoted
    # Handle None
    if s == 'None':
        return None
    # Try to parse as dict if it looks like one
    if (s.startswith('{') and s.endswith('}')) or (s.startswith('[') and s.endswith(']')):
        try:
            return ast.literal_eval(s)
        except Exception:
            pass
    # Handle booleans
    if s.lower() == 'true':
        return True
    if s.lower() == 'false':
        return False
    try:
        return int(s)
    except ValueError:
        try:
            return float(s)
        except ValueError:
            return s