# Architektura

## Struktura projektu

<figure><img src="https://873576933-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FIClARKFpKUzMs7QIjylt%2Fuploads%2FrI7nfRIWclQ3TOwzDeNs%2Fcompare_classes.png?alt=media&#x26;token=09eb9bb6-469e-47c1-bd15-6cd3e9a264ef" alt=""><figcaption></figcaption></figure>

Logika serwisu podzielona jest na 4 główne serwisy:

* `CompareService` - odpowiada za walidację, inicjalizację i uruchamianie procesu porównania
* `TableComparer` - odpowiada za proces porównania ze sobą dwóch tabel
* `BindingService` - odpowiada za wstępne dopasowanie do siebie dwóch tabel i zwrócenie informacji o nich - kolumnach i ich typach, kluczach głównych, indeksach oraz zgodności tych typów oraz informację o koniecznych mapowaniach, które trzeba zaznaczyć w opcjach aby przeprowadzić porównanie tabel
* `CompareResultService` - odpowiada za zwracanie informacji o statusie porównania jako całość, konkretnych tabel oraz zapytań wygenerowanych w trakcie porównania do wyrównania stanu bazy docelowej

Cała interakcja z bazami danych wydzielona jest do klasy `QueryExecutor`, która wewnętrznie korzysta z odpowiednich dla bazy implementacji `QueryBuilder` oraz `DataManager`.&#x20;

`QueryBuilder` to klasa odpowiedzialna za generowanie zapytań z odpowiednią składnią. Gdy to możliwe buduje ona cache, aby zminimalizować obciążenie przy powtarzających się zapytaniach.

`DataManager` to klasa odpowiedzialna za mapowanie danych między bazą danych, a Java'owymi typami danych oraz konwersji oraz z reprezentacji tekstowej danych.

## Struktura bazy danych

Wszystkie tabele zapisywane są w schemacie `compare`

<figure><img src="https://873576933-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FIClARKFpKUzMs7QIjylt%2Fuploads%2FRye31CZNdVpZB8sw60lk%2Fcompare_entities.png?alt=media&#x26;token=0a944115-d344-4595-a7e9-74313d3d916e" alt=""><figcaption></figcaption></figure>

`CompareInfo` to główna encja, która zapisuje informację o porównaniu baz danych. Zapisana jest tam jest cała konfiguracja oraz w trakcie porównania aktualizowany jest stan zmiennych opisujących jego stan.

Dla każdej pary tabel do porównania tworzona jest encja `Table CompareInfo`,  która podobnie zapisuje konfigurację oraz służy do zapisu postępu stanu porównania.

Encja `TableCompareResultQuery` służy do wydzielenia zapisu wygenerowanych w trakcie porównania dwóch tabel zapytań korygujących bazę docelową.

Encja `PrimaryKeyEntry` służy do zapisu listy kluczy, dla których występuje różnica w podziale na operację DELETE, UPDATE, INSERT.

Encja `DatabaseConfiguration` służy do zapisu danych dostępowych do bazy danych. Hasło jest zabezpieczane przed zapisem. Jednakowe konfiguracje nie są duplikowane

Encja `TableParams` służy do zapisu danych o porównywanej tabeli. Jednakowe konfiguracje nie są duplikowane

## Ogólne flow porównywania

1. Inicjalizacja porównania - walidowane są informacje podane w request oraz zapisywane do bazy
2. Uruchomienie porównania - proces porównania zostaje zakolejkowany do puli wątków nadzorujących porównania. Rozmiar puli jest konfigurowalny.
3. Porównywanie tabel - każda para tabel porównywana jest w osobnym wątku, ich jednoczesna liczba jest ograniczona przez max. dozwoloną liczbę połączeń do bazy danych oraz konfigurowalny rozmiar puli wątków
4. Wątek nadzorujący porównanie czeka, aż wszystkie tabele zostaną porównane i zapisuje informację o jego statusie - jeśli któreś z poszczególnych porównań się nie powiedzie nie przekłąda się to na status całkości porównania

## Szczegóły procesu porównania tabel

W serwisie TableComparer wyizolowany jest proces porównania ze sobą dwóch tabel

