简体   繁体   English

使用 Python 中安装的 libsqlite3 版本

[英]Use the installed version of libsqlite3 from Python

Is it possible to use the installed version of SQLite3 from Python, with ctypes?是否可以使用 Python 中已安装的 SQLite3 版本和 ctypes? If so, how?如果是这样,如何?

On a Mac, the below works without error:在 Mac 上,下面的操作没有错误:

from ctypes import CDLL
libsqlite3 = CDLL("libsqlite3.dylib")

... but then from https://www.sqlite.org/c3ref/sqlite3.html ...但随后来自https://www.sqlite.org/c3ref/sqlite3.html

Each open SQLite database is represented by a pointer to an instance of the opaque structure named "sqlite3".每个打开的 SQLite 数据库都由一个指向名为“sqlite3”的不透明结构实例的指针表示。

(emphasis mine) (强调我的)

which to me suggests you can't really make a ctypes.Structure for the database, say to then pass to sqlite3_open .这对我来说表明你不能真正为数据库制作一个ctypes.Structure ,然后传递给sqlite3_open

(Context: I want to use parts of SQLite from Python that are not exposed by the built-in sqlite3 module) (上下文:我想使用 Python 中的 SQLite 的一部分,这些部分没有被内置的 sqlite3 模块公开)

The sqlite3 -API uses an opaque pointer, so in the end there is no need to know its memory layout - one just could use a void -pointer. sqlite3 -API 使用不透明指针,因此最终无需知道其 memory 布局 - 只需使用void -指针即可。

For example, opening a sqlite3 -database would create such a pointer:例如,打开一个sqlite3数据库会创建这样一个指针:

int sqlite3_open(
  const char *filename,   /* Database filename (UTF-8) */
  sqlite3 **ppDb          /* OUT: SQLite db handle */
);

ie the second parameter is a pointer to pointer.即第二个参数是指向指针的指针。 This function will create the structure and give its address to us - no need to know the exact layout of the the structur at all.这个 function 将创建结构并将其地址提供给我们——根本不需要知道结构的确切布局。

Later, we only need the address of this structure to be able to use further functionality, ie:之后,我们只需要这个结构的地址就可以使用更多的功能,即:

int sqlite3_close(sqlite3*);

The type-safety is something ensured by the compiler, once we have the machine code, the gloves are off and we can pass anything instead of sqlite3* to the function, but we have to ensure that it would work.类型安全是由编译器确保的,一旦我们有了机器代码,手套就脱掉了,我们可以将任何东西而不是sqlite3*传递给 function,但我们必须确保它能工作。 Any pointer can be replaced by void* as long as it points to a valid memory (ie with correct memory layout).任何指针都可以用void*替换,只要它指向有效的 memory(即具有正确的 memory 布局)。 That leads to:这导致:

import ctypes
libsqlite3 = ctypes.CDLL("libsqlite3.dylib")
sqlite3_handle = ctypes.c_void_p()  # nullptr

# pass handle by reference:
res = libsqlite3.sqlite3_open(b"mydb.db", ctypes.byref(sqlite3_handle))
print("open result",  res)                 # check res == 0
print("pointer value:", sqlite3_handle)    # address is set

# do what ever needed...

# example usage of handle:
res = libsqlite3.sqlite3_close(sqlite3_handle)
print("close result",  res)# check res == 0

sqlite3_handle = None # make sure nobody accesses dangling pointer

This is somewhat quick and dirty: usually one needs to set argument-types and return-value-type.这有点快速和肮脏:通常需要设置参数类型和返回值类型。 But in the functions above, defaults get correct behavior so I've skipped this (otherwise important) step.但是在上面的函数中,默认值会得到正确的行为,所以我跳过了这个(否则很重要)步骤。

Based on ead's answer , this is a more complete example of how to use libsqlite3 from Python, which is also at https://gist.github.com/michalc/a3147997e21665896836e0f4157975cb根据ead 的回答,这是一个更完整的示例,说明如何使用 Python 中的 libsqlite3,该示例也在https://gist.github.com/michalc/a3147997e21665896836e0f4157975cb

The below defines a (generator) function, query下面定义一个(生成器)function, query

