Skip to content
 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

270 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Reaper: Synthesizing SQL data modification queries from input output examples.

Różnice względem Mestway/Scythe

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 SELECT z tabel wejściowych i oczekiwanego wyniku.
  • Reaper zachowuje syntezę SELECT, ale dodaje syntezę UPDATE oraz DELETE na podstawie stanu tabeli przed i po modyfikacji.

Porównanie funkcjonalne

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.


Jak Reaper syntetyzuje UPDATE

Reaper traktuje przykład jako:

tabela przed UPDATE
+
inne tabele wejściowe
+
tabela po UPDATE

Przebieg jest następujący:

  1. Porównuje wiersze przed i po według ich pozycji.
  2. Wyznacza indeksy zmienionych wierszy.
  3. Buduje pomocniczy przykład zawierający tylko zmienione wiersze.
  4. Uruchamia odziedziczony syntezator SELECT, aby znaleźć zapytanie wybierające te wiersze.
  5. Z warunku tego SELECT bierze klauzulę WHERE.
  6. Osobno syntetyzuje klauzulę SET.
  7. Składa UPDATE tabela SET ... WHERE ....

Kod UpdateSynthesizer pokazuje dokładnie ten podział na:

generateCandidateFilters(...)
AbstractSetClause.enumerateFromIO(...)
new UpdateNode(orig, candidateFilters, setClause)

Obsługiwane formy SET

Dla każdej kolumny Reaper próbuje kolejno:

  1. Brak zmiany

    Taka kolumna nie jest drukowana w SET.

  2. Przypisanie stałej

    SET firstname = 'John'

    Stałe podawane są osobno dla poszczególnych kolumn przez pole updateConstants.

  3. Skopiowanie innej kolumny

    SET destination = source
  4. Skalarne podzapytanie

    SET text = (
        SELECT ...
    )

Kod nie ma osobnego wariantu dla wyrażeń arytmetycznych typu:

SET counter = counter + 1

Zdefiniowane 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;

Jak Reaper syntetyzuje DELETE

Dla DELETE Reaper:

  1. Traktuje wynik jako tabelę po usunięciu wierszy.
  2. Przechodzi jednocześnie po tabeli pierwotnej i wynikowej.
  3. Zakłada, że wynik jest podciągiem zachowującym kolejność wierszy wejściowych.
  4. Wiersze pominięte uznaje za usunięte.
  5. Ponownie używa Scythe, aby znaleźć SELECT * FROM tabela WHERE ..., który wybiera dokładnie te wiersze.
  6. 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);

W jaki sposób powstaje WHERE

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 predicate

i wyciąga z niego predicate.

Podzapytania w WHERE

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.


Nowy format przykładów

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.


Generalizacja identyfikatorów

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.


Zmiany w odziedziczonym silniku SELECT

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.


Istotne ograniczenia funkcjonalne

1. Zależność od kolejności 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.

2. Jeden target

Może istnieć dokładnie jedna tabela #input*. Nie ma wielotabelowych modyfikacji ani syntezy kilku instrukcji DML.

3. Brak pełnych wyrażeń w SET

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.

4. Ograniczona korelacja podzapytań

Dla kilku aktualizowanych wierszy podzapytanie w SET musi zostać rozpoznane jako skorelowane. Kod odrzuca m.in. kandydatów z joinem większym niż dwuelementowy.

5. DML drukuje tylko jednego kandydata

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.


Konkretne błędy w obecnym kodzie

To nie są wyłącznie ograniczenia projektu — w aktualnym master są rzeczywiste błędy.

Błędna walidacja DELETE

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.

Nieskończona pętla przy wyborze podzapytania

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.

Problematyczna składnia DELETE

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.

Brak gwarancji zachowania kolejności ID

Mechanizm transformacji ID używa HashSet, chociaż komentarz deklaruje zachowanie porządku. To może zmieniać znaczenie predykatów <, >, MIN i MAX.


Ocena praktyczna

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 SET o operatory arytmetyczne i funkcje;
  • zwracać listę zweryfikowanych kandydatów zamiast pierwszego;
  • bezpośrednio ewaluować końcowe, przepisane podzapytania skorelowane.

About

Synthesizing SQL queries from input / output examples

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages