Является ли «неправильным» выдавать вставку SQLite, которая не вызывает sqlite3_bind? - PullRequest
0 голосов
/ 10 апреля 2020

Я пишу пользовательскую оболочку SQLite для игры MacOS, которая просто для удовольствия. Я изучил очень хороший учебник SQLite с Swift , и в нем описан трудоемкий метод построения оператора INSERT, который использует вызовы связывания, например:

let insertStatementString = "INSERT INTO Contact (Id, Name) VALUES (?, ?);"
insert(id:4, name:"Chris")

func insert(id: Int32, name: NSString) {

    var insertStatement: OpaquePointer? = nil

    if sqlite3_prepare_v2(db, insertStatementString, -1, &insertStatement, nil) == SQLITE_OK {

        sqlite3_bind_int(insertStatement, 1, id)
        sqlite3_bind_text(insertStatement, 2, name.utf8String, -1, nil)

        if sqlite3_step(insertStatement) == SQLITE_DONE {
            print("Successfully inserted row.")
        } else {
            print("Could not insert row.")
        }

    } else {
        print("INSERT statement could not be prepared.")
    }

    sqlite3_finalize(insertStatement)
}

Вместо этого я обнаружил, что следующее работает так же хорошо и исключает необходимость привязки данных к заявлению. Это рискованно писать так? Я понимаю, что подготовленные заявления быстрее / эффективнее, но мне не нужна скорость для моих целей. Я не вижу подобных примеров нигде. Спасибо за любой совет - я только начал изучать Swift и продолжаю находить вещи немного сложнее, чем я думал.

let insertItemSql : String = "INSERT INTO item (itemid, characterClass, 
itemtype, itemtypeid,  mincharges, maxcharges, name) VALUES (%d,%d,%d,%d,%d,%d,'%@');"

let sqlHelper = SQLHelper(databasePath: Globals.SharedInstance.databaseUrl)
let command = String(format: insertItemSql, item.id, 0, 0, 0, 0, 0, item.name)
let success = sqlHelper.nonQuery(sqlCommand: command)


func nonQuery(sqlCommand cmd: String) -> Bool {

        var success : Bool = false

        if let db = openDatabase() {

            var nonQueryStatement: OpaquePointer? = nil
            if sqlite3_prepare_v2(db, cmd, -1, &nonQueryStatement, nil) == SQLITE_OK {
                if sqlite3_step(nonQueryStatement) == SQLITE_DONE {
                    success = true
                } else {
                    print("Could not execute nonQuery statement.")
                    if let errorPointer = sqlite3_errmsg(db) {
                        let message = String.init(cString: errorPointer)
                        print("Error message was " + message)
                    }
                }
            } else {
                print("nonQuery statement could not be prepared")
                if let errorPointer = sqlite3_errmsg(db) {
                    let message = String.init(cString: errorPointer)
                    print("Error message was " + message)
                }
            }
            sqlite3_finalize(nonQueryStatement)

        }

        return success

    }

1 Ответ

2 голосов
/ 11 апреля 2020

Было бы преувеличением сказать, что это «неправильно», но это может быть agile и, как правило, не считается лучшей практикой. Рекомендация этого руководства по значениям привязки является хорошей техникой.

Например, что если значение, связанное со столбцом name, содержит '? Например, «О'Коннор». Одиночная кавычка в этой строке преждевременно завершит вашу строку SQL, например,

INSERT INTO item (itemid, characterClass, itemtype, itemtypeid,  mincharges, maxcharges, name)
    VALUES (1,2,3,4,5,6,'O'Connor');

. В этом сценарии (среди прочих) подготовка вручную созданного оператора SQL может завершиться неудачно. sqlite3_bind_text устраняет этот класс проблем и всегда безопасен. Как документация гласит:

Затем используйте функции sqlite3_bind_XXXX(), чтобы связать ваши большие строковые значения с оператором SQL. Использование связывания избавляет от необходимости экранировать кавычки в строке, снижая риск атак SQL инъекций. Он также работает быстрее, так как большую строку не нужно анализировать или копировать столько же.

Конечно, если вы знаете , что у вас никогда не возникнет проблем c input (например, это просто числовые или внутренние строковые значения, которые, как вы знаете, никогда не могут иметь ни одного из этих проблемных c значений), тогда вам не нужно использовать sqlite3_bind_xxx функции, но такое предположение, которое имеет тенденцию кусаться позже.

Как правило, значения привязки безопаснее. И если вы обеспокоены тем, что синтаксис sqlite3_bind_text является громоздким, то я мог бы предложить класс-оболочку SQLite, который выполняет связывание для вас, но абстрагирует вас от этих деталей.


Кстати, другое преимущество связывания для NULLABLE столбцов. Если вы просто используете ? заполнителей, то вы можете связать либо NULL, либо правильное значение для этого столбца. Если вы создаете SQL вручную, правильная обработка столбцов NULLABLE немного сложнее.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...