1. Pobieranie informacji o typach danych kolumn i klucza głównego, walidacja ich zgodności
2. Porównianie tabel przebiegające w następujący sposób:\
   Serwis sprawdza ile jest rzędów w tabeli źródłowej i docelowej. Następnie iteruje przez wszystkie rzędy posortowane po podanym kluczu głównym pobierając za każdym razem z baz batch N rzędów (domyślnie 1000) oraz porównując rząd po rzędzie.\
   Porównanie polega na sprawdzeniu w pierwszej kolejności zgodności kluczy, a gdy są zgodne skrótu md5 pozostałych porównywanych kolumn. Zapisywane są przy tym informacje, które rzędy (klucze) należy aktualizować, usunąć i dodać do bazy docelowej.
3. Jeśli została wybrana opcja `recheckKeys` serwis po zdefiniowanym czasie (`keyRecheckWaitDurationMillis`) ponownie pobiera wszystkie rzędy, które zostały zakwalifikowane jako różniące się i sprawdza, czy nadal należy je zaktualizować/usunąć/dodać
4. Zapisywanie wyników - na tym etapie zapisywane są wszystkie znalezione klucze (`PrimaryKeyEntry`) oraz generowane są zapytania korygujące stan bazy docelowej (`TableCompareResultQuery`)

## Mapowanie typów danych

Klasa `DataManager` obsługuje mapowanie typów:

* z bazy do Javy &#x20;
* z Javy do bazy
* z Javy do string
* z string do Javy

Wszystkie mapowania odbywają się na podstawie struktury `ColumnInfo`:

```
String name; -> nazwa kolumny
ColumnType type; -> przypisany typ kolumny
int javaSqlType; -> typ kolumny zwracany przed Javę jako int
                    (liczby odpowiadją stałym w java.sql.Types)
String dbTypeName; -> typ kolumny pobrany z bazy danych
ColumnType originalType; -> pierwotny typ kolumny, wykorzystywane przy mapowaniu
                            typów char i int na boolean
boolean isMappedBoolean; -> flaga określająca, czy data kolumna jest mapowana na typ boolean
```

Zawartość pola `type` jest określana domyślnie na podstawie ręcznego mapowania z  `javaSqlType` i dodatkowo korygowana per konkretna baza danych na podstawie dodatkowych informacji o typie wyciąganym z bazy danych w momencie budowania struktury.\
Proces ten można kontrolować dodatkowo podając flagi, które są przekazywane do `DataManagera`. Na tę chwilę dostępne są trzy opcje:

* `mapBooleanTypes` -> jeśli flaga ustawiona na true moduł próbuje mapować typy boolean - jeśli w jednej bazie mamy BOOLEAN lub BIT, a w drugiej CHAR(1) lub NUMBER(1) to wartości z drugiej bazy będą interpretowane jako wartości boolean. Może prowadzić do błędów, jeśli w mapowanej kolumnie są nietypowe wartości. W przypadku opisu tekstowego compare obsługuje jedynie wartości 't' i 'f'.
* `treatOracleDateAsTimestamp` -> w bazie oracle type DATE obsługuje również godzinę. Jeśli oprócz dat w kolumnach są też godziny należy ustawić tę flagę na true i wtedy kolumna będzie traktowana jako timestamp. Jeśli jej nie ustawimy to część godzinowa będzie ignorowana przy generowaniu końcowych zapytań, ale w trakcie porównania ta godzina będzie powodowała wykrycie różnicy względem rzędu w którym godzina nie jest podana. &#x20;
* `mapPostgresMoneyTypeToDouble` -> typ MONEY w bazie postgres może mieć różne formatowanie, (np. $234.42) przez co nie da się przekonwertować go na typ numeric. Domyślnie jest on pobierany jako String, jednak gdy ta flaga jest ustawiona na TRUE to jest on castowany na typ numeric przy pobieraniu, co pozwala na porównywanie z innymi bazami.

\
Typy boolean są traktowane specjalnie, ponieważ na etapie mapowania musimy wiedzieć, jaki był oryginalny typ danych, w innych przypadkach nie jest to potrzebne.

`ColumnType` może przyjmować wartości:

```
STRING, DECIMAL, BIGINT, BOOLEAN, TIME_WITH_TIMEZONE, TIMESTAMP_WITH_TIMEZONE, DATE, TIME, TIMESTAMP, BINARY
```

Sam proces mapowania opiera się zazwyczaj na gotowych funkcjach w Javie:

* z bazy do Javy -> np. ResultSet::getTimestamp
* z Javy do bazy -> np. PreparedStatement::setTimestamp&#x20;
* z Javy do string -> Object.toString
* z string do Javy -> np. Timestamp::valueOf

W przypadku bardziej specyficznych typów potrzebne jest dodatkowe przetwarzanie
