О результатах однострочного запроса с параметризованным запросом

Итак, у меня есть несколько мест в моей программе, где я просто извлекаю одну строку (иногда даже один столбец в одной строке), например

SELECT id,title,posted FROM posts WHERE id=4;

Поскольку это вернет только одну строку (если только я не сосу материал с базой данных и каким-то образом умудряюсь иметь дублированные идентификаторы), я чувствую, что слишком сложно пройти весь путь sqlite3_prepare_v2(...), sqlite3_bind_int(...), sqlite3_step(...), sqlite3_finalize(...) только для получения одной строки.

Я знаю, что это можно сделать с помощью sqlite3_exec(...) и с помощью функции обратного вызова для обработки результатов, которые также return 1; прерывают дальнейшие выполнения, но в моем случае это не работает, потому что мой запрос выглядит так:

SELECT id,title,posted FROM posts WHERE id=?;

И у вас не может быть параметризованных запросов с sqlite3_exec(...). Вставка идентификатора через какой-то printf(...) здесь тоже небезопасна.

Итак, это то, что я делаю прямо сейчас

char *sql;
sqlite3_stmt *stmt;

sql = sqlite3_mprintf(
       "SELECT \
          title, \
          ((julianday(posted) - 2440587.5) * 86400), \
          text \
        FROM \
           %s \
        WHERE id=?;",
     POSTS_TABLE); /* printf safe here, POSTS_TABLE is a #define */

if (sqlite3_prepare_v2(db, sql, strlen(sql), &stmt, NULL) != SQLITE_OK)
   show_sqlite_error(db);
else
{
   sqlite3_bind_text(stmt, 1, id, strlen(id), SQLITE_TRANSIENT);
   sqlite_code = sqlite3_step(stmt); /* This right here seems just ugly */

   if (sqlite_code != SQLITE_ROW) /* especially this */
      printf("<div id=\"error\">ERROR: No such ID in database</div>\n");
   else
   {
      int i;
      char time_string[25];

      const unsigned char *title = sqlite3_column_text(stmt, 0);
      time_t time = sqlite3_column_int(stmt, 1);
      const unsigned char *text  = sqlite3_column_text(stmt, 2);

      strftime(time_string, 25, DATE_FORMAT, gmtime(&time));

      printf("<h2 id=\"post_headline\">%s</h2>\n\n", title);
      printf("<h3 id=\"post_time\">%s</h3>\n", time_string);
      printf("<p id=\"post_body\">\n");

      for (i = 0; i < strlen((char *)text); i++)
         if (text[i] == '\n')
            printf("<br />");
         else
            printf("%c", text[i]);
   }
}

Моя проблема с этим кодом заключается в том, что я вызываю sqlite3_step() один раз, выдаю ошибку, если было возвращено все, кроме SQLITE_ROW, отображаю все, что нужно отобразить, если было возвращено SQLITE_ROW, и вызываю finalize, фактически не переходя к SQLITE_DONE. Хотя это (как мне сказали) не причинит никакого вреда оператору SELECT, оно кажется уродливым и излишним.

Другое дело — запросы, которые просто возвращают одно значение.

SELECT MAX(id) FROM posts;

В моем случае это просто возвращает «5». Ни больше ни меньше. Особенно здесь это действительно излишество, чтобы пройти весь путь подготовки, привязки, шага, финализации, но я ДОЛЖЕН сделать это таким образом, потому что мой запрос снова выглядит как

SELECT MIN(id) FROM posts WHERE id > ?;

например, чтобы получить следующий идентификатор после текущего. Он просто возвращает «5», когда текущий идентификатор равен «4».

Подводя итог: есть ли быстрый способ получить результаты одной строки из оператора SELECT без явного зацикливания до тех пор, пока SQLITE_DONE не будет получено из sqlite3_step() И как мне получить одну строку, одно значение столбца (в основном скалярное), не повторяя весь путь снова, включая звонок sqlite_column_...?


person LukeN    schedule 09.07.2010    source источник
comment
Почему здесь нельзя вставить идентификатор с помощью sprintf?   -  person nos    schedule 09.07.2010
comment
если ничего другого ... Я бы реорганизовал это, чтобы все это управление SQL выполнялось в функции, чтобы это был однострочный вызов, когда я его фактически использую.   -  person Fosco    schedule 09.07.2010
comment
@nos Это сценарий CGI, и идентификатор предоставляется через URL-адрес. Хотя я мог бы сделать это здесь, просто преобразовав идентификатор в фактическое число, а не сохраняя его в виде строки, я все равно рано или поздно снова столкнусь с этими проблемами :)   -  person LukeN    schedule 09.07.2010


Ответы (1)


Я сторонник предотвращения SQL-инъекций, но я думаю, что это должно быть безопасно:

sql = sqlite3_mprintf("SELECT ... WHERE id=%d;", atoi(id));

Это интерпретирует строку id как целое число через atoi(), поэтому вероятность внедрения SQL отсутствует.

Что касается извлечения одной строки или одного скаляра, я бы написал функцию-оболочку, которая выполняет SQL и просто возвращает содержимое. Возможно, вам придется использовать глобальную переменную для передачи содержимого из функции обратного вызова в функцию-оболочку.

person Bill Karwin    schedule 09.07.2010
comment
Видит лучший способ сделать это, я полагаю. Кстати, полезно почитать про SQL-инъекции. Теперь я снова параноик. :( - person LukeN; 10.07.2010
comment
Вам не нужно быть параноиком, вам просто нужно методично и убедиться, что вы обработали каждый небезопасный ввод с помощью фильтрации, цитирования или параметризации. - person Bill Karwin; 10.07.2010