How to Load Extensions into SQLite

How to load extensions into SQLite?

SQLite extensions are libraries with dynamic linkage. You can find some examples here (This is a fossil repository, click on “login/fill captcha” to enable hyperlinks). See for example md5.c.

  • load_extension must be enabled in SQLite (pragma IIRC)
  • it requires as first argument the path of the library
  • The second argument is the name of the entry point function (in md5.c it is sqlite3_extension_init). Its prototype must be int(sqlite3*, char **, const sqlite3_api_routines *).
  • In SQL you can try SELECT load_extension('md5.so', 'sqlite3_extension_init'); or simply SELECT load_extension('md5.so');

You can try to compile md5.c, and from the sqlite shell use .load md5.so

PyQt5: Loading an extension into sqlite

One possible solution is to create a library that is loaded using ctypes.

In this case I show the solution for ubuntu linux but I suppose that similar steps can be applied to other OS.

Compile Library

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

To compile you must use Qt so in this case I will use aqtinstall(python -m pip install aqtinstall) by executing the following command:

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

Note: To compile the library it is necessary to have the headers of sqlite3 for this you must install with libsqlite3-dev in ubuntu: sudo apt install -y --no-install-recommends libsqlite3-dev.

This creates the libqsqlite.so library that must be copied next to the script, for example the following code loads the spatialite module(sudo apt install -y --no-install-recommends libsqlite3-mod-spatialite).

main.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

Output:

LINESTRING(11.333333 51, 11.666667 50)

The same library can be used for 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()

For the test I have used docker that you can find here.



Related Topics



Leave a reply



Submit