【问题标题】:PyQt5: Loading an extension into sqlitePyQt5:将扩展加载到 sqlite
【发布时间】:2020-10-14 17:26:06
【问题描述】:

我开始使用 PyQt5 及其带有 sqlite 的 Sql 类。我想将扩展加载到 sqlite。为此,必须在运行时为 sqlite 启用扩展加载。在python模块sqlite3中,这是通过enable_load_extension启用的。

在 C++ 中,sqlite 句柄可以这样获得(取自 https://doc.qt.io/qt-5/qsqldriver.html#handle):

QSqlDatabase db = QSqlDatabase::database();
QVariant v = db.driver()->handle();
if (v.isValid() && (qstrcmp(v.typeName(), "sqlite3*") == 0)) {
    // v.data() returns a pointer to the handle
    sqlite3 *handle = *static_cast<sqlite3 **>(v.data());
    if (handle) {
        // ...
    }
}

python 等价物是

from PyQt5.QtSql import QSqlDatabase

db = QSqlDatabase.addDatabase('QSQLITE')
db.driver().handle()
-> TypeError: unable to convert a C++ 'sqlite3*' instance to a Python object

附带说明,在 Pyside2 中,handle 方法没有公开。

看来这是错误的做法。有什么办法可以通过 PyQt5 加载我的 sqlite 扩展?

【问题讨论】:

    标签: python sqlite pyqt pyqt5


    【解决方案1】:

    一种可能的解决方案是创建一个使用 ctypes 加载的库。

    在这种情况下,我展示了 ubuntu linux 的解决方案,但我认为类似的步骤可以应用于其他操作系统。

    编译库

    qsqlite.pro

    QT -= gui
    QT += sql
    TEMPLATE = lib
    DEFINES += QSQLITE_LIBRARY
    CONFIG += unversioned_libname unversioned_soname
    CONFIG += c++11
    SOURCES += \
        qsqlite.cpp
    
    HEADERS += \
        qsqlite_global.h \
        qsqlite.h
    
    LIBS += -lsqlite3
    

    qsqlite_global.h

    #ifndef QSQLITE_GLOBAL_H
    #define QSQLITE_GLOBAL_H
    
    #if defined(_MSC_VER) || defined(WIN64) || defined(_WIN64) || defined(__WIN64__) || defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__)
    #  define Q_DECL_EXPORT __declspec(dllexport)
    #  define Q_DECL_IMPORT __declspec(dllimport)
    #else
    #  define Q_DECL_EXPORT     __attribute__((visibility("default")))
    #  define Q_DECL_IMPORT     __attribute__((visibility("default")))
    #endif
    
    #if defined(QSQLITE_LIBRARY)
    #  define QSQLITE_EXPORT Q_DECL_EXPORT
    #else
    #  define QSQLITE_EXPORT Q_DECL_IMPORT
    #endif
    
    #endif // QSQLITE_GLOBAL_H
    

    qsqlite.h

    #ifndef QSQLITE_H
    #define QSQLITE_H
    
    #include "qsqlite_global.h"
    
    class QSqlDriver;
    
    extern "C" {
        bool QSQLITE_EXPORT enable_extension(QSqlDriver *ptr, bool enabled);
    }
    
    #endif // QSQLITE_H
    

    qsqlite.cpp

    #include "qsqlite.h"
    
    #include <sqlite3.h>
    
    #include <QSqlDriver>
    #include <QVariant>
    
    bool enable_extension(QSqlDriver *driver, bool enabled)
    {
        if(!driver)
            return false;
        QVariant v = driver->handle();
        if (!v.isValid() || !(qstrcmp(v.typeName(), "sqlite3*")==0))
            return false;
        if(sqlite3 *db_handle = *static_cast<sqlite3 **>(v.data())){
            sqlite3_initialize();
            sqlite3_enable_load_extension(db_handle, enabled);
            return true;
        }
        return false;
    }
    
    qsqlite/
    ├── qsqlite.cpp
    ├── qsqlite_global.h
    ├── qsqlite.h
    └── qsqlite.pro
    

    要编译,你必须使用 Qt,所以在这种情况下,我将通过执行以下命令来使用 aqtinstall(python -m pip install aqtinstall):

    python -m aqt install 5.15.0 linux desktop --outputdir qt
    qt/5.15.0/gcc_64/bin/qmake qsqlite
    make
    

    注意:要编译库,必须有 sqlite3 的头文件,你必须在 ubuntu 中安装 libsqlite3-devsudo apt install -y --no-install-recommends libsqlite3-dev

    这将创建 libqsqlite.so 库,该库必须复制到脚本旁边,例如以下代码加载了 spatialite 模块(sudo apt install -y --no-install-recommends libsqlite3-mod-spatialite)。

    ma​​in.py

    from ctypes import CDLL, c_void_p
    import os
    
    from PyQt5.QtSql import QSqlDatabase, QSqlQuery
    
    import sip
    
    CURRENT_DIR = os.path.dirname(os.path.realpath(__file__))
    
    
    def load_spatialite():
        queries = (
            "SELECT load_extension('mod_spatialite')",
            "SELECT InitSpatialMetadata(1)",
        )
        q = QSqlQuery()
        for query in queries:
            if not q.exec_(query):
                print(
                    f"Error: cannot load the Spatialite extension ({q.lastError().text()})"
                )
                return False
        return True
    
    
    def main():
        db = QSqlDatabase.addDatabase("QSQLITE")
    
        db.setDatabaseName("foo.sqlite")
        if not db.open():
            sys.exit(-1)
    
        lib = CDLL(os.path.join(CURRENT_DIR, "libqsqlite.so"))
        lib.enable_extension(c_void_p(sip.unwrapinstance(db.driver()).__int__()), True)
        load_spatialite()
    
        query = QSqlQuery()
    
        query.exec_("CREATE TABLE my_line(id INTEGER PRIMARY KEY)")
        query.exec_(
            """SELECT AddGeometryColumn("my_line","geom" , 4326, "LINESTRING", 2)"""
        )
    
        polygon_wkt = "POLYGON ((11 50,11 51,12 51,12 50,11 50))"
    
        XA = 11
        YA = 52
        XB = 12
        YB = 49
    
        line_wkt = "LINESTRING({0} {1}, {2} {3})".format(XA, YA, XB, YB)
    
        query.prepare("""INSERT INTO my_line VALUES (?,GeomFromText(?, 4326))""")
    
        query.addBindValue(1)
        query.addBindValue(line_wkt)
        query.exec_()
    
        query.prepare(
            """SELECT astext(st_intersection(geom, GeomFromText(?, 4326))) from my_line WHERE st_intersects(geom, GeomFromText(?, 4326))"""
        )
        query.addBindValue(polygon_wkt)
        query.addBindValue(polygon_wkt)
        query.exec_()
    
        while query.next():
            for i in range(query.record().count()):
                print(query.value(i))
    
    
    if __name__ == "__main__":
        main()
    
    ├── main.py
    └── libqsqlite.so
    

    输出:

    LINESTRING(11.333333 51, 11.666667 50)
    

    PySide2 可以使用相同的库:

    from ctypes import CDLL, c_void_p
    import os
    
    from PySide2.QtSql import QSqlDatabase, QSqlQuery
    
    import shiboken2
    
    CURRENT_DIR = os.path.dirname(os.path.realpath(__file__))
    
    
    def load_spatialite():
        queries = (
            "SELECT load_extension('mod_spatialite')",
            "SELECT InitSpatialMetadata(1)",
        )
        q = QSqlQuery()
        for query in queries:
            if not q.exec_(query):
                print(
                    f"Error: cannot load the Spatialite extension ({q.lastError().text()})"
                )
                return False
        return True
    
    
    def main():
        db = QSqlDatabase.addDatabase("QSQLITE")
    
        db.setDatabaseName("foo.sqlite")
        if not db.open():
            sys.exit(-1)
    
        lib = CDLL(os.path.join(CURRENT_DIR, "libqsqlite.so"))
        lib.enable_extension(c_void_p(shiboken2.getCppPointer(db.driver())[0]))
        load_spatialite()
    
        query = QSqlQuery()
    
        query.exec_("CREATE TABLE my_line(id INTEGER PRIMARY KEY)")
        query.exec_(
            """SELECT AddGeometryColumn("my_line","geom" , 4326, "LINESTRING", 2)"""
        )
    
        polygon_wkt = "POLYGON ((11 50,11 51,12 51,12 50,11 50))"
    
        XA = 11
        YA = 52
        XB = 12
        YB = 49
    
        line_wkt = "LINESTRING({0} {1}, {2} {3})".format(XA, YA, XB, YB)
    
        query.prepare("""INSERT INTO my_line VALUES (?,GeomFromText(?, 4326))""")
    
        query.addBindValue(1)
        query.addBindValue(line_wkt)
        query.exec_()
    
        query.prepare(
            """SELECT astext(st_intersection(geom, GeomFromText(?, 4326))) from my_line WHERE st_intersects(geom, GeomFromText(?, 4326))"""
        )
        query.addBindValue(polygon_wkt)
        query.addBindValue(polygon_wkt)
        query.exec_()
    
        while query.next():
            for i in range(query.record().count()):
                print(query.value(i))
    
    
    if __name__ == "__main__":
        main()
    

    为了测试我使用了docker,你可以找到here

    【讨论】:

      猜你喜欢
      • 2017-01-12
      • 1970-01-01
      • 2019-02-24
      • 2012-02-04
      • 1970-01-01
      • 2012-08-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多