Initial commit
This commit is contained in:
commit
54b6bf1467
154
.gitignore
vendored
Normal file
154
.gitignore
vendored
Normal file
@ -0,0 +1,154 @@
|
||||
# ---> Python
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py,cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
cover/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
.pybuilder/
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
# For a library or package, you might want to ignore these files since the code is
|
||||
# intended to run in multiple environments; otherwise, check them in:
|
||||
# .python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
#Pipfile.lock
|
||||
|
||||
# poetry
|
||||
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||
# commonly ignored for libraries.
|
||||
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||
#poetry.lock
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
# pytype static type analyzer
|
||||
.pytype/
|
||||
|
||||
# Cython debug symbols
|
||||
cython_debug/
|
||||
|
||||
# PyCharm
|
||||
# JetBrains specific template is maintainted in a separate JetBrains.gitignore that can
|
||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
||||
|
3
README.md
Normal file
3
README.md
Normal file
@ -0,0 +1,3 @@
|
||||
# sqlite3tricks-py
|
||||
|
||||
Teach Python's SQLite3 library new tricks using ctypes.
|
5
__init__.py
Normal file
5
__init__.py
Normal file
@ -0,0 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from direct import connect, execute
|
||||
from vtab import VTableIterable, vtab_iterable
|
||||
from async_function import create_function
|
29
async_function.py
Normal file
29
async_function.py
Normal file
@ -0,0 +1,29 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import sys
|
||||
import asyncio
|
||||
|
||||
def get_event_loop():
|
||||
if get_event_loop.loop:
|
||||
return get_event_loop.loop
|
||||
try:
|
||||
loop = asyncio.get_running_loop()
|
||||
except RuntimeError:
|
||||
if sys.platform != 'win32':
|
||||
loop = asyncio.new_event_loop()
|
||||
else:
|
||||
loop = asyncio.ProactorEventLoop()
|
||||
get_event_loop.loop = loop
|
||||
return loop
|
||||
get_event_loop.loop = None
|
||||
|
||||
def create_function(conn, name, num_params, func, deterministic=False):
|
||||
original_create_function = conn._create_function if hasattr(conn, '_create_function') else conn.create_function
|
||||
if conn.create_function == create_function:
|
||||
fn = conn._create_function
|
||||
if asyncio.iscoroutinefunction(func):
|
||||
return original_create_function(name, num_params, (lambda *args: get_event_loop().run_until_complete(func(*args))), deterministic=deterministic)
|
||||
else:
|
||||
return original_create_function(name, num_params, func, deterministic=deterministic)
|
||||
|
||||
__all__ = ['create_function']
|
369
direct.py
Normal file
369
direct.py
Normal file
@ -0,0 +1,369 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import sys
|
||||
import sqlite3
|
||||
import ctypes
|
||||
import contextlib
|
||||
from collections import namedtuple
|
||||
|
||||
maxsize32 = 2**31 - 1
|
||||
|
||||
# Flags for file open operations
|
||||
# https://www.sqlite.org/c3ref/c_open_autoproxy.html
|
||||
SQLITE_OPEN_READWRITE = 0x00000002
|
||||
|
||||
# Constants defining special destructor behavior
|
||||
# https://www.sqlite.org/c3ref/c_static.html
|
||||
SQLITE_TRANSIENT = -1
|
||||
|
||||
# Virtual table constraint operator codes
|
||||
# https://www.sqlite.org/c3ref/c_index_constraint_eq.html
|
||||
SQLITE_INDEX_CONSTRAINT_EQ = 2
|
||||
|
||||
# Result codes
|
||||
# https://www.sqlite.org/rescode.html
|
||||
SQLITE_OK = 0
|
||||
SQLITE_ERROR = 1
|
||||
SQLITE_CONSTRAINT = 19
|
||||
SQLITE_ROW = 100
|
||||
SQLITE_DONE = 101
|
||||
|
||||
# Fundamental datatypes
|
||||
# https://www.sqlite.org/c3ref/c_blob.html
|
||||
SQLITE_INTEGER = 1
|
||||
SQLITE_FLOAT = 2
|
||||
SQLITE_TEXT = 3
|
||||
SQLITE_BLOB = 4
|
||||
SQLITE_NULL = 5
|
||||
|
||||
# Custom pointer types
|
||||
c_char_p_p = ctypes.POINTER(ctypes.c_char_p)
|
||||
c_int64_p = ctypes.POINTER(ctypes.c_int64)
|
||||
c_void_p_p = ctypes.POINTER(ctypes.c_void_p)
|
||||
|
||||
class c_PyObject(ctypes.Structure):
|
||||
"""All Python object types are extensions of this type."""
|
||||
# https://stackoverflow.com/questions/35438103/referencing-a-pointer-of-array-in-a-struct-with-ctypes
|
||||
_fields_ = [
|
||||
('ob_refcnt', ctypes.c_ssize_t),
|
||||
('ob_type', ctypes.c_void_p),
|
||||
]
|
||||
|
||||
class c_sqlite3_p(ctypes.c_void_p):
|
||||
def execute(self, sql, parameters=()):
|
||||
return execute(self, sql, parameters)
|
||||
|
||||
class c_pysqlite_connection(ctypes.Structure):
|
||||
"""Just enough struct to get at the pointer to the SQLite db
|
||||
Defined at https://github.com/python/cpython/blob/main/Modules/_sqlite/connection.h
|
||||
"""
|
||||
_fields_ = [
|
||||
('ob_base', c_PyObject),
|
||||
('db', c_sqlite3_p),
|
||||
]
|
||||
|
||||
class c_sqlite3_module(ctypes.Structure): pass # forward declaration
|
||||
c_sqlite3_module_p = ctypes.POINTER(c_sqlite3_module)
|
||||
|
||||
class c_sqlite3_vtab(ctypes.Structure):
|
||||
# https://www.sqlite.org/c3ref/vtab.html
|
||||
_fields_ = [
|
||||
('pModule', c_sqlite3_module_p),
|
||||
('nRef', ctypes.c_int),
|
||||
('zErrMsg', ctypes.c_char_p),
|
||||
# Custom fields
|
||||
('tableFactory', ctypes.py_object),
|
||||
]
|
||||
c_sqlite3_vtab_p = ctypes.POINTER(c_sqlite3_vtab)
|
||||
c_sqlite3_vtab_p_p = ctypes.POINTER(c_sqlite3_vtab_p)
|
||||
|
||||
class c_sqlite3_index_constraint(ctypes.Structure):
|
||||
_fields_ = [
|
||||
('iColumn', ctypes.c_int),
|
||||
('op', ctypes.c_ubyte),
|
||||
('usable', ctypes.c_ubyte),
|
||||
('iTermOffset', ctypes.c_int),
|
||||
]
|
||||
c_sqlite3_index_constraint_p = ctypes.POINTER(c_sqlite3_index_constraint)
|
||||
|
||||
class c_sqlite3_index_orderby(ctypes.Structure):
|
||||
_fields_ = [
|
||||
('iColumn', ctypes.c_int),
|
||||
('desc', ctypes.c_ubyte),
|
||||
]
|
||||
c_sqlite3_index_orderby_p = ctypes.POINTER(c_sqlite3_index_orderby)
|
||||
|
||||
class c_sqlite3_index_constraint_usage(ctypes.Structure):
|
||||
_fields_ = [
|
||||
('argvIndex', ctypes.c_int),
|
||||
('omit', ctypes.c_ubyte),
|
||||
]
|
||||
c_sqlite3_index_constraint_usage_p = ctypes.POINTER(c_sqlite3_index_constraint_usage)
|
||||
|
||||
class c_sqlite3_index_info(ctypes.Structure):
|
||||
# https://www.sqlite.org/c3ref/index_info.html
|
||||
_fields_ = [
|
||||
# Inputs
|
||||
('nConstraint', ctypes.c_int),
|
||||
('aConstraint', c_sqlite3_index_constraint_p),
|
||||
('nOrderBy', ctypes.c_int),
|
||||
('aOrderBy', c_sqlite3_index_orderby_p),
|
||||
# Outputs
|
||||
('aConstraintUsage', c_sqlite3_index_constraint_usage_p),
|
||||
('idxNum', ctypes.c_int),
|
||||
('idxStr', ctypes.c_char_p),
|
||||
('needToFreeIdxStr', ctypes.c_int),
|
||||
('orderByConsumed', ctypes.c_int),
|
||||
('estimatedCost', ctypes.c_double),
|
||||
# Fields below are only available in SQLite 3.8.2 and later
|
||||
('estimatedRows', ctypes.c_int64),
|
||||
# Fields below are only available in SQLite 3.9.0 and later
|
||||
('idxFlags', ctypes.c_int),
|
||||
# Fields below are only available in SQLite 3.10.0 and later
|
||||
('colUsed', ctypes.c_uint64),
|
||||
]
|
||||
c_sqlite3_index_info_p = ctypes.POINTER(c_sqlite3_index_info)
|
||||
|
||||
class c_sqlite3_vtab_cursor(ctypes.Structure):
|
||||
# https://www.sqlite.org/c3ref/vtab_cursor.html
|
||||
_fields_ = [
|
||||
('pVtab', c_sqlite3_vtab_p),
|
||||
# Custom fields
|
||||
('tableFactory', ctypes.py_object),
|
||||
('tableIterator', ctypes.py_object),
|
||||
('rowData', ctypes.py_object),
|
||||
('rowIdx', ctypes.c_int64),
|
||||
]
|
||||
c_sqlite3_vtab_cursor_p = ctypes.POINTER(c_sqlite3_vtab_cursor)
|
||||
c_sqlite3_vtab_cursor_p_p = ctypes.POINTER(c_sqlite3_vtab_cursor_p)
|
||||
|
||||
class c_sqlite3_context(ctypes.Structure):
|
||||
pass
|
||||
c_sqlite3_context_p = ctypes.POINTER(c_sqlite3_context)
|
||||
|
||||
class c_sqlite3_value(ctypes.Structure):
|
||||
pass
|
||||
c_sqlite3_value_p = ctypes.POINTER(c_sqlite3_value)
|
||||
c_sqlite3_value_p_p = ctypes.POINTER(c_sqlite3_value_p)
|
||||
|
||||
# https://www.sqlite.org/c3ref/module.html
|
||||
c_sqlite3_module._fields_ = [
|
||||
('iVersion', ctypes.c_int),
|
||||
# int (*xCreate)(sqlite3*, void *pAux, int argc, const char *const*argv, sqlite3_vtab **ppVTab, char**);
|
||||
('xCreate', ctypes.CFUNCTYPE(ctypes.c_int, c_sqlite3_p, ctypes.c_void_p, ctypes.c_int, c_char_p_p, c_sqlite3_vtab_p_p, c_char_p_p)),
|
||||
# int (*xConnect)(sqlite3*, void *pAux, int argc, const char *const*argv, sqlite3_vtab **ppVTab, char**);
|
||||
('xConnect', ctypes.CFUNCTYPE(ctypes.c_int, c_sqlite3_p, ctypes.c_void_p, ctypes.c_int, c_char_p_p, c_sqlite3_vtab_p_p, c_char_p_p)),
|
||||
# int (*xBestIndex)(sqlite3_vtab *pVTab, sqlite3_index_info*);
|
||||
('xBestIndex', ctypes.CFUNCTYPE(ctypes.c_int, c_sqlite3_vtab_p, c_sqlite3_index_info_p)),
|
||||
# int (*xDisconnect)(sqlite3_vtab *pVTab);
|
||||
('xDisconnect', ctypes.CFUNCTYPE(ctypes.c_int, c_sqlite3_vtab_p)),
|
||||
# int (*xDestroy)(sqlite3_vtab *pVTab);
|
||||
('xDestroy', ctypes.CFUNCTYPE(ctypes.c_int, c_sqlite3_vtab_p)),
|
||||
# int (*xOpen)(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor);
|
||||
('xOpen', ctypes.CFUNCTYPE(ctypes.c_int, c_sqlite3_vtab_p, c_sqlite3_vtab_cursor_p_p)),
|
||||
# int (*xClose)(sqlite3_vtab_cursor*);
|
||||
('xClose', ctypes.CFUNCTYPE(ctypes.c_int, c_sqlite3_vtab_cursor_p)),
|
||||
# int (*xFilter)(sqlite3_vtab_cursor*, int idxNum, const char *idxStr, int argc, sqlite3_value **argv);
|
||||
('xFilter', ctypes.CFUNCTYPE(ctypes.c_int, c_sqlite3_vtab_cursor_p, ctypes.c_int, ctypes.c_char_p, ctypes.c_int, c_sqlite3_value_p_p)),
|
||||
# int (*xNext)(sqlite3_vtab_cursor*);
|
||||
('xNext', ctypes.CFUNCTYPE(ctypes.c_int, c_sqlite3_vtab_cursor_p)),
|
||||
# int (*xEof)(sqlite3_vtab_cursor*);
|
||||
('xEof', ctypes.CFUNCTYPE(ctypes.c_int, c_sqlite3_vtab_cursor_p)),
|
||||
# int (*xColumn)(sqlite3_vtab_cursor*, sqlite3_context*, int);
|
||||
('xColumn', ctypes.CFUNCTYPE(ctypes.c_int, c_sqlite3_vtab_cursor_p, c_sqlite3_context_p, ctypes.c_int)),
|
||||
# int (*xRowid)(sqlite3_vtab_cursor*, sqlite3_int64 *pRowid);
|
||||
('xRowid', ctypes.CFUNCTYPE(ctypes.c_int, c_sqlite3_vtab_cursor_p, c_int64_p)),
|
||||
# int (*xUpdate)(sqlite3_vtab *, int, sqlite3_value **, sqlite3_int64 *);
|
||||
('xUpdate', ctypes.CFUNCTYPE(ctypes.c_int, c_sqlite3_vtab_p, c_sqlite3_value_p_p, c_int64_p)),
|
||||
# int (*xBegin)(sqlite3_vtab *pVTab);
|
||||
('xBegin', ctypes.CFUNCTYPE(ctypes.c_int, c_sqlite3_vtab_p)),
|
||||
# int (*xSync)(sqlite3_vtab *pVTab);
|
||||
('xSync', ctypes.CFUNCTYPE(ctypes.c_int, c_sqlite3_vtab_p)),
|
||||
# int (*xCommit)(sqlite3_vtab *pVTab);
|
||||
('xCommit', ctypes.CFUNCTYPE(ctypes.c_int, c_sqlite3_vtab_p)),
|
||||
# int (*xRollback)(sqlite3_vtab *pVTab);
|
||||
('xRollback', ctypes.CFUNCTYPE(ctypes.c_int, c_sqlite3_vtab_p)),
|
||||
# int (*xFindFunction)(sqlite3_vtab *pVtab, int nArg, const char *zName, void (**pxFunc)(sqlite3_context*,int,sqlite3_value**), void **ppArg);
|
||||
('xFindFunction', ctypes.CFUNCTYPE(ctypes.c_int, c_sqlite3_vtab_p, ctypes.c_int, ctypes.c_char_p, ctypes.CFUNCTYPE(c_void_p_p, c_sqlite3_context_p, ctypes.c_int, c_sqlite3_value_p_p), c_void_p_p)),
|
||||
# int (*xRename)(sqlite3_vtab *pVtab, const char *zNew);
|
||||
('xRename', ctypes.CFUNCTYPE(ctypes.c_int, c_sqlite3_vtab_p, ctypes.c_char_p)),
|
||||
# The methods above are in version 1 of the sqlite_module object. Those below are for version 2 and greater.
|
||||
# int (*xSavepoint)(sqlite3_vtab *pVTab, int);
|
||||
('xSavepoint', ctypes.CFUNCTYPE(ctypes.c_int, c_sqlite3_vtab_p, ctypes.c_int)),
|
||||
# int (*xRelease)(sqlite3_vtab *pVTab, int);
|
||||
('xRelease', ctypes.CFUNCTYPE(ctypes.c_int, c_sqlite3_vtab_p, ctypes.c_int)),
|
||||
# int (*xRollbackTo)(sqlite3_vtab *pVTab, int);
|
||||
('xRollbackTo', ctypes.CFUNCTYPE(ctypes.c_int, c_sqlite3_vtab_p, ctypes.c_int)),
|
||||
# The methods above are in versions 1 and 2 of the sqlite_module object. Those below are for version 3 and greater.
|
||||
# int (*xShadowName)(const char*);
|
||||
('xShadowName', ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_char_p)),
|
||||
]
|
||||
c_sqlite3_module._types_ = namedtuple('c_sqlite3_module', (k for k, v in c_sqlite3_module._fields_))(*(v for k, v in c_sqlite3_module._fields_))
|
||||
|
||||
# Reference counting
|
||||
import copy
|
||||
pythonapi = copy.deepcopy(ctypes.pythonapi)
|
||||
Py_IncRef = pythonapi.Py_IncRef
|
||||
Py_DecRef = pythonapi.Py_DecRef
|
||||
Py_IncRef.argtypes = [ctypes.py_object]
|
||||
Py_DecRef.argtypes = [ctypes.py_object]
|
||||
|
||||
try:
|
||||
import ctypes.util
|
||||
libsqlite3 = ctypes.cdll.LoadLibrary(ctypes.util.find_library('sqlite3'))
|
||||
except TypeError:
|
||||
import os
|
||||
libsqlite3 = ctypes.cdll.LoadLibrary(os.path.join(os.path.dirname(sys.executable), 'DLLs', 'sqlite3'))
|
||||
|
||||
# https://news.ycombinator.com/item?id=22030336
|
||||
#sqlite = ctypes.CDLL(ctypes.util.find_library('sqlite3'))
|
||||
|
||||
# bind value to SQLite statement
|
||||
libsqlite3.sqlite3_bind_int64.argtypes = (ctypes.c_void_p, ctypes.c_int, ctypes.c_int64)
|
||||
libsqlite3.sqlite3_bind_double.argtypes = (ctypes.c_void_p, ctypes.c_int, ctypes.c_double)
|
||||
map_sqlite3_bind = {
|
||||
type(0): libsqlite3.sqlite3_bind_int64,
|
||||
type(0.0): libsqlite3.sqlite3_bind_double,
|
||||
type(''): lambda stmt, i, value: libsqlite3.sqlite3_bind_text(stmt, i, value.encode('utf-8'), len(value.encode('utf-8')), SQLITE_TRANSIENT),
|
||||
type(b''): lambda stmt, i, value: libsqlite3.sqlite3_bind_blob(stmt, i, value, len(value), SQLITE_TRANSIENT),
|
||||
type(None): lambda stmt, i, _: libsqlite3.sqlite3_bind_null(stmt, i),
|
||||
}
|
||||
if sys.maxsize <= maxsize32:
|
||||
map_sqlite3_bind[type(0)] = lambda stmt, i, value: libsqlite3.sqlite3_bind_int(stmt, i, value) if value <= maxsize32 else libsqlite3.sqlite3_bind_int64(stmt, i, value)
|
||||
|
||||
# extract SQLite column value
|
||||
libsqlite3.sqlite3_column_name.restype = ctypes.c_char_p
|
||||
libsqlite3.sqlite3_column_type.restype = ctypes.c_int
|
||||
libsqlite3.sqlite3_column_int64.restype = ctypes.c_int64
|
||||
libsqlite3.sqlite3_column_double.restype = ctypes.c_double
|
||||
libsqlite3.sqlite3_column_blob.restype = ctypes.c_void_p
|
||||
libsqlite3.sqlite3_column_bytes.restype = ctypes.c_int64
|
||||
map_sqlite3_column = {
|
||||
SQLITE_INTEGER: libsqlite3.sqlite3_column_int64,
|
||||
SQLITE_FLOAT: libsqlite3.sqlite3_column_double,
|
||||
SQLITE_TEXT: lambda stmt, i: ctypes.string_at(
|
||||
libsqlite3.sqlite3_column_blob(stmt, i),
|
||||
libsqlite3.sqlite3_column_bytes(stmt, i),
|
||||
).decode(),
|
||||
SQLITE_BLOB: lambda stmt, i: ctypes.string_at(
|
||||
libsqlite3.sqlite3_column_blob(stmt, i),
|
||||
libsqlite3.sqlite3_column_bytes(stmt, i),
|
||||
),
|
||||
SQLITE_NULL: lambda stmt, i: None,
|
||||
}
|
||||
|
||||
# set SQLite result
|
||||
libsqlite3.sqlite3_result_int64.argtypes = (c_sqlite3_context_p, ctypes.c_int64)
|
||||
libsqlite3.sqlite3_result_double.argtypes = (c_sqlite3_context_p, ctypes.c_double)
|
||||
libsqlite3.sqlite3_result_text64.argtypes = (c_sqlite3_context_p, ctypes.c_char_p, ctypes.c_uint64, ctypes.c_void_p, ctypes.c_ubyte)
|
||||
libsqlite3.sqlite3_result_blob64.argtypes = (c_sqlite3_context_p, ctypes.c_void_p, ctypes.c_uint64, ctypes.c_void_p)
|
||||
libsqlite3.sqlite3_result_null.argtypes = (c_sqlite3_context_p,)
|
||||
map_sqlite3_result = {
|
||||
type(0): libsqlite3.sqlite3_result_int64,
|
||||
type(0.0): libsqlite3.sqlite3_result_double,
|
||||
type(''): lambda ctx, value: libsqlite3.sqlite3_result_text64(ctx, s := value.encode('utf-8'), len(s), SQLITE_TRANSIENT, SQLITE_UTF8),
|
||||
type(b''): lambda ctx, value: libsqlite3.sqlite3_result_blob64(ctx, value, len(value), -1),
|
||||
type(None): lambda ctx, _: libsqlite3.sqlite3_result_null(ctx),
|
||||
}
|
||||
if sys.maxsize <= maxsize32:
|
||||
map_sqlite3_result[type(0)] = lambda ctx, value: libsqlite3.sqlite3_result_int(ctx, value) if value <= maxsize32 else libsqlite3.sqlite3_result_int64(ctx, value)
|
||||
|
||||
# extract SQLite value
|
||||
libsqlite3.sqlite3_value_type.restype = ctypes.c_int
|
||||
libsqlite3.sqlite3_value_type.argtypes = (c_sqlite3_value_p,)
|
||||
libsqlite3.sqlite3_value_int64.restype = ctypes.c_int64
|
||||
libsqlite3.sqlite3_value_int64.argtypes = (c_sqlite3_value_p,)
|
||||
libsqlite3.sqlite3_value_double.restype = ctypes.c_double
|
||||
libsqlite3.sqlite3_value_double.argtypes = (c_sqlite3_value_p,)
|
||||
libsqlite3.sqlite3_value_blob.restype = ctypes.c_void_p
|
||||
libsqlite3.sqlite3_value_blob.argtypes = (c_sqlite3_value_p,)
|
||||
libsqlite3.sqlite3_value_bytes.restype = ctypes.c_int64
|
||||
libsqlite3.sqlite3_value_bytes.argtypes = (c_sqlite3_value_p,)
|
||||
map_sqlite3_value = {
|
||||
SQLITE_INTEGER: libsqlite3.sqlite3_value_int64,
|
||||
SQLITE_FLOAT: libsqlite3.sqlite3_value_double,
|
||||
SQLITE_TEXT: lambda x: ctypes.string_at(
|
||||
libsqlite3.sqlite3_value_blob(x),
|
||||
libsqlite3.sqlite3_value_bytes(x),
|
||||
).decode(),
|
||||
SQLITE_BLOB: lambda x: ctypes.string_at(
|
||||
libsqlite3.sqlite3_value_blob(x),
|
||||
libsqlite3.sqlite3_value_bytes(x),
|
||||
),
|
||||
SQLITE_NULL: lambda x: None,
|
||||
}
|
||||
|
||||
# Query execution
|
||||
|
||||
# https://gist.github.com/michalc/a3147997e21665896836e0f4157975cb
|
||||
libsqlite3.sqlite3_errstr.restype = ctypes.c_char_p
|
||||
libsqlite3.sqlite3_errmsg.restype = ctypes.c_char_p
|
||||
libsqlite3.sqlite3_declare_vtab.restype = ctypes.c_int
|
||||
libsqlite3.sqlite3_malloc.restype = ctypes.c_void_p
|
||||
libsqlite3.sqlite3_create_module.restype = ctypes.c_int
|
||||
|
||||
def connect(database, allownew=True, throw=True):
|
||||
"""Retrieves raw connection or opens new connection to SQLite database."""
|
||||
res = None
|
||||
if isinstance(database, c_sqlite3_p):
|
||||
res = database
|
||||
elif isinstance(database, sqlite3.Connection):
|
||||
res = c_pysqlite_connection.from_address(id(database)).db
|
||||
elif isinstance(database, str) and allownew:
|
||||
pDB = c_sqlite3_p()
|
||||
callAPI(libsqlite3.sqlite3_open_v2, database.encode('utf-8'), ctypes.byref(pDB), SQLITE_OPEN_READWRITE, None)
|
||||
res = pDB
|
||||
if res:
|
||||
return res
|
||||
elif throw:
|
||||
raise IOError('Invalid database')
|
||||
|
||||
def callAPI(func, *args, pDB=None):
|
||||
"""Calls SQLite3 API with exceptions."""
|
||||
res = func(*args)
|
||||
if res != 0:
|
||||
if pDB is None:
|
||||
raise sqlite3.OperationalError(libsqlite3.sqlite3_errstr(res).decode())
|
||||
else:
|
||||
raise sqlite3.OperationalError(libsqlite3.sqlite3_errmsg(pDB).decode())
|
||||
|
||||
@contextlib.contextmanager
|
||||
def connection(database, allownew=True):
|
||||
"""Manages connection to SQLite database."""
|
||||
pDB = connect(database, allownew=allownew)
|
||||
try:
|
||||
yield pDB
|
||||
finally:
|
||||
if isinstance(database, sqlite3.Connection):
|
||||
database.close()
|
||||
else:
|
||||
callAPI(libsqlite3.sqlite3_close, pDB, pDB=pDB)
|
||||
|
||||
@contextlib.contextmanager
|
||||
def prepared_statement(pDB, sql):
|
||||
stmt = ctypes.c_void_p()
|
||||
callAPI(libsqlite3.sqlite3_prepare_v3, pDB, sql.encode('utf-8'), -1, 0, ctypes.byref(stmt), None, pDB=pDB)
|
||||
try:
|
||||
yield stmt
|
||||
finally:
|
||||
callAPI(libsqlite3.sqlite3_finalize, stmt, pDB=pDB)
|
||||
|
||||
def execute(pDB, sql, parameters=()):
|
||||
with prepared_statement(pDB, sql) as stmt:
|
||||
for i, param in enumerate(parameters, start=1):
|
||||
callAPI(map_sqlite3_bind[type(param)], stmt, i, param, pDB=pDB)
|
||||
row_factory = namedtuple('Row', (
|
||||
libsqlite3.sqlite3_column_name(stmt, i).decode() for i in range(libsqlite3.sqlite3_column_count(stmt))
|
||||
))
|
||||
while True:
|
||||
res = libsqlite3.sqlite3_step(stmt)
|
||||
if res == SQLITE_ROW:
|
||||
yield row_factory(*(
|
||||
map_sqlite3_column[libsqlite3.sqlite3_column_type(stmt, i)](stmt, i) for i in range(0, len(row_factory._fields))
|
||||
))
|
||||
elif res == SQLITE_DONE:
|
||||
break
|
||||
else:
|
||||
raise sqlite3.OperationalError(libsqlite3.sqlite3_errstr(res).decode())
|
||||
|
||||
__all__ = ['connect', 'execute']
|
148
test.py
Normal file
148
test.py
Normal file
@ -0,0 +1,148 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from __init__ import *
|
||||
|
||||
class RangeIterable(VTableIterable):
|
||||
def __init__(self, start=0, stop=float('inf'), step=1):
|
||||
self.start = start
|
||||
self.stop = stop
|
||||
self.step = step
|
||||
self.curr = self.start
|
||||
def __iter__(self):
|
||||
print(self.__class__.__name__, self)
|
||||
return self
|
||||
def __next__(self):
|
||||
if self.curr <= self.stop:
|
||||
res = self.curr
|
||||
self.curr += self.step
|
||||
return (res,)
|
||||
else:
|
||||
raise StopIteration
|
||||
|
||||
class RangeIterableGenerator(VTableIterable):
|
||||
def __init__(self, start=0, stop=float('inf'), step=1):
|
||||
self.start = start
|
||||
self.stop = stop
|
||||
self.step = step
|
||||
def __iter__(self):
|
||||
print(self.__class__.__name__, self)
|
||||
curr = self.start
|
||||
while curr <= self.stop:
|
||||
yield (curr,)
|
||||
curr += self.step
|
||||
|
||||
class RangeIterableMember(object):
|
||||
def __init__(self, extra):
|
||||
self.extra = extra
|
||||
@vtab_iterable(name='RangeIterableMember')
|
||||
def generator(self, start=0, stop=float('inf'), step=1):
|
||||
print(self.__class__.__name__, self, self.generator, self.extra)
|
||||
for curr in range(start, stop, step):
|
||||
yield (curr,)
|
||||
|
||||
class RangeIterableFancy(VTableIterable):
|
||||
_vtname_ = 'RangeIterableRenamed'
|
||||
_vtparams_ = ('start', 'stop', 'step', 'extra')
|
||||
_vtcolumns_ = ('once', 'twice', 'thrice')
|
||||
def __init__(self, start=0, stop=float('inf'), step=1, **kw):
|
||||
self.start = start
|
||||
self.stop = stop
|
||||
self.step = step
|
||||
self.kw = kw
|
||||
def __iter__(self):
|
||||
print(self.__class__.__name__, self, self.kw)
|
||||
curr = self.start
|
||||
while curr <= self.stop:
|
||||
yield (curr, curr*2, curr*3)
|
||||
curr += self.step
|
||||
|
||||
@vtab_iterable
|
||||
def RangeGenerator(start=0, stop=float('inf'), step=1):
|
||||
print(RangeGenerator.__name__, RangeGenerator)
|
||||
for curr in range(start, stop, step):
|
||||
yield (curr,)
|
||||
|
||||
@vtab_iterable(name='RangeGeneratorRenamed', params=('start', 'stop', 'step', 'extra'), columns=('once', 'twice', 'thrice'))
|
||||
def RangeGeneratorFancy(start=0, stop=float('inf'), step=1, **kw):
|
||||
print(RangeGeneratorFancy.__name__, RangeGeneratorFancy, kw)
|
||||
for curr in range(start, stop, step):
|
||||
yield (curr, curr*2, curr*3)
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sqlite3
|
||||
|
||||
conn = sqlite3.connect(':memory:')
|
||||
pDB = connect(conn)
|
||||
|
||||
conn.execute('CREATE TABLE test (a INTEGER, b INTEGER)')
|
||||
conn.execute('INSERT INTO test (a, b) VALUES (?, ?)', (1, 2))
|
||||
conn.execute('INSERT INTO test (a, b) VALUES (?, ?)', (3, 4))
|
||||
conn.execute('INSERT INTO test (a, b) VALUES (?, ?)', (5, 6))
|
||||
conn.execute('INSERT INTO test (a, b) VALUES (?, ?)', (7, 8))
|
||||
|
||||
for row in conn.execute('SELECT * FROM test'):
|
||||
print('sqlite3:', row)
|
||||
for row in pDB.execute('SELECT * FROM test'):
|
||||
print('tricks:', row)
|
||||
print()
|
||||
|
||||
for row in conn.execute('SELECT * FROM test WHERE a = ?', (1,)):
|
||||
print('sqlite3:', row)
|
||||
for row in pDB.execute('SELECT * FROM test WHERE a = ?', (1,)):
|
||||
print('tricks:', row)
|
||||
print()
|
||||
|
||||
RangeIterable.register(conn)
|
||||
for row in conn.execute('SELECT * FROM RangeIterable(10, 16, 2)'):
|
||||
print('sqlite3:', row)
|
||||
for row in pDB.execute('SELECT * FROM RangeIterable(10, 16, 2)'):
|
||||
print('tricks:', row)
|
||||
print()
|
||||
|
||||
RangeIterableGenerator.register(conn)
|
||||
for row in conn.execute('SELECT * FROM RangeIterableGenerator(20, 26, 2)'):
|
||||
print('sqlite3:', row)
|
||||
for row in pDB.execute('SELECT * FROM RangeIterableGenerator(20, 26, 2)'):
|
||||
print('tricks:', row)
|
||||
print()
|
||||
|
||||
obj = RangeIterableMember('hello')
|
||||
obj.generator.register(conn)
|
||||
for row in conn.execute('SELECT * FROM RangeIterableMember(30, 36, 2)'):
|
||||
print('sqlite3:', row)
|
||||
for row in pDB.execute('SELECT * FROM RangeIterableMember(30, 36, 2)'):
|
||||
print('tricks:', row)
|
||||
print()
|
||||
|
||||
RangeIterableFancy.register(conn)
|
||||
for row in conn.execute('SELECT * FROM RangeIterableRenamed(40, 46, 2, datetime())'):
|
||||
print('sqlite3:', row)
|
||||
for row in pDB.execute('SELECT * FROM RangeIterableRenamed(40, 46, 2, datetime())'):
|
||||
print('tricks:', row)
|
||||
print()
|
||||
|
||||
RangeGenerator.register(conn)
|
||||
for row in conn.execute('SELECT * FROM RangeGenerator(50, 56, 2)'):
|
||||
print('sqlite3:', row)
|
||||
for row in pDB.execute('SELECT * FROM RangeGenerator(50, 56, 2)'):
|
||||
print('tricks:', row)
|
||||
print()
|
||||
|
||||
RangeGeneratorFancy.register(conn)
|
||||
for row in conn.execute('SELECT * FROM RangeGeneratorRenamed(60, 66, 2, datetime())'):
|
||||
print('sqlite3:', row)
|
||||
for row in pDB.execute('SELECT * FROM RangeGeneratorRenamed(60, 66, 2, datetime())'):
|
||||
print('tricks:', row)
|
||||
print()
|
||||
|
||||
import hashlib
|
||||
|
||||
async def md5sum(t):
|
||||
return hashlib.md5(str(t).encode('utf-8')).hexdigest()
|
||||
|
||||
create_function(conn, 'md5', 1, md5sum)
|
||||
|
||||
for row in conn.execute('SELECT md5(value) FROM RangeIterable(10, 16, 2)'):
|
||||
print(row)
|
||||
for row in pDB.execute('SELECT md5(value) AS md5_value FROM RangeIterable(10, 16, 2)'):
|
||||
print(row)
|
260
vtab.py
Normal file
260
vtab.py
Normal file
@ -0,0 +1,260 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import sys
|
||||
import sqlite3
|
||||
import ctypes
|
||||
import functools
|
||||
import inspect
|
||||
import traceback
|
||||
|
||||
import direct
|
||||
from direct import libsqlite3, Py_IncRef, Py_DecRef, c_sqlite3_module, c_sqlite3_value_p_p, map_sqlite3_value, map_sqlite3_result, SQLITE_OK, SQLITE_ERROR, SQLITE_CONSTRAINT, SQLITE_INDEX_CONSTRAINT_EQ
|
||||
|
||||
USE_SQLITE_CONSTRAINT = tuple(map(int, sqlite3.sqlite_version.split('.'))) >= (3, 26, 0)
|
||||
|
||||
@direct.c_sqlite3_module._types_.xConnect
|
||||
def vtab_xConnect(pDB, pAux, argc, argv, ppVTab, pzErr): # sqlite3 *db, void *pAux, int argc, const char *const*argv, sqlite3_vtab **ppVTab, char **pzErr
|
||||
"""
|
||||
Invoked whenever a database connection attaches to or reparses a schema.
|
||||
A virtual table is eponymous if its xCreate method is the exact same function as the xConnect method, or if the xCreate method is NULL.
|
||||
"""
|
||||
tableFactory = ctypes.cast(pAux, ctypes.py_object).value
|
||||
res = libsqlite3.sqlite3_declare_vtab(pDB, f'CREATE TABLE x({vtab_columns_declaration(tableFactory)})'.encode('utf-8'))
|
||||
if res == SQLITE_OK:
|
||||
szVTab = ctypes.sizeof(direct.c_sqlite3_vtab)
|
||||
pVTab = ctypes.cast(libsqlite3.sqlite3_malloc(szVTab), direct.c_sqlite3_vtab_p)
|
||||
ctypes.memset(pVTab, 0, szVTab)
|
||||
pVTab.contents.tableFactory = ctypes.cast(pAux, ctypes.py_object)
|
||||
Py_IncRef(pVTab.contents.tableFactory)
|
||||
ppVTab[0] = pVTab
|
||||
return res
|
||||
|
||||
@direct.c_sqlite3_module._types_.xBestIndex
|
||||
def vtab_xBestIndex(pVTab, pIdxInfo): # sqlite3_vtab *pVTab, sqlite3_index_info *pIdxInfo
|
||||
"""Used to determine the best way to access the virtual table."""
|
||||
tableFactory = pVTab.contents.tableFactory
|
||||
vIdxInfo = pIdxInfo.contents
|
||||
nArg = 0
|
||||
columns = []
|
||||
nParams = len(tableFactory._vtparams_)
|
||||
nColumns = len(tableFactory._vtcolumns_)
|
||||
for i in range(vIdxInfo.nConstraint):
|
||||
vConstraint = vIdxInfo.aConstraint[i]
|
||||
if vConstraint.usable and vConstraint.op == SQLITE_INDEX_CONSTRAINT_EQ:
|
||||
columns.append(tableFactory._vtparams_[vConstraint.iColumn - nColumns])
|
||||
nArg += 1
|
||||
vIdxInfo.aConstraintUsage[i].argvIndex = nArg
|
||||
vIdxInfo.aConstraintUsage[i].omit = 1
|
||||
if nArg > 0 or nParams == 0:
|
||||
if nArg == nParams:
|
||||
# All parameters are present, this is ideal.
|
||||
vIdxInfo.estimatedCost = 1.0
|
||||
vIdxInfo.estimatedRows = 10
|
||||
else:
|
||||
# Penalize score based on number of missing params.
|
||||
vIdxInfo.estimatedCost = 1e13 * (nParams - nArg)
|
||||
vIdxInfo.estimatedRows = 10 ** (nParams - nArg)
|
||||
# Store a reference to the columns in the index info structure.
|
||||
joinedCols = ','.join(columns).encode('utf-8')
|
||||
idxStr = libsqlite3.sqlite3_malloc((len(joinedCols) + 1) * ctypes.sizeof(ctypes.c_char))
|
||||
idxStr = (ctypes.c_char*(len(joinedCols) + 1)).from_address(idxStr)
|
||||
ctypes.memmove(idxStr, joinedCols, len(joinedCols)) # memcpy
|
||||
idxStr[len(joinedCols)] = b'\x00'
|
||||
vIdxInfo.idxStr = ctypes.cast(idxStr, ctypes.c_char_p)
|
||||
vIdxInfo.needToFreeIdxStr = 0
|
||||
elif USE_SQLITE_CONSTRAINT:
|
||||
return SQLITE_CONSTRAINT
|
||||
else:
|
||||
vIdxInfo.estimatedCost = DBL_MAX
|
||||
vIdxInfo.estimatedRows = 100000
|
||||
return SQLITE_OK
|
||||
|
||||
@direct.c_sqlite3_module._types_.xDisconnect
|
||||
def vtab_xDisconnect(pVTab): # sqlite3_vtab *pVTab
|
||||
"""Releases a connection to a virtual table."""
|
||||
Py_DecRef(pVTab.contents.tableFactory)
|
||||
libsqlite3.sqlite3_free(pVTab)
|
||||
return SQLITE_OK
|
||||
|
||||
@direct.c_sqlite3_module._types_.xOpen
|
||||
def vtab_xOpen(pVTab, ppCursor): # sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor
|
||||
"""Creates a new cursor used for accessing (read and/or writing) a virtual table."""
|
||||
# tableFactory = pVTab.contents.tableFactory
|
||||
szCursor = ctypes.sizeof(direct.c_sqlite3_vtab_cursor)
|
||||
pCursor = ctypes.cast(libsqlite3.sqlite3_malloc(szCursor), direct.c_sqlite3_vtab_cursor_p)
|
||||
ctypes.memset(pCursor, 0, szCursor)
|
||||
vCursor = pCursor.contents
|
||||
vCursor.tableFactory = pVTab.contents.tableFactory
|
||||
Py_IncRef(vCursor.tableFactory)
|
||||
vCursor.rowData = None
|
||||
vCursor.rowIdx = 0
|
||||
ppCursor[0] = pCursor
|
||||
return SQLITE_OK
|
||||
|
||||
@direct.c_sqlite3_module._types_.xClose
|
||||
def vtab_xClose(pCursor): # sqlite3_vtab_cursor *pCursor
|
||||
"""Closes a cursor previously opened by xOpen."""
|
||||
Py_DecRef(pCursor.contents.tableFactory)
|
||||
libsqlite3.sqlite3_free(pCursor)
|
||||
return SQLITE_OK
|
||||
|
||||
def vtab_iterate(vCursor):
|
||||
if vCursor.rowData:
|
||||
Py_DecRef(vCursor.rowData)
|
||||
vCursor.rowData = None
|
||||
try:
|
||||
vCursor.rowData = next(vCursor.tableIterator)
|
||||
Py_IncRef(vCursor.rowData)
|
||||
except StopIteration:
|
||||
Py_DecRef(vCursor.tableIterator)
|
||||
vCursor.tableIterator = None
|
||||
except:
|
||||
traceback.print_exc()
|
||||
return SQLITE_ERROR
|
||||
else:
|
||||
vCursor.rowIdx += 1
|
||||
return SQLITE_OK
|
||||
|
||||
@direct.c_sqlite3_module._types_.xFilter
|
||||
def vtab_xFilter(pCursor, idxNum, idxStr, argc, argv): # sqlite3_vtab_cursor *pCursor, int idxNum, const char *idxStr, int argc, sqlite3_value **argv
|
||||
"""Begins a search of a virtual table."""
|
||||
vCursor = pCursor.contents
|
||||
tableFactory = vCursor.tableFactory
|
||||
idxStr = ctypes.cast(idxStr, ctypes.c_char_p).value # avoid null terminator
|
||||
if not idxStr or argc == 0 and len(tableFactory._vtparams_):
|
||||
return SQLITE_ERROR
|
||||
elif len(idxStr):
|
||||
params = idxStr.decode().split(',')
|
||||
else:
|
||||
params = []
|
||||
py_args = tuple(map_sqlite3_value[libsqlite3.sqlite3_value_type(arg := argv[i])](arg) for i in range(argc))
|
||||
query = dict(zip(params, py_args))
|
||||
try:
|
||||
tableIterator = iter(tableFactory(**query))
|
||||
except:
|
||||
traceback.print_exc()
|
||||
return SQLITE_ERROR
|
||||
vCursor.tableIterator = tableIterator
|
||||
Py_IncRef(vCursor.tableIterator)
|
||||
return vtab_iterate(vCursor)
|
||||
|
||||
@direct.c_sqlite3_module._types_.xNext
|
||||
def vtab_xNext(pCursor): # sqlite3_vtab_cursor *pCursor
|
||||
"""Advances a virtual table cursor to the next row of a result set initiated by xFilter."""
|
||||
return vtab_iterate(pCursor.contents)
|
||||
|
||||
@direct.c_sqlite3_module._types_.xEof
|
||||
def vtab_xEof(pCursor): # sqlite3_vtab_cursor *pCursor
|
||||
"""Returns false (zero) if the specified cursor currently points to a valid row of data, or true (non-zero) otherwise."""
|
||||
return 1 if pCursor.contents.tableIterator is None else 0
|
||||
|
||||
@direct.c_sqlite3_module._types_.xColumn
|
||||
def vtab_xColumn(pCursor, pContext, iCol): # sqlite3_vtab_cursor *pCursor, sqlite3_context *pContext, int iCol
|
||||
"""Invoked to retrieve the N-th column of the current row."""
|
||||
if iCol == -1:
|
||||
libsqlite3.sqlite3_result_int64(pContext, pCursor.contents.rowIdx)
|
||||
return SQLITE_OK
|
||||
if not pCursor.contents.rowData:
|
||||
libsqlite3.sqlite3_result_error(pContext, 'no row data'.encode('utf-8'), -1)
|
||||
return SQLITE_ERROR
|
||||
map_sqlite3_result[type(value := pCursor.contents.rowData[iCol])](pContext, value)
|
||||
return SQLITE_OK
|
||||
|
||||
@direct.c_sqlite3_module._types_.xRowid
|
||||
def vtab_xRowid(pCursor, pRowid): # sqlite3_vtab_cursor *pCursor, sqlite3_int64 *pRowid
|
||||
"""Fills *pRowid with the rowid of the current row."""
|
||||
pRowid[0] = pCursor.contents.rowIdx
|
||||
|
||||
def new_sqlite3_module_vtabfunc():
|
||||
module = direct.c_sqlite3_module()
|
||||
ctypes.memset(ctypes.byref(module), 0, ctypes.sizeof(direct.c_sqlite3_module))
|
||||
module.iVersion = 0
|
||||
#module.xCreate = direct.c_sqlite3_module._types_.xCreate(0)
|
||||
module.xConnect = vtab_xConnect
|
||||
module.xBestIndex = vtab_xBestIndex
|
||||
module.xDisconnect = vtab_xDisconnect
|
||||
#module.xDestroy = direct.c_sqlite3_module._types_.xDestroy(0)
|
||||
module.xOpen = vtab_xOpen
|
||||
module.xClose = vtab_xClose
|
||||
module.xFilter = vtab_xFilter
|
||||
module.xNext = vtab_xNext
|
||||
module.xEof = vtab_xEof
|
||||
module.xColumn = vtab_xColumn
|
||||
module.xRowid = vtab_xRowid
|
||||
#module.xUpdate = direct.c_sqlite3_module._types_.xUpdate(0)
|
||||
#module.xBegin = direct.c_sqlite3_module._types_.xBegin(0)
|
||||
#module.xSync = direct.c_sqlite3_module._types_.xSync(0)
|
||||
#module.xCommit = direct.c_sqlite3_module._types_.xCommit(0)
|
||||
#module.xRollback = direct.c_sqlite3_module._types_.xRollback(0)
|
||||
#module.xFindFunction = direct.c_sqlite3_module._types_.xFindFunction(0)
|
||||
#module.xRename = direct.c_sqlite3_module._types_.xRename(0)
|
||||
return module
|
||||
|
||||
def vtab_columns_declaration(factory):
|
||||
acc = []
|
||||
for column in factory._vtcolumns_:
|
||||
if isinstance(column, str):
|
||||
acc.append(column)
|
||||
elif isinstance(column, tuple) and len(column) != 2:
|
||||
acc.append('%s %s'%column)
|
||||
else:
|
||||
raise ValueError('Column must be either a string or a 2-tuple of name, type')
|
||||
for param in factory._vtparams_:
|
||||
acc.append('%s HIDDEN'%param)
|
||||
return ', '.join(acc)
|
||||
|
||||
class VTableIterable(object):
|
||||
@classmethod
|
||||
def register(cls, database, name=None):
|
||||
if not cls._vtparams_:
|
||||
cls._vtparams_ = tuple(k for k in inspect.signature(cls).parameters)
|
||||
if not cls._vtcolumns_:
|
||||
cls._vtcolumns_ = ('value',)
|
||||
return libsqlite3.sqlite3_create_module(direct.connect(database), (name or cls._vtname_ or cls.__name__).encode('utf-8'), ctypes.byref(cls._vtmod_), ctypes.py_object(cls))
|
||||
@classmethod
|
||||
def unregister(cls, database, name=None):
|
||||
return libsqlite3.sqlite3_create_module(direct.connect(database), (name or cls._vtname_ or cls.__name__).encode('utf-8'), None, None)
|
||||
def __iter__(self):
|
||||
raise NotImplementedError
|
||||
def __next__(self):
|
||||
raise NotImplementedError
|
||||
_vtmod_ = new_sqlite3_module_vtabfunc()
|
||||
_vtname_ = None
|
||||
_vtparams_ = None
|
||||
_vtcolumns_ = None
|
||||
|
||||
def vtab_wrap(dst, src, shift=0, name=None, params=None, columns=None):
|
||||
dst._vtname_ = name or getattr(src, '_vtname_', None) or src.__name__
|
||||
dst._vtparams_ = params or getattr(src, '_vtparams_', None) or tuple(inspect.signature(src).parameters)[shift:]
|
||||
dst._vtcolumns_ = columns or getattr(src, '_vtcolumns_', None) or ('value',)
|
||||
dst.register = lambda database, name=None: libsqlite3.sqlite3_create_module(direct.connect(database), (name or dst._vtname_).encode('utf-8'), ctypes.byref(VTableIterable._vtmod_), ctypes.py_object(dst))
|
||||
dst.unregister = getattr(src, 'unregister', None) or (lambda database, name=None: libsqlite3.sqlite3_create_module(direct.connect(database), (name or dst._vtname_).encode('utf-8'), None, None))
|
||||
dst.__doc__ = src.__doc__
|
||||
dst.__name__ = src.__name__
|
||||
return dst
|
||||
|
||||
class vtab_descriptor(object):
|
||||
def __init__(self, func, name=None, params=None, columns=None):
|
||||
self._vtfunc_ = func
|
||||
vtab_wrap(self, func, 1, name=name, params=params, columns=columns)
|
||||
def __call__(self, *args, **kwargs):
|
||||
return self._vtfunc_(*args, **kwargs)
|
||||
def __get__(self, obj, cls):
|
||||
return vtab_wrap(functools.partial(self._vtfunc_, obj), self)
|
||||
|
||||
def vtab_iterable(func=None, *, name=None, params=None, columns=None, member=False):
|
||||
def decorator(src):
|
||||
try:
|
||||
if member or inspect.getfullargspec(src)[0][0] in {'self', 'cls'}:
|
||||
return vtab_descriptor(src, name=name, params=params, columns=columns)
|
||||
except IndexError:
|
||||
pass
|
||||
def proxy(*args, **kw):
|
||||
return src(*args, **kw)
|
||||
return vtab_wrap(proxy, src, name=name, params=params, columns=columns)
|
||||
if func is None:
|
||||
return decorator
|
||||
else:
|
||||
return decorator(func)
|
||||
|
||||
__all__ = ['VTableIterable', 'vtab_iterable']
|
Loading…
Reference in New Issue
Block a user