C++/Qt/SQL - kwestia poprawności kodu

C++/Qt/SQL - kwestia poprawności kodu
MI
  • Rejestracja:ponad 13 lat
  • Ostatnio:około 8 lat
  • Postów:110
0

Witam,
chciałbym poradzić się Was w sprawie poniższego kodu - **co można/należy poprawić/zmienić ? **

Mam klasę DataBaseOperation, która odpowiedzialna jest za komunikację z bazą danych..

Kopiuj
 
class DataBaseOperation;

//metoda wyciągająca dane produktow o konkretnej wartosci
QList<QStringList> DataBaseOperation::getValue(QString value)  
{                                                                                          
    QString query = QString("SELECT * FROM Product WHERE value= ?");

    QStringList arg;
    arg.append(value);

    QList<QStringList> result = this->Select(query, arg);

    return result;
}

// ogólna metoda wykorzystywana do wykonywania select'ów do bazy danych..
QList<QStringList> DataBaseOperation::Select(QString query, QStringList param)
{
    QSqlQuery Query;
    Query.prepare(query);

    for (int i=0; i<param.count(); i++)
        Query.bindValue(i,param[i]);

     if (!Query.exec()) 
         qDebug() << Query.lastError().text();
     
     QList<QStringList> tab;

     if (Query.size()>0)
     {
         while (Query.next())
         {
             QStringList record;
             for(int j=0;j< Query.record().count();j++)
                 record << Query.value(j).toString();

             tab.append(record);
         }
     }
     else
     {
         qDebug() << "error";
     }

     return list;
}

Klasa ValuenWidget wyświetlająca dane osoby np. w QEditLine'ach..

Kopiuj
 
class ValueWidget;

DataBaseOperation db;
QList<QStringList> records2 = db.getValue(QString value);

A teraz jeszcze konkretne pytania od mnie:

  1. Wydaje mi się, że tutaj powinno się raczej stosować typ INT, bo tego typu jest kolumna w bazie, ale z drugiej strony wszędzie trzeba operować na stringach więc i tak trzeba rzutować..
Kopiuj
QList<QStringList> DataBaseOperation::getValue(QString value)  
  1. Czy w takich sytuacja, w c++ argumenty funkcji powinno przekazywać się przez referencje ? (deklaracja funkcji z &query, &arg ) ?
Kopiuj
QList<QStringList> result = this->Select(query, arg);
  1. Jak zbudowalibyście metodę wyciągającą dane z bazy danych - chodzi konkretnie o select'a ?
Kopiuj
QList<QStringList> DataBaseOperation::Select(QString query, QStringList param)

Ja zdecydowałem się wykorzystać typ QList<QStringList>, gdzie QStringList reprezentują kolejne wiersze, natomiast QList jest kontenerem (tabelą) dla tych wierszy.. Zdaje to egzamin, ale coś nie 'pasi' mi przy przekazywaniu tego typu między klasami/metodami.. bo praktycznie jest on przekazywany przez wartość.. (btw. czy w tym przypadku to jest złe ?). Chciałem wykorzystać tutaj wskaźniki, ale zauważyłem że byłby problem z prawidłowym zwalnianiem pamięci.. (przynajmniej jeśli zachowałbym kod w takiej postaci jak jest..)

podsumowując: szukam podpowiedzi mających na celu wzniesienie tego kod na wyższy (odpowiedni) poziom...

gośćabc
  • Rejestracja:prawie 11 lat
  • Ostatnio:ponad 3 lata
  • Lokalizacja:Szczecin
  • Postów:500
0
MI
  • Rejestracja:ponad 13 lat
  • Ostatnio:około 8 lat
  • Postów:110
0

@gośćabc - rozumiem, że twoje 'wskazówki' dotyczą przede wszystkim SQL injection ? Niestety nie wiele znalazłem tam informacji na temat uniknięcia sql-injection w Qt, ale sam znalazłem inne źródło ( https://www.ics.com/designpatterns/book/qsqldatabase.html ) i znalazłem informację, iż aby uniknąć wstrzykiwania kodu należy stosować metodę bindValue() dla używanych kolumn, co też czynie w zamieszczony w pierwszym poście:

Kopiuj
 Query.bindValue(i,param[i]); 

Jeżeli chodziło Tobie o coś innego to proszę o doprecyzowanie :) Poza tym zadaje moje pytania jeszcze raz i nadal nie znam na nie odpowiedzi.. :@

edytowany 1x, ostatnio: mikajlo
gośćabc
  • Rejestracja:prawie 11 lat
  • Ostatnio:ponad 3 lata
  • Lokalizacja:Szczecin
  • Postów:500
0

w ogóle nie przeczytałeś tego co Ci podlinkowałem, "in which malicious SQL statements are inserted into an entry field for execution", "With most development platforms, parameterized statements that work with parameters can be used (sometimes called placeholders or bind variables) instead of embedding user input in the statement." to z linku 3

czemu poruszyłem temat sql injection, bo Ty to u siebie robisz; w skrócie pozwalasz, aby input z zewnątrz wylądował w kwerendzie bez weryfikacji z bazą danych (jaki typ ma to pole w bazie danych), ktoś kto będzie chciał Ci zaszkodzić, wprowadzi np. login DROP DATABASE i do widzenia z Twoją bazą;

w linku 2 masz obiekt odpowiedzialny za tworzenie i wykonywanie podanych kwerend; SA TAM PRZYKŁADY

a w linku 1 jak utworzyć połączenie do różnych baz danych

edit:
jeżeli nie znasz angielskiego to umieść to w profilu czy coś, bo to tylko komplikuje sprawy; a ja w języku polskim programowania nie tykam

edit:
sql injection tu nie ma sorx, nie czytałem więcej niż 1 funkcję Twojego przykładu co mnie sprowokowało do takiej a nie innej odpowiedzi

edytowany 2x, ostatnio: gośćabc
MI
@gośćabc - hehe, mimo wszystko dzięki za zainteresowanie ;)
kq
Moderator C/C++
  • Rejestracja:prawie 12 lat
  • Ostatnio:2 dni
  • Lokalizacja:Szczecin
1
mikajlo napisał(a):
  1. Wydaje mi się, że tutaj powinno się raczej stosować typ INT, bo tego typu jest kolumna w bazie, ale z drugiej strony wszędzie trzeba operować na stringach więc i tak trzeba rzutować..
    Qt zwraca Ci QVariant. Nie przekształcaj go w QStringa.
mikajlo napisał(a):
  1. Czy w takich sytuacja, w c++ argumenty funkcji powinno przekazywać się przez referencje ? (deklaracja funkcji z &query, &arg ) ?
    QString składa się wyłącznie z d-pointera, więc teoretycznie kopia sprowadzi się tylko do inkrementacji refcounta, ale ogółem, jeśli nie zamierzasz zmieniać tej wartości to przekazanie przez const ref jest wskazane (QString const&).
mikajlo napisał(a):
  1. Jak zbudowalibyście metodę wyciągającą dane z bazy danych - chodzi konkretnie o select'a ?
Kopiuj
QList<QStringList> DataBaseOperation::Select(QString query, QStringList param)

Ja zdecydowałem się wykorzystać typ QList<QStringList>, gdzie QStringList reprezentują kolejne wiersze, natomiast QList jest kontenerem (tabelą) dla tych wierszy.. Zdaje to egzamin, ale coś nie 'pasi' mi przy przekazywaniu tego typu między klasami/metodami.. bo praktycznie jest on przekazywany przez wartość.. (btw. czy w tym przypadku to jest złe ?). Chciałem wykorzystać tutaj wskaźniki, ale zauważyłem że byłby problem z prawidłowym zwalnianiem pamięci.. (przynajmniej jeśli zachowałbym kod w takiej postaci jak jest..)

podsumowując: szukam podpowiedzi mających na celu wzniesienie tego kod na wyższy (odpowiedni) poziom...
Pytasz o konstrukcję metody, a masz na myśli typ zwracany. Zapraszam do dokumentacji: http://qt-project.org/doc/qt-5/qlist.html#QList-2

QList::QList(const QList<T> & other)
Constructs a copy of other.

This operation takes constant time, because QList is implicitly shared.

Jak widać, przekazywanie tego jest stosunkowo tanie (znów refcount).

Zwróciłbym kontener<kontener<QVariant>>, gdzie kontener to std::vector lub QList, w zależności od potrzeb. Jeśli chodzi o samą metodę, to nie widzę specjalnie potrzeby aby istniała - QSqlQuery ma exec, a zbudować kwerendę i tak musisz (a bindValue czytelniej i bezpieczniej używać z parametrami po nazwach, nie po indeksach)

gośćabc napisał(a):

w ogóle nie przeczytałeś tego co Ci podlinkowałem, "in which malicious SQL statements are inserted into an entry field for execution", "With most development platforms, parameterized statements that work with parameters can be used (sometimes called placeholders or bind variables) instead of embedding user input in the statement." to z linku 3

czemu poruszyłem temat sql injection, bo Ty to u siebie robisz; w skrócie pozwalasz, aby input z zewnątrz wylądował w kwerendzie bez weryfikacji z bazą danych (jaki typ ma to pole w bazie danych), ktoś kto będzie chciał Ci zaszkodzić, wprowadzi np. login DROP DATABASE i do widzenia z Twoją bazą;
Mam wrażenie, że nie przeczytałeś jego kodu.

Jedyne miejsce, gdzie @mikajlo przypisuje wartość do kwerendy to:

Kopiuj
    for (int i=0; i<param.count(); i++)
        Query.bindValue(i,param[i]);

i robi to w bezpieczny (przynajmniej od strony SQL Injection) sposób.

@mikajlo: jeszcze jedna pierdółka (i raczej sprawa opinii, a nie poprawności): w zdecydowanej większości dobrego kodu z jakim mam styczność, jedyne co zaczyna się wielką literą - jeśli cokolwiek - to nazwy typów (no i makra, ale one są całe wielkimi), natomiast zmienne zaczynają się małą literą. To tak odnośnie Twojego Query.


gośćabc
tak, a co robi w 1 funkcji?
kq
Przygotowuje argumenty, które potem przypina do kwerendy za pomocą bindValue.
gośćabc
ale muka, racja, nie czytałem dalej, wadą nie jest tutaj sql inj. +1
MI
@kq - dzięki za odpowiedzi.. postaram się je wdrożyć w życie i prawdopodobnie wrzucę kod ponownie do wątku .. (bo zapewne jeszcze coś 'pokręce'..)
MI
  • Rejestracja:ponad 13 lat
  • Ostatnio:około 8 lat
  • Postów:110
0

Witam ponownie..
przerobiłem kod według wskazówek @kq , nie wszystkich.. ale o tym na końcu w pytaniach..

Kod:

Mam klasę DataBaseOperation, która odpowiedzialna jest za komunikację z bazą danych..

Kopiuj
 
class DataBaseOperation;

//metoda wyciągająca dane produktow o konkretnej wartosci
QList< QList<QVariant> > DataBaseOperation::getValue(QVariant const& value)  
{                                                                                          
    QString query = QString("SELECT * FROM Product WHERE value= ?");

    QList<QVariant> arg;
    arg.append(value);

    QList<QStringList> result = this->Select(query, arg);

    return result;
}

// ogólna metoda wykorzystywana do wykonywania select'ów do bazy danych..
QList< QList<QVariant> > DatabaseOperationsClass::Select(QString const& querytext, QList<QVariant> const& arg)
{
    QSqlQuery query;
    query.prepare(querytext);
    for (int i=0; i<arg.count();i++)
        query.bindValue(i,arg.at(i));

    if (!query.exec()) {
        qDebug() << query.lastError().text();
    }

    QList< QList<QVariant> > tab;

    if (query.size() > 0 )
    {
        while (query.next())
        {
           QList<QVariant> record;
           for(int i=0; i< query.record().count(); ++i)
              record.append(query.value(i));

           tab.append(record);
        }
    }
    else
    {
        qDebug() << "error";
    }

    return tab;
}

Klasa ValueWidget wyświetlająca dane osoby np. w QEditLine'ach..

Kopiuj
 
class ValueWidget;

DataBaseOperation db;
QList< QList<QVariant> > records2 = db.getValue(QVariant value);
  1. Przebudowałem metody select'ów, aby zwracały QList<QList <QVariant> >. Jednak nie wiem czy dalej dobrze rozumiem..
kq napisał(a):

Jeśli chodzi o samą metodę, to nie widzę specjalnie potrzeby aby istniała - QSqlQuery ma exec, a zbudować kwerendę i tak musisz (a bindValue czytelniej i bezpieczniej używać z parametrami po nazwach, nie po indeksach)

Czy chodziło Tobie o tego typu konstrukcję:

Kopiuj
     QSqlQuery query;
     query.prepare("INSERT INTO person (id, forename, surname) "
                   "VALUES (:id, :forename, :surname)");
     query.bindValue(":id", 1001);
     query.bindValue(":forename", "Bart");
     query.bindValue(":surname", "Simpson");
     query.exec();
 

Tylko o ile dobrze widzę, w takiej sytuacji każda metoda (np. selectPerson, selectValue, itd..) musiałaby zawierać QSqlQuery i dość rozbudowane bindowanie, tak ? W ten sposób zyskuje się na czytelności, ale z drugiej strony stosunkowo dużo kodu jest powielane... czy może się mylę, i tutaj akurat 'wskazane' jest powielanie tego kodu dla uzyskania lepszej czytelności.. ?

  1. I jeszcze jedno 'upewniające' mnie pytanie... Rozumiem, że stosowanie QVariant powinno zaoszczędzić trochę mocy obliczeniowej i w końcowym rozrachunku przyspieszyć wykonywanie zapytań, ponieważ nie będzie wymagane niepotrzebne rzutowanie między QVariant <--> QString. Ale pewnie to za bardzo 'kosztowne obliczenia' nie są.. (tak mi się wydaje przynajmniej..), czy są jeszcze jakieś korzyści stosowanie QVariant? (bo może to i mało znaczący szczegół, ale przyjemniej debuggowało (qDebug) mi się całe QStringList'y jako wiersze .. :P
Kliknij, aby dodać treść...

Pomoc 1.18.8

Typografia

Edytor obsługuje składnie Markdown, w której pojedynczy akcent *kursywa* oraz _kursywa_ to pochylenie. Z kolei podwójny akcent **pogrubienie** oraz __pogrubienie__ to pogrubienie. Dodanie znaczników ~~strike~~ to przekreślenie.

Możesz dodać formatowanie komendami , , oraz .

Ponieważ dekoracja podkreślenia jest przeznaczona na linki, markdown nie zawiera specjalnej składni dla podkreślenia. Dlatego by dodać podkreślenie, użyj <u>underline</u>.

Komendy formatujące reagują na skróty klawiszowe: Ctrl+B, Ctrl+I, Ctrl+U oraz Ctrl+S.

Linki

By dodać link w edytorze użyj komendy lub użyj składni [title](link). URL umieszczony w linku lub nawet URL umieszczony bezpośrednio w tekście będzie aktywny i klikalny.

Jeżeli chcesz, możesz samodzielnie dodać link: <a href="link">title</a>.

Wewnętrzne odnośniki

Możesz umieścić odnośnik do wewnętrznej podstrony, używając następującej składni: [[Delphi/Kompendium]] lub [[Delphi/Kompendium|kliknij, aby przejść do kompendium]]. Odnośniki mogą prowadzić do Forum 4programmers.net lub np. do Kompendium.

Wspomnienia użytkowników

By wspomnieć użytkownika forum, wpisz w formularzu znak @. Zobaczysz okienko samouzupełniające nazwy użytkowników. Samouzupełnienie dobierze odpowiedni format wspomnienia, zależnie od tego czy w nazwie użytkownika znajduje się spacja.

Znaczniki HTML

Dozwolone jest używanie niektórych znaczników HTML: <a>, <b>, <i>, <kbd>, <del>, <strong>, <dfn>, <pre>, <blockquote>, <hr/>, <sub>, <sup> oraz <img/>.

Skróty klawiszowe

Dodaj kombinację klawiszy komendą notacji klawiszy lub skrótem klawiszowym Alt+K.

Reprezentuj kombinacje klawiszowe używając taga <kbd>. Oddziel od siebie klawisze znakiem plus, np <kbd>Alt+Tab</kbd>.

Indeks górny oraz dolny

Przykład: wpisując H<sub>2</sub>O i m<sup>2</sup> otrzymasz: H2O i m2.

Składnia Tex

By precyzyjnie wyrazić działanie matematyczne, użyj składni Tex.

<tex>arcctg(x) = argtan(\frac{1}{x}) = arcsin(\frac{1}{\sqrt{1+x^2}})</tex>

Kod źródłowy

Krótkie fragmenty kodu

Wszelkie jednolinijkowe instrukcje języka programowania powinny być zawarte pomiędzy obróconymi apostrofami: `kod instrukcji` lub ``console.log(`string`);``.

Kod wielolinijkowy

Dodaj fragment kodu komendą . Fragmenty kodu zajmujące całą lub więcej linijek powinny być umieszczone w wielolinijkowym fragmencie kodu. Znaczniki ``` lub ~~~ umożliwiają kolorowanie różnych języków programowania. Możemy nadać nazwę języka programowania używając auto-uzupełnienia, kod został pokolorowany używając konkretnych ustawień kolorowania składni:

```javascript
document.write('Hello World');
```

Możesz zaznaczyć również już wklejony kod w edytorze, i użyć komendy  by zamienić go w kod. Użyj kombinacji Ctrl+`, by dodać fragment kodu bez oznaczników języka.

Tabelki

Dodaj przykładową tabelkę używając komendy . Przykładowa tabelka składa się z dwóch kolumn, nagłówka i jednego wiersza.

Wygeneruj tabelkę na podstawie szablonu. Oddziel komórki separatorem ; lub |, a następnie zaznacz szablonu.

nazwisko;dziedzina;odkrycie
Pitagoras;mathematics;Pythagorean Theorem
Albert Einstein;physics;General Relativity
Marie Curie, Pierre Curie;chemistry;Radium, Polonium

Użyj komendy by zamienić zaznaczony szablon na tabelkę Markdown.

Lista uporządkowana i nieuporządkowana

Możliwe jest tworzenie listy numerowanych oraz wypunktowanych. Wystarczy, że pierwszym znakiem linii będzie * lub - dla listy nieuporządkowanej oraz 1. dla listy uporządkowanej.

Użyj komendy by dodać listę uporządkowaną.

1. Lista numerowana
2. Lista numerowana

Użyj komendy by dodać listę nieuporządkowaną.

* Lista wypunktowana
* Lista wypunktowana
** Lista wypunktowana (drugi poziom)

Składnia Markdown

Edytor obsługuje składnię Markdown, która składa się ze znaków specjalnych. Dostępne komendy, jak formatowanie , dodanie tabelki lub fragmentu kodu są w pewnym sensie świadome otaczającej jej składni, i postarają się unikać uszkodzenia jej.

Dla przykładu, używając tylko dostępnych komend, nie możemy dodać formatowania pogrubienia do kodu wielolinijkowego, albo dodać listy do tabelki - mogłoby to doprowadzić do uszkodzenia składni.

W pewnych odosobnionych przypadkach brak nowej linii przed elementami markdown również mógłby uszkodzić składnie, dlatego edytor dodaje brakujące nowe linie. Dla przykładu, dodanie formatowania pochylenia zaraz po tabelce, mogłoby zostać błędne zinterpretowane, więc edytor doda oddzielającą nową linię pomiędzy tabelką, a pochyleniem.

Skróty klawiszowe

Skróty formatujące, kiedy w edytorze znajduje się pojedynczy kursor, wstawiają sformatowany tekst przykładowy. Jeśli w edytorze znajduje się zaznaczenie (słowo, linijka, paragraf), wtedy zaznaczenie zostaje sformatowane.

  • Ctrl+B - dodaj pogrubienie lub pogrub zaznaczenie
  • Ctrl+I - dodaj pochylenie lub pochyl zaznaczenie
  • Ctrl+U - dodaj podkreślenie lub podkreśl zaznaczenie
  • Ctrl+S - dodaj przekreślenie lub przekreśl zaznaczenie

Notacja Klawiszy

  • Alt+K - dodaj notację klawiszy

Fragment kodu bez oznacznika

  • Alt+C - dodaj pusty fragment kodu

Skróty operujące na kodzie i linijkach:

  • Alt+L - zaznaczenie całej linii
  • Alt+, Alt+ - przeniesienie linijki w której znajduje się kursor w górę/dół.
  • Tab/⌘+] - dodaj wcięcie (wcięcie w prawo)
  • Shit+Tab/⌘+[ - usunięcie wcięcia (wycięcie w lewo)

Dodawanie postów:

  • Ctrl+Enter - dodaj post
  • ⌘+Enter - dodaj post (MacOS)