from contextlib import contextmanager
from collections import namedtuple
from ctypes import cdll, byref, string_at, c_char_p, c_int, c_double, c_int64, c_void_p
from sys import platform


def query(db_file, sql, params=()):
    libsqlite3 = cdll.LoadLibrary({'linux': 'libsqlite3.so', 'darwin': 'libsqlite3.dylib'}[platform])
    libsqlite3.sqlite3_errstr.restype = c_char_p
    libsqlite3.sqlite3_errmsg.restype = c_char_p
    libsqlite3.sqlite3_column_name.restype = c_char_p
    libsqlite3.sqlite3_column_double.restype = c_double
    libsqlite3.sqlite3_column_int64.restype = c_int64
    libsqlite3.sqlite3_column_blob.restype = c_void_p
    libsqlite3.sqlite3_column_bytes.restype = c_int64
    SQLITE_ROW = 100
    SQLITE_DONE = 101
    SQLITE_TRANSIENT = -1
    SQLITE_OPEN_READWRITE = 0x00000002

    bind = {
        type(0): libsqlite3.sqlite3_bind_int64,
        type(0.0): libsqlite3.sqlite3_bind_double,
        type(''): lambda pp_stmt, i, value: libsqlite3.sqlite3_bind_text(pp_stmt, i, value.encode('utf-8'), len(value.encode('utf-8')), SQLITE_TRANSIENT),
        type(b''): lambda pp_stmt, i, value: libsqlite3.sqlite3_bind_blob(pp_stmt, i, value, len(value), SQLITE_TRANSIENT),
        type(None): lambda pp_stmt, i, _: libsqlite3.sqlite3_bind_null(pp_stmt, i),
    }

    extract = {
        1: libsqlite3.sqlite3_column_int64,
        2: libsqlite3.sqlite3_column_double,
        3: lambda pp_stmt, i: string_at(
            libsqlite3.sqlite3_column_blob(pp_stmt, i),
            libsqlite3.sqlite3_column_bytes(pp_stmt, i),
        ).decode(),
        4: lambda pp_stmt, i: string_at(
            libsqlite3.sqlite3_column_blob(pp_stmt, i),
            libsqlite3.sqlite3_column_bytes(pp_stmt, i),
        ),
        5: lambda pp_stmt, i: None,
    }

    def run(func, *args):
        res = func(*args)
        if res != 0:
            raise Exception(libsqlite3.sqlite3_errstr(res).decode())

    def run_with_db(db, func, *args):
        if func(*args) != 0:
            raise Exception(libsqlite3.sqlite3_errmsg(db).decode())

    @contextmanager
    def get_db(db_file):
        db = c_void_p()
        run(libsqlite3.sqlite3_open_v2, db_file.encode(), byref(db), SQLITE_OPEN_READWRITE, None)
        try:
            yield db
        finally:
            run_with_db(db, libsqlite3.sqlite3_close, db)

    @contextmanager
    def get_pp_stmt(db, sql):
        pp_stmt = c_void_p()
        run_with_db(db, libsqlite3.sqlite3_prepare_v3, db, sql.encode(), -1, 0, byref(pp_stmt), None)
        try:
            yield pp_stmt
        finally:
            run_with_db(db, libsqlite3.sqlite3_finalize, pp_stmt)

    with \
            get_db(db_file) as db, \
            get_pp_stmt(db, sql) as pp_stmt:

        for i, param in enumerate(params):
            run_with_db(db, bind[type(param)], pp_stmt, i + 1, param)

        row_constructor = namedtuple('Row', (
            libsqlite3.sqlite3_column_name(pp_stmt, i).decode()
            for i in range(0, libsqlite3.sqlite3_column_count(pp_stmt))
        ))

        while True:
            res = libsqlite3.sqlite3_step(pp_stmt)
            if res == SQLITE_DONE:
                break
            if res != SQLITE_ROW:
                raise Exception(libsqlite3.sqlite3_errstr(res).decode())

            yield row_constructor(*(
                extract[libsqlite3.sqlite3_column_type(pp_stmt, i)](pp_stmt, i)
                for i in range(0, len(row_constructor._fields))
            ))

which can be used as, for example:可以用作,例如:

for row in query('my.db', 'SELECT * FROM my_table WHERE a = ?;', ('b',)):
    print(row)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM