Skip to content

Commit c67e63d

Browse files
committed
remove prints + apply ruff
1 parent a1622ea commit c67e63d

29 files changed

Lines changed: 284 additions & 159 deletions

MIGRATION_NOTES.md

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
# Migração de fs_storage para SqlStorage (abstra-json-sql)
2+
3+
## Data: 2025-10-19
4+
5+
## Resumo
6+
Substituímos a implementação de `FileSystemStorage` (fs_storage.py) por `SqlStorage` que utiliza a biblioteca `abstra-json-sql` para armazenamento de dados estruturados usando SQL sobre JSON.
7+
8+
## Alterações Realizadas
9+
10+
### Arquivos Criados
11+
- `abstra_internals/services/sql_storage.py` - Nova implementação usando abstra-json-sql
12+
- `abstra_internals/services/sql_storage_test.py` - Testes adaptados (renomeado de fs_storage_test.py)
13+
14+
### Arquivos Removidos
15+
- `abstra_internals/services/fs_storage.py`
16+
17+
### Arquivos Modificados
18+
- `abstra_internals/repositories/execution.py` - Atualizado para usar SqlStorage
19+
- `abstra_internals/repositories/tasks.py` - Atualizado para usar SqlStorage
20+
- `requirements.txt` - Adicionada dependência `abstra-json-sql`
21+
22+
## Problemas Encontrados e Soluções
23+
24+
### 1. Palavras Reservadas SQL como Nomes de Colunas
25+
**Problema:** Quando um modelo Pydantic tem campos com nomes que são palavras reservadas SQL (como `update`, `select`, `where`, etc.), o parser da biblioteca `abstra-json-sql` falha ao interpretar os comandos INSERT e UPDATE.
26+
27+
**Exemplo de erro:**
28+
```python
29+
AssertionError: Expected column name, got Token(type='keyword', value='update')
30+
```
31+
32+
**Solução implementada:**
33+
Usar aspas duplas (`"`) para escapar nomes de colunas em todas as queries SQL:
34+
```python
35+
# Ao invés de:
36+
INSERT INTO data (id, update, name) VALUES ('1', 'value', 'John')
37+
38+
# Usamos:
39+
INSERT INTO data ("id", "update", "name") VALUES ('1', 'value', 'John')
40+
```
41+
42+
Isso garante que palavras reservadas sejam tratadas como identificadores, não como keywords.
43+
44+
**Teste adicionado:** `test_reserved_sql_keywords` para garantir que campos com nomes reservados funcionam corretamente.
45+
46+
### 2. WHERE Clause não funcionando em alguns casos
47+
**Problema:** Ao executar queries SQL com `WHERE` clause, a biblioteca `abstra-json-sql` lança erro `Unknown variable: <column_name>` em alguns contextos, mesmo quando a coluna existe e os dados foram inseridos corretamente.
48+
49+
**Exemplo de erro:**
50+
```python
51+
eval_sql("SELECT * FROM data WHERE id = 'test_id'", tables, {})
52+
# ValueError: Unknown variable: id
53+
```
54+
55+
**Workaround implementado:**
56+
- Em `_load()`: Carregar todos os registros com `SELECT * FROM table` e filtrar manualmente em Python
57+
- Em `save()`: Carregar todos os IDs e filtrar para checar existência
58+
- Em `delete()`: Carregar todos os registros, encontrar o índice e usar `_delete()` diretamente
59+
60+
**Código exemplo do workaround:**
61+
```python
62+
# Ao invés de:
63+
eval_sql(f"SELECT * FROM {table_name} WHERE id = '{id}'", tables, {})
64+
65+
# Usamos:
66+
result = eval_sql(f"SELECT * FROM {table_name}", tables, {})
67+
for row in result:
68+
if row.get("id") == id:
69+
return row
70+
```
71+
72+
**TODO para json-sql:** Investigar por que WHERE clause falha em alguns contextos. Os testes unitários da biblioteca passam, mas em uso real com `FileSystemJsonTables` o problema ocorre consistentemente.
73+
74+
### 2. Singleton Pattern para FileSystemJsonTables
75+
**Problema:** Criar uma nova instância de `FileSystemJsonTables` a cada acesso poderia causar problemas de sincronização.
76+
77+
**Solução:** Implementado um singleton pattern usando `_tables_instance` cached no `SqlStorage`.
78+
79+
## Notas de Implementação
80+
81+
### Serialização
82+
- Valores são serializados usando `abstra_internals.interface.sdk.tables.utils.serialize()`
83+
- Objetos complexos (dict, list) são convertidos para JSON strings
84+
- Valores None são armazenados como strings vazias
85+
- Na deserialização, tentamos fazer parse JSON, se falhar mantemos como string
86+
87+
### Campo ID
88+
- O campo `id` é sempre criado como coluna primary key, mesmo que não exista no modelo Pydantic
89+
- Se o modelo não tem campo `id`, ele é adicionado automaticamente ao salvar
90+
- Isso garante compatibilidade com modelos que não têm `id` explícito
91+
92+
### Thread Safety
93+
- Mantido o uso de `mp_context.RLock()` para garantir thread-safety nas operações
94+
95+
## Testes
96+
Todos os 7 testes passaram:
97+
- ✅ test_clear
98+
- ✅ test_delete
99+
- ✅ test_load_all
100+
- ✅ test_load_nonexistent
101+
- ✅ test_save_and_load
102+
- ✅ test_save_invalid_data
103+
- ✅ test_reserved_sql_keywords (novo teste para palavras reservadas)
104+
105+
## Próximos Passos
106+
1. Reportar o problema do WHERE clause no repositório do abstra-json-sql
107+
2. Quando o bug for corrigido, remover os workarounds e usar WHERE clause diretamente
108+
3. Considerar adicionar índices para melhorar performance em tabelas grandes
109+
4. Avaliar uso de `FileSystemJsonLTables` (JSONL format) para melhor performance com grandes volumes

abstra_json_sql/apply.py

Lines changed: 36 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,51 @@
1-
from typing import List, Dict, Optional
2-
from .infer import infer_expression
3-
from .parser import parse_expression
4-
from .lexer import scan
5-
from .tables import ITablesSnapshot, Table, Column
6-
from .persistence import ExtendedTables
7-
from .field_name import field_name, expression_name
1+
from dataclasses import dataclass
2+
from typing import Dict, List, Optional
3+
84
from .ast import (
5+
AndExpression,
6+
Command,
7+
DefaultExpression,
98
Delete,
9+
DivideExpression,
10+
EqualExpression,
1011
Expression,
11-
Update,
12-
StringExpression,
13-
IntExpression,
14-
Select,
12+
FalseExpression,
1513
FloatExpression,
16-
With,
17-
NameExpression,
18-
DefaultExpression,
1914
From,
20-
SelectField,
21-
Where,
22-
Wildcard,
23-
GroupBy,
2415
FunctionCallExpression,
25-
Command,
26-
PlusExpression,
27-
Insert,
28-
NullExpression,
29-
NotExpression,
30-
AndExpression,
31-
OrExpression,
32-
IsExpression,
33-
FalseExpression,
34-
TrueExpression,
35-
OrderBy,
36-
MinusExpression,
37-
MultiplyExpression,
38-
DivideExpression,
39-
EqualExpression,
40-
NotEqualExpression,
4116
GreaterThanExpression,
4217
GreaterThanOrEqualExpression,
18+
GroupBy,
19+
Insert,
20+
IntExpression,
21+
IsExpression,
4322
LessThanExpression,
4423
LessThanOrEqualExpression,
4524
Limit,
25+
MinusExpression,
26+
MultiplyExpression,
27+
NameExpression,
28+
NotEqualExpression,
29+
NotExpression,
30+
NullExpression,
31+
OrderBy,
32+
OrExpression,
33+
PlusExpression,
34+
Select,
35+
SelectField,
36+
StringExpression,
37+
TrueExpression,
38+
Update,
39+
Where,
40+
Wildcard,
41+
With,
4642
)
47-
from dataclasses import dataclass
43+
from .field_name import expression_name, field_name
44+
from .infer import infer_expression
45+
from .lexer import scan
46+
from .parser import parse_expression
47+
from .persistence import ExtendedTables
48+
from .tables import Column, ITablesSnapshot, Table
4849

4950

5051
def is_aggregate_function(name: str) -> bool:

abstra_json_sql/apply_test.py

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,27 @@
11
from unittest import TestCase
2+
3+
from .apply import (
4+
apply_expression,
5+
apply_group_by,
6+
apply_limit,
7+
apply_order_by,
8+
apply_where,
9+
)
210
from .ast import (
3-
Where,
11+
DivideExpression,
12+
FloatExpression,
413
GreaterThanExpression,
5-
NameExpression,
14+
GroupBy,
615
IntExpression,
16+
Limit,
17+
MinusExpression,
18+
MultiplyExpression,
19+
NameExpression,
720
OrderBy,
821
OrderField,
9-
GroupBy,
10-
StringExpression,
1122
PlusExpression,
12-
MinusExpression,
13-
MultiplyExpression,
14-
DivideExpression,
15-
FloatExpression,
16-
Limit,
17-
)
18-
19-
from .apply import (
20-
apply_expression,
21-
apply_where,
22-
apply_order_by,
23-
apply_group_by,
24-
apply_limit,
23+
StringExpression,
24+
Where,
2525
)
2626

2727

abstra_json_sql/ast.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
from typing import List, Optional, Literal, Union, Tuple
2-
from dataclasses import dataclass
31
from abc import ABC
2+
from dataclasses import dataclass
3+
from typing import List, Literal, Optional, Tuple, Union
44

55

66
@dataclass

abstra_json_sql/cli.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
1-
from pathlib import Path
2-
from .tables import Column, ColumnType, Table
3-
from .persistence import FileSystemJsonLTables
4-
from .eval import eval_sql
51
from argparse import ArgumentParser
62
from csv import DictWriter
3+
from json import dumps
4+
from pathlib import Path
75
from sys import stdout
86
from typing import List, Literal
9-
from json import dumps
7+
8+
from .eval import eval_sql
9+
from .persistence import FileSystemJsonLTables
10+
from .tables import Column, ColumnType, Table
1011

1112

1213
def query(code: str, workdir: Path, ctx: dict):

abstra_json_sql/eval.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1+
from .apply import apply_command
12
from .lexer import scan
23
from .parser import parse
3-
from .apply import apply_command
44
from .tables import ITablesSnapshot
55

66

abstra_json_sql/eval_test.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
from unittest import TestCase
2+
23
from .eval import eval_sql
3-
from .tables import Table, Column, ColumnType
44
from .persistence import InMemoryTables
5+
from .tables import Column, ColumnType, Table
56

67

78
class TestEvalSQL(TestCase):

abstra_json_sql/field_name.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from .ast import SelectField, NameExpression, FunctionCallExpression, Expression
1+
from .ast import Expression, FunctionCallExpression, NameExpression, SelectField
22

33

44
def field_name(field: SelectField) -> str:

abstra_json_sql/infer.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
1-
from .tables import ColumnType
21
from .ast import (
2+
DivideExpression,
33
Expression,
4-
StringExpression,
5-
IntExpression,
6-
FloatExpression,
7-
NullExpression,
8-
TrueExpression,
94
FalseExpression,
10-
PlusExpression,
5+
FloatExpression,
6+
FunctionCallExpression,
7+
IntExpression,
118
MinusExpression,
129
MultiplyExpression,
13-
DivideExpression,
14-
FunctionCallExpression,
10+
NullExpression,
11+
PlusExpression,
12+
StringExpression,
13+
TrueExpression,
1514
)
15+
from .tables import ColumnType
1616

1717

1818
def infer_expression(expr: Expression, ctx: dict) -> ColumnType:

abstra_json_sql/infer_test.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
from unittest import TestCase
2+
23
from .infer import infer_expression
3-
from .tables import ColumnType
44
from .lexer import scan
55
from .parser import parse_expression
6+
from .tables import ColumnType
67

78

89
class TestInferExpression(TestCase):

0 commit comments

Comments
 (0)