ezig/Reaper jest bezpośrednim forkiem Mestway/Scythe. Porównanie obecnych gałęzi master pokazuje, że Reaper jest 67 commitów przed Scythe i 0 commitów za nim; zmiany obejmują około 70 plików.
Najważniejsza różnica funkcjonalna jest wyraźna:
- Scythe syntetyzuje zapytania
SELECTz tabel wejściowych i oczekiwanego wyniku. - Reaper zachowuje syntezę
SELECT, ale dodaje syntezęUPDATEorazDELETEna podstawie stanu tabeli przed i po modyfikacji.
| Obszar | Scythe | Reaper |
|---|---|---|
Zwykły SELECT |
Tak | Tak, odziedziczony |
SELECT z agregacją |
Tak, flaga -aggr |
Tak, flaga -aggr |
UPDATE |
Nie | Tak, flaga -update |
DELETE |
Nie | Tak, flaga -del |
INSERT / MERGE |
Nie | Nie |
| Wskazanie modyfikowanej tabeli | Brak takiego pojęcia | Jedna tabela oznaczona jako #input* nazwa |
Synteza WHERE |
Część zapytania SELECT |
Używana jako klasyfikator wierszy do zmiany/usunięcia |
Synteza SET |
Nie dotyczy | Stała, kopia kolumny albo podzapytanie |
| Liczba wyników DML | Nie dotyczy | Praktycznie drukowany jest jeden pierwszy kandydat |
Interfejs Reapera wprost dodaje cztery tryby Select, WAggr, Update i Delete, podczas gdy CLI Scythe rozróżnia tylko zwykłą syntezę i syntezę z agregacjami.
Reaper traktuje przykład jako:
tabela przed UPDATE
+
inne tabele wejściowe
+
tabela po UPDATE
Przebieg jest następujący:
- Porównuje wiersze przed i po według ich pozycji.
- Wyznacza indeksy zmienionych wierszy.
- Buduje pomocniczy przykład zawierający tylko zmienione wiersze.
- Uruchamia odziedziczony syntezator
SELECT, aby znaleźć zapytanie wybierające te wiersze. - Z warunku tego
SELECTbierze klauzulęWHERE. - Osobno syntetyzuje klauzulę
SET. - Składa
UPDATE tabela SET ... WHERE ....
Kod UpdateSynthesizer pokazuje dokładnie ten podział na:
generateCandidateFilters(...)
AbstractSetClause.enumerateFromIO(...)
new UpdateNode(orig, candidateFilters, setClause)Dla każdej kolumny Reaper próbuje kolejno:
-
Brak zmiany
Taka kolumna nie jest drukowana w
SET. -
Przypisanie stałej
SET firstname = 'John'
Stałe podawane są osobno dla poszczególnych kolumn przez pole
updateConstants. -
Skopiowanie innej kolumny
SET destination = source
-
Skalarne podzapytanie
SET text = ( SELECT ... )
Kod nie ma osobnego wariantu dla wyrażeń arytmetycznych typu:
SET counter = counter + 1Zdefiniowane termy to tylko Identity, Constant, Projection i NestedQ.
Przykłady w repozytorium pokazują zamierzoną obsługę:
UPDATE customers
SET firstname = 'John', lastname = 'Smith'
WHERE id = 1;oraz skorelowanego podzapytania do tej samej tabeli:
UPDATE t
SET text = (
SELECT text
FROM t t2
WHERE t.id = t2.id
AND lang = 'EN'
)
WHERE text IS NULL;Dla DELETE Reaper:
- Traktuje wynik jako tabelę po usunięciu wierszy.
- Przechodzi jednocześnie po tabeli pierwotnej i wynikowej.
- Zakłada, że wynik jest podciągiem zachowującym kolejność wierszy wejściowych.
- Wiersze pominięte uznaje za usunięte.
- Ponownie używa Scythe, aby znaleźć
SELECT * FROM tabela WHERE ..., który wybiera dokładnie te wiersze. - Sam warunek wykorzystuje w
DELETE.
Przykład z repozytorium zakłada syntezę warunku z agregacją:
DELETE FROM notes
WHERE id = (SELECT MAX(id) FROM notes);To jest prawdopodobnie najciekawsza część techniczna.
Reaper nie ma całkowicie nowego syntezatora predykatów. Zamiast tego zamienia problem:
znajdź warunek wybierający zmienione wiersze
na problem Scythe:
znajdź
SELECT, którego wynikiem są zmienione wiersze.
Następnie akceptuje tylko kandydata równoważnego:
SELECT *
FROM modyfikowana_tabela
WHERE predicatei wyciąga z niego predicate.
Reaper potrafi przepisać niektóre rozwiązania oparte na joinie:
SELECT *
FROM target
JOIN nested_result ...na:
SELECT *
FROM target
WHERE target.x = (
SELECT ...
)Ale są mocne ograniczenia:
- join musi mieć dokładnie dwa składniki;
- jeden musi odpowiadać modyfikowanej tabeli;
- drugi składnik musi zwracać dokładnie wynik
1 × 1; - końcowy klasyfikator nadal musi być oparty bezpośrednio na docelowej tabeli.
Do tego celu dodano nowy NestedQueryCompFilter, reprezentujący porównanie wartości kolumny z wynikiem skalarnego podzapytania.
Nie jest to więc pełna obsługa dowolnego:
UPDATE ... JOIN ...
DELETE ... USING ...
WHERE EXISTS (...)Reaper raczej próbuje sprowadzić rozwiązanie do prostego UPDATE/DELETE na jednej tabeli, ewentualnie z podzapytaniami.
Reaper rozszerza parser przykładów o oznaczenie tabeli modyfikowanej gwiazdką:
#input* customers
Pozostałe tabele są zwykłymi:
#input products
Parser dopuszcza tylko jedną modyfikowaną tabelę i rzuca wyjątek przy próbie oznaczenia kilku.
Konfiguracja zyskuje również osobne stałe dla poszczególnych kolumn:
{
"constants": [1],
"updateConstants": {
"firstname": ["John"],
"lastname": ["Smith"]
},
"aggregation_functions": []
}W Scythe były tylko ogólne stałe i funkcje agregujące.
Reaper dodaje mechanizm oznaczania kolumn identyfikatorów:
id[id1]
customer_id[id1]
Kolumny z takim samym numerem należą do tej samej grupy identyfikatorów. Reaper próbuje zamienić konkretne ID na losowe wartości, przeprowadzić syntezę, a następnie zweryfikować wynik na oryginalnych danych.
Cel jest sensowny: zapytanie nie powinno powstać tylko dlatego, że w pojedynczym przykładzie przypadkowo występują ID 1, 2, 3.
Transformacja nie jest wykonywana, gdy oznaczona kolumna ID sama jest modyfikowana.
Jest jednak problem implementacyjny: komentarz mówi o zachowaniu kolejności ID, ale mapowanie iteruje po HashSet<Integer>. Java nie gwarantuje kolejności iteracji HashSet, więc transformacja nie gwarantuje faktycznie monotonicznego mapowania.
Reaper nadal zawiera niemal cały mechanizm Scythe:
- enumerację joinów;
- agregacje;
- left joiny;
- uniony obecne w enumeratorze;
- wyszukiwanie predykatów;
- ranking kandydatów;
- rozkładanie wyniku na części.
Fork dodaje jednak kilka elementów potrzebnych przez DML:
- możliwość ograniczenia do wymaganej tabeli bazowej przez
requiredBase; - ewaluację filtra w kontekście konkretnego wiersza;
- przepisywanie filtrów na filtry z podzapytaniem;
- rozbudowane usuwanie zbędnych aliasów i rename'ów;
- poprawki obsługi
NULL; - opcję poprawiania czytelności wygenerowanego SQL.
Przykładowo Environment w Reaperze ma konstruktor przyjmujący wiersz i nazwę tabeli, którego nie ma w Scythe. Jest on używany do oceniania predykatu dla poszczególnych modyfikowanych wierszy.
To najpoważniejsze ograniczenie modelu danych.
UPDATE porównuje wiersz numer i wejścia z wierszem numer i wyniku. DELETE zakłada, że pozostałe rekordy tworzą podciąg wejścia w tej samej kolejności.
W SQL bez ORDER BY kolejność wierszy nie jest semantycznie określona. Reaper może więc błędnie interpretować samo przestawienie wierszy jako serię aktualizacji lub usunięć.
Nie dopasowuje rekordów po kluczu głównym.
Może istnieć dokładnie jedna tabela #input*. Nie ma wielotabelowych modyfikacji ani syntezy kilku instrukcji DML.
Nie ma bezpośredniej obsługi:
SET value = value + 1
SET value = CONCAT(a, b)
SET value = CASE ...Możliwości kończą się na stałej, kopii kolumny i podzapytaniu.
Dla kilku aktualizowanych wierszy podzapytanie w SET musi zostać rozpoznane jako skorelowane. Kod odrzuca m.in. kandydatów z joinem większym niż dwuelementowy.
Choć wewnętrznie istnieje lista filtrów i kandydatów, UpdateNode i DeleteNode używają:
candidateFilters.get(0)Podzapytanie SET również bierze pierwszy element rewrittenCandidates. W przeciwieństwie do deklarowanego przez Scythe zwracania listy zapytań, tryby DML praktycznie prezentują tylko jeden wynik.
To nie są wyłącznie ograniczenia projektu — w aktualnym master są rzeczywiste błędy.
Kod zawiera:
if (!orig.getContent().containsAll(orig.getContent())) {
return false;
}To porównuje kolekcję samą ze sobą i zawsze daje true. Najprawdopodobniej miało być:
orig.getContent().containsAll(modified.getContent())W efekcie wynik zawierający rekordy, których nie było w wejściu, nie zostanie prawidłowo odrzucony.
W removeFirstNCandidates jest:
Integer i = 0;
while (i < n) {
candidates.remove(0);
rewrittenCandidates.remove(0);
}Brakuje i++. Gdy poprawnym rozwiązaniem jest kandydat o indeksie większym od zera, metoda będzie usuwać elementy aż do wyjątku.
Generator drukuje:
DELETE notes
WHERE ...zamiast bardziej przenośnego:
DELETE FROM notes
WHERE ...Pierwsza składnia może działać w niektórych dialektach, ale nie jest poprawna np. dla SQLite, mimo że jeden z dołączonych przykładów pochodzi właśnie z SQLite i w rozwiązaniu używa DELETE FROM.
Mechanizm transformacji ID używa HashSet, chociaż komentarz deklaruje zachowanie porządku. To może zmieniać znaczenie predykatów <, >, MIN i MAX.
Reaper nie jest alternatywną, ulepszoną wersją Scythe do ogólnej syntezy SELECT. Jest wyspecjalizowanym rozszerzeniem badawczym:
Scythe
+ rozpoznanie zmienionych/usuniętych wierszy
+ synteza klasyfikatora WHERE
+ synteza SET
+ AST dla UPDATE/DELETE
+ kilka transformacji upraszczających SQL
= Reaper
Dla problemu polegającego na znalezieniu prostego SELECT, który generuje daną listę ID, Reaper nie daje dużej przewagi nad Scythe. Jego najważniejsze dodatki uruchamiają się dopiero wtedy, gdy wejściem jest stan tabeli przed i po operacji.
Dla syntezy UPDATE lub DELETE Reaper jest natomiast zdecydowanie ciekawszą bazą, ale obecnego kodu nie traktowałbym jako gotowej biblioteki. Przed dalszym forkiem należałoby co najmniej:
- naprawić walidację
DELETE; - naprawić
removeFirstNCandidates; - uniezależnić porównanie od kolejności wierszy, najlepiej przez wskazany klucz;
- poprawić generowanie
DELETE FROM; - rozszerzyć AST
SETo operatory arytmetyczne i funkcje; - zwracać listę zweryfikowanych kandydatów zamiast pierwszego;
- bezpośrednio ewaluować końcowe, przepisane podzapytania skorelowane.