Testowanie zmian w bazie danych

Potrzebuję wskazówek jak zabrać się za pewną aplikację, która pozwalała by użytkownikowi wpisywać polecenia surowego SQL’a i sprawdzać czy zostało ono wykonane. Dla przykładu użytkownik wpisuje:
CREATE TABLE Persons (PersonID int, LastName varchar(255), FirstName varchar(255)); a program wykonuje polecenie w bazie danych, a następnie sprawdza czy nowy rekord zosał poprawnie dodany (nie tylko czy liczba rekordów się powiekszyła, poprawność również), a następnie cofał by operacje.
W skrócie program ma sprawdzać znajomość SQL.
Z góry dziękuje za jakiekolwiek wskazówki, linki do materiałów itp.

Przedewszystkim musisz użyć postgresa bo mysql nie pozwala na rollbackowanie zapytań DDL (modyfikujących strukturę) w transakcjach. Więc mysql jest niestety tutaj bezuzyteczny chyba że dla każdego użytkownika będziesz tworzył nową bazę.

Tak czy tak stworzyłbym osobną bazę testową więc chcesz osiągnąć to coś takiego:

  class TestDatabase < ActiveRecord::Base
    establish_connection :sandbox_database # Zdefiniuj sandbox_database w config/database.yml
    self.abstract_class = true # Co by nie działało STI i można było z tej klasy dziedziczyć.
    self.inheritance_column = nil # Wyłącza STI nawet jakby się pojawiła kolumna "type"
  
    def self.test_sql(query)
      transaction do
        connection.execute query

        connection.schema_cache.clear! # głowy nie dam czy nadal to jest potrzebne,  dawniej nie było, potem było, nie wiem nawet czy jeszcze jest.
        reset_column_information

        check_results

        raise ActiveRecord::Rollback
      end
  
    def self.check_results
      columns.any?{|c| c.name="foo" && c.type==:bar}
      
      model = make_model "tabelka co ją miał użytkownik zrobić"
      
      model.exists?
      model.first
      model.where(first_name: "foo").first
      etc. 
    end

    def self.make_model(table_name)
      model = Class.new(self){ self.table_name = table_name } # Tu ważne żeby dziedziczyć

      # Ponizsze 2 linijki sa opcjonalne z tego co pamietam ale glowy nie dam bo pisze na sucho
      model_name = "EnsureTheresNoNameConflicts#{table_name.camelize}"
      const_set(model_name, model)

      model
  end

oczywiście sprawa jest ciut bardziej złożona bo trzeba przesłać warunki testowe itd. itp. ale powinno ci to dać pojęcie co trzeba zrobić. Problem jest deczko taki że będziesz musiał bardzo bardzo uważać żeby ci ktoś SQL incjection nie wpieprzył przez przypadeczek (dlatego polecam osobną bazę danych).

Powodzenia.

1 Like

Po wykonaniu następującej transakcji pobieram zmieniony rekord, następnie cofam transakcje i pobieram ponownie ten sam rekord do innej zmiennej. Dlaczego zmienna @task_backup przechowuje wartość ustawioną w transakcji którą potem cofnoąłem.

query = "update tasks set content='nowa tresc' where id= 5"

        connection = ActiveRecord::Base.connection
        Task.transaction do
        connection.execute query

        connection.schema_cache.clear! 
        reset_column_information

         @nowa = Task.find(5)

        raise ActiveRecord::Rollback
                 
        end

        @task_backup = Task.find(5)

Możesz sprawdzić i wkleić logi wykonania (SQL który jest wykonywany przez AR)?

Problem rozwiązany, powodem był prosty błąd.

Na początku chcę podziekować za pomoc, która jest dla mnie bardzo przydatna.
Mam jeszcze jeden temat, nad którym się zasatnawiam i proszę o rade, każdy komentarz będzie mile widziany.

Wiec mam przetestować polecenie SQL, które wprowadził użytkownik czy daje taki sam (lub podobny) wynik jak wcześniej wprowadzone poprawne polecenie SQL. Jeśli zapytania są sobie równe przydzielam użytkownikowi punkt (coś w stylu testu). Wykonuje w transakcji najpierw poprawne polecenie SQL, zapisuje do zmiennej, nastepnie robie rollback i to samo z drugim zapytaniem użytkownika.

Jeśli testuje zapytania typu SELECT stosuje narazie porównanie obu zmiennych (first_result.to_hash==second_result.to_hash), ale nie wiem czy w każdym przypadku taki sposób będzie dobry. Pozatym chciałbym sprawdzanie nie było wrażliwe na kolejność tabel w wyniku, oraz przydzialało połówkowe punkty za np. nie posortowanie wyniku.

A jeśli chodzi o polecenia UPDATE, DELETE, INSERT itd. to po wykonaniu polecenia SQL użytkownika porównuję całą baze danych z bazą po wykonianiu polecenia poprawnego. Tutaj już chyba nie da rady ocenić zapytania połówkowo.

Czy idę dobrym tropem, a może jest jakiś inny sposób?

@micrus Ja bym się raczej skoncentrował na sprawdzeniu konkretnych warunkó dla danego zapytania niż porównywaniu całych tabel.

czyli jeżeli masz DELETE FROM table WHERE id = 3 to sprawdząłbym czy SELECT COUNT(*) = 0 FROM table WHERE id = 3

Jeżeli chodzi o połówkowe punkty za sortowanie to po prostu przed porównaniem posortuj je w ruby ręcznie. TableA.all.to_a.sort = TableB.all.to_a.sort