|
| 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 |
0 commit comments