From e0373c7dbfc1b58adfea64f85a27602ebaa851e2 Mon Sep 17 00:00:00 2001 From: Chris Arderne Date: Thu, 1 Aug 2024 17:48:45 +0100 Subject: [PATCH] vendor pysqlcipher (#136) * vendor pysqlcipher3 * working * openssl already installed * fix manifest * try * move sqlcipher * add openssl back * try * now * disable test * doh * a * clean --- .github/workflows/release.yml | 49 +- .gitignore | 1 - MANIFEST.in | 3 + pyproject.toml | 17 +- requirements-dev.lock | 45 +- setup.py | 105 + src/pysqlcipher3/README.md | 17 + src/pysqlcipher3/__init__.py | 0 src/pysqlcipher3/c/cache.c | 357 ++++ src/pysqlcipher3/c/cache.h | 73 + src/pysqlcipher3/c/connection.c | 1741 +++++++++++++++++ src/pysqlcipher3/c/connection.h | 132 ++ src/pysqlcipher3/c/cursor.c | 1109 +++++++++++ src/pysqlcipher3/c/cursor.h | 75 + src/pysqlcipher3/c/microprotocols.c | 146 ++ src/pysqlcipher3/c/microprotocols.h | 55 + src/pysqlcipher3/c/module.c | 483 +++++ src/pysqlcipher3/c/module.h | 56 + src/pysqlcipher3/c/prepare_protocol.c | 83 + src/pysqlcipher3/c/prepare_protocol.h | 48 + src/pysqlcipher3/c/row.c | 276 +++ src/pysqlcipher3/c/row.h | 39 + .../pysqlcipher3/c/sqlcipher}/README.md | 2 + .../pysqlcipher3/c/sqlcipher}/sqlite3.c | 0 .../pysqlcipher3/c/sqlcipher}/sqlite3.h | 0 src/pysqlcipher3/c/statement.c | 521 +++++ src/pysqlcipher3/c/statement.h | 59 + src/pysqlcipher3/c/util.c | 172 ++ src/pysqlcipher3/c/util.h | 42 + src/pysqlcipher3/dbapi2.py | 96 + 30 files changed, 5708 insertions(+), 94 deletions(-) create mode 100644 MANIFEST.in create mode 100644 setup.py create mode 100644 src/pysqlcipher3/README.md create mode 100644 src/pysqlcipher3/__init__.py create mode 100644 src/pysqlcipher3/c/cache.c create mode 100644 src/pysqlcipher3/c/cache.h create mode 100644 src/pysqlcipher3/c/connection.c create mode 100644 src/pysqlcipher3/c/connection.h create mode 100644 src/pysqlcipher3/c/cursor.c create mode 100644 src/pysqlcipher3/c/cursor.h create mode 100644 src/pysqlcipher3/c/microprotocols.c create mode 100644 src/pysqlcipher3/c/microprotocols.h create mode 100644 src/pysqlcipher3/c/module.c create mode 100644 src/pysqlcipher3/c/module.h create mode 100644 src/pysqlcipher3/c/prepare_protocol.c create mode 100644 src/pysqlcipher3/c/prepare_protocol.h create mode 100644 src/pysqlcipher3/c/row.c create mode 100644 src/pysqlcipher3/c/row.h rename {sqlcipher => src/pysqlcipher3/c/sqlcipher}/README.md (91%) rename {sqlcipher => src/pysqlcipher3/c/sqlcipher}/sqlite3.c (100%) rename {sqlcipher => src/pysqlcipher3/c/sqlcipher}/sqlite3.h (100%) create mode 100644 src/pysqlcipher3/c/statement.c create mode 100644 src/pysqlcipher3/c/statement.h create mode 100644 src/pysqlcipher3/c/util.c create mode 100644 src/pysqlcipher3/c/util.h create mode 100644 src/pysqlcipher3/dbapi2.py diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 958f026..2e6a650 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -18,55 +18,16 @@ jobs: steps: - uses: actions/checkout@v4 - - name: prep pysqlcipher3 - run: | - git clone --depth=1 --branch=master https://github.com/rigglemania/pysqlcipher3.git - cd pysqlcipher3 - mkdir amalgamation - cp ../sqlcipher/sqlite3.[ch] amalgamation - mkdir src/python3/sqlcipher - cp ../sqlcipher/sqlite3.[ch] src/python3/sqlcipher - pip install setuptools - - - name: build pysqlcipher3 linux - if: matrix.os == 'ubuntu-latest' - run: | - cd pysqlcipher3 - python setup.py build_amalgamation - python setup.py build - echo 'PLATFORM_TAG="manylinux2014_x86_64"' >> $GITHUB_ENV - - - name: build pysqlcipher3 macOS - if: matrix.os == 'macos-latest' - run: | - brew install openssl - export LDFLAGS="-L/opt/homebrew/opt/openssl@3/lib" - export CPPFLAGS="-I/opt/homebrew/opt/openssl@3/include" - cd pysqlcipher3 - python setup.py build_amalgamation - python setup.py build - echo 'PLATFORM_TAG="macosx_10_9_universal2"' >> $GITHUB_ENV - - - name: post-build pysqlcipher3 - run: | - mkdir src/pysqlcipher3 - for f in pysqlcipher3/build/lib.*/pysqlcipher3/*.{py,so}; do cp $f src/pysqlcipher3/; done - - name: build run: | pip install build + export LDFLAGS="-L/opt/homebrew/opt/openssl@3/lib" + export CPPFLAGS="-I/opt/homebrew/opt/openssl@3/include" python -m build cd dist - NEW_NAME=$(ls *.whl | sed "s/any.whl$/${{ env.PLATFORM_TAG }}.whl/") - mv *.whl $NEW_NAME echo "WHEEL_NAME=$(ls *.whl)" >> $GITHUB_ENV echo "SDIST_NAME=$(ls *.tar.gz)" >> $GITHUB_ENV - - name: test - run: | - pip install dist/*.whl pytest - pytest - - uses: actions/upload-artifact@v4 with: name: ${{ env.WHEEL_NAME }} @@ -79,6 +40,12 @@ jobs: name: ${{ env.SDIST_NAME }} path: "dist/*.tar.gz" + - name: test + run: | + pip install dist/*.whl pytest + pytest + + upload: needs: [build] if: startsWith(github.ref, 'refs/tags/') diff --git a/.gitignore b/.gitignore index 4fa9e00..cbc2507 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,4 @@ # Custom -src/pysqlcipher3 # OS generated files .DS_Store diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..7babf20 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,3 @@ +graft src + +global-exclude *.pyc diff --git a/pyproject.toml b/pyproject.toml index ce10cc0..7f8624c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,16 +41,14 @@ dependencies = [ managed = true generate-hashes = false dev-dependencies = [ - "build", "pyright", "pre-commit", "pytest", "pytest-cov", - "ruff ~= 0.2", - "setuptools-scm", - "twine", "types-Markdown", "types-emoji", + "setuptools-scm~=8.1.0", + "build~=1.2.1", ] [project.urls] @@ -61,14 +59,13 @@ repository = "https://github.com/carderne/signal-export" sigexport = "sigexport.main:cli" [build-system] -requires = ["pdm-backend"] -build-backend = "pdm.backend" +requires = ["setuptools>=64", "setuptools_scm>=8"] +build-backend = "setuptools.build_meta" -[tool.pdm.build] -includes = ["src/"] +[tool.setuptools] +package-dir = {"" = "src"} -[tool.pdm.version] -source = "scm" +[tool.setuptools_scm] [tool.ruff] target-version = "py39" diff --git a/requirements-dev.lock b/requirements-dev.lock index 6367557..332738c 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -12,13 +12,9 @@ -e file:. beautifulsoup4==4.12.3 # via signal-export -build==1.1.1 -certifi==2024.2.2 - # via requests +build==1.2.1 cfgv==3.4.0 # via pre-commit -charset-normalizer==3.3.2 - # via requests click==8.1.7 # via typer colorama==0.4.6 @@ -27,34 +23,20 @@ coverage==7.4.4 # via pytest-cov distlib==0.3.8 # via virtualenv -docutils==0.20.1 - # via readme-renderer emoji==2.10.1 # via signal-export filelock==3.13.1 # via virtualenv identify==2.5.35 # via pre-commit -idna==3.6 - # via requests -importlib-metadata==7.0.2 - # via twine iniconfig==2.0.0 # via pytest -jaraco-classes==3.3.1 - # via keyring -keyring==24.3.1 - # via twine markdown==3.6 # via signal-export markdown-it-py==3.0.0 # via rich mdurl==0.1.2 # via markdown-it-py -more-itertools==10.2.0 - # via jaraco-classes -nh3==0.2.15 - # via readme-renderer nodeenv==1.8.0 # via pre-commit # via pyright @@ -62,8 +44,6 @@ packaging==24.0 # via build # via pytest # via setuptools-scm -pkginfo==1.10.0 - # via twine platformdirs==4.2.0 # via virtualenv pluggy==1.4.0 @@ -72,9 +52,8 @@ pre-commit==3.6.2 pycryptodome==3.20.0 # via signal-export pygments==2.17.2 - # via readme-renderer # via rich -pyproject-hooks==1.0.0 +pyproject-hooks==1.1.0 # via build pyright==1.1.354 pytest==8.1.1 @@ -82,39 +61,21 @@ pytest==8.1.1 pytest-cov==4.1.0 pyyaml==6.0.1 # via pre-commit -readme-renderer==43.0 - # via twine -requests==2.31.0 - # via requests-toolbelt - # via twine -requests-toolbelt==1.0.0 - # via twine -rfc3986==2.0.0 - # via twine rich==13.7.1 - # via twine # via typer -ruff==0.3.3 setuptools==69.2.0 # via nodeenv # via setuptools-scm -setuptools-scm==8.0.4 +setuptools-scm==8.1.0 shellingham==1.5.4 # via typer soupsieve==2.5 # via beautifulsoup4 -twine==5.0.0 typer==0.9.0 # via signal-export types-emoji==2.1.0.3 types-markdown==3.6.0.20240316 typing-extensions==4.10.0 - # via setuptools-scm # via typer -urllib3==2.2.1 - # via requests - # via twine virtualenv==20.25.1 # via pre-commit -zipp==3.18.1 - # via importlib-metadata diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..a35c069 --- /dev/null +++ b/setup.py @@ -0,0 +1,105 @@ +# -*- coding: ISO-8859-1 -*- +# setup.py: the distutils script +# +# Copyright (C) 2015 David Riggleman +# Copyright (C) 2013 Kali Kaneko (sqlcipher support) +# Copyright (C) 2005-2010 Gerhard Häring +# +# This file is part of pysqlcipher. +# +# This software is provided 'as-is', without any express or implied +# warranty. In no event will the authors be held liable for any damages +# arising from the use of this software. +# +# Permission is granted to anyone to use this software for any purpose, +# including commercial applications, and to alter it and redistribute it +# freely, subject to the following restrictions: +# +# 1. The origin of this software must not be misrepresented; you must not +# claim that you wrote the original software. If you use this software +# in a product, an acknowledgment in the product documentation would be +# appreciated but is not required. +# 2. Altered source versions must be plainly marked as such, and must not be +# misrepresented as being the original software. +# 3. This notice may not be removed or altered from any source distribution. +import os +import sys +from pathlib import Path +from typing import Optional + +import setuptools +from setuptools import Extension +from setuptools.command.build_ext import build_ext + +# Work around clang raising hard error for unused arguments +if sys.platform == "darwin": + os.environ["CFLAGS"] = "-Qunused-arguments" + print("CFLAGS: " + os.environ["CFLAGS"]) + + +def quote_argument(arg: str) -> str: + quote = '"' if sys.platform != "win32" else '\\"' + return quote + arg + quote + + +PACKAGE_NAME = "pysqlcipher3" +sources = [str(p) for p in Path("src/pysqlcipher3/c").glob("*.c")] +EXTENSION_MODULE_NAME = "._sqlite3" +define_macros: Optional[list[tuple[str, Optional[str]]]] = [ + ("MODULE_NAME", quote_argument(PACKAGE_NAME + ".dbapi2")) +] + + +class AmalgationLibSQLCipherBuilder(build_ext): + def build_extension(self, ext): # noqa # type: ignore + sqlcipher_root = "src/pysqlcipher3/c/sqlcipher" + sqlcipher_header = os.path.join(sqlcipher_root, "sqlite3.h") + sqlcipher_source = os.path.join(sqlcipher_root, "sqlite3.c") + if not os.path.exists(sqlcipher_header) or not os.path.exists(sqlcipher_source): + raise RuntimeError("SQLCipher amalgamation not found") + + ext.include_dirs.append(sqlcipher_root) + ext.sources.append(sqlcipher_source) + + # build with fulltext search enabled + ext.define_macros.append(("SQLITE_ENABLE_FTS3", "1")) + ext.define_macros.append(("SQLITE_ENABLE_RTREE", "1")) + + # SQLCipher options + ext.define_macros.append(("SQLITE_ENABLE_LOAD_EXTENSION", "1")) + ext.define_macros.append(("SQLITE_HAS_CODEC", "1")) + ext.define_macros.append(("SQLITE_TEMP_STORE", "2")) + + if sys.platform == "win32": + # Try to locate openssl + openssl_conf = os.environ.get("OPENSSL_CONF") + if not openssl_conf: + error_message = "Fatal error: OpenSSL could not be detected!" + raise RuntimeError(error_message) + + openssl = os.path.dirname(os.path.dirname(openssl_conf)) + openssl_lib_path = os.path.join(openssl, "lib") + + # Configure the compiler + ext.include_dirs.append(os.path.join(openssl, "include")) + ext.define_macros.append(("inline", "__inline")) + + # Configure the linker + ext.extra_link_args.append("libeay32.lib") + ext.extra_link_args.append("/LIBPATH:" + openssl_lib_path) + else: + ext.extra_link_args.append("-lcrypto") + + build_ext.build_extension(self, ext) + + +setuptools.setup( + ext_modules=[ + Extension( + name=PACKAGE_NAME + EXTENSION_MODULE_NAME, + sources=sources, + define_macros=define_macros, + ) + ], + cmdclass={"build_ext": AmalgationLibSQLCipherBuilder}, +) diff --git a/src/pysqlcipher3/README.md b/src/pysqlcipher3/README.md new file mode 100644 index 0000000..71a9f38 --- /dev/null +++ b/src/pysqlcipher3/README.md @@ -0,0 +1,17 @@ +# pysqlcipher3 + +Source: https://github.com/rigglemania/pysqlcipher3/ + +All files in this tree include their original copyright statements. + +Vendored to make static builds easier. + +Build on macOS: +```bash +export LDFLAGS="-L/opt/homebrew/opt/openssl@3/lib" +export CPPFLAGS="-I/opt/homebrew/opt/openssl@3/include" +python setup.py bdist_wheel + +# or +python -m build +``` diff --git a/src/pysqlcipher3/__init__.py b/src/pysqlcipher3/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/pysqlcipher3/c/cache.c b/src/pysqlcipher3/c/cache.c new file mode 100644 index 0000000..3689a4e --- /dev/null +++ b/src/pysqlcipher3/c/cache.c @@ -0,0 +1,357 @@ +/* cache .c - a LRU cache + * + * Copyright (C) 2004-2010 Gerhard Häring + * + * This file is part of pysqlite. + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + */ + +#include "cache.h" +#include + +/* only used internally */ +pysqlite_Node* pysqlite_new_node(PyObject* key, PyObject* data) +{ + pysqlite_Node* node; + + node = (pysqlite_Node*) (pysqlite_NodeType.tp_alloc(&pysqlite_NodeType, 0)); + if (!node) { + return NULL; + } + + Py_INCREF(key); + node->key = key; + + Py_INCREF(data); + node->data = data; + + node->prev = NULL; + node->next = NULL; + + return node; +} + +void pysqlite_node_dealloc(pysqlite_Node* self) +{ + Py_DECREF(self->key); + Py_DECREF(self->data); + + Py_TYPE(self)->tp_free((PyObject*)self); +} + +int pysqlite_cache_init(pysqlite_Cache* self, PyObject* args, PyObject* kwargs) +{ + PyObject* factory; + int size = 10; + + self->factory = NULL; + + if (!PyArg_ParseTuple(args, "O|i", &factory, &size)) { + return -1; + } + + /* minimum cache size is 5 entries */ + if (size < 5) { + size = 5; + } + self->size = size; + self->first = NULL; + self->last = NULL; + + self->mapping = PyDict_New(); + if (!self->mapping) { + return -1; + } + + Py_INCREF(factory); + self->factory = factory; + + self->decref_factory = 1; + + return 0; +} + +void pysqlite_cache_dealloc(pysqlite_Cache* self) +{ + pysqlite_Node* node; + pysqlite_Node* delete_node; + + if (!self->factory) { + /* constructor failed, just get out of here */ + return; + } + + /* iterate over all nodes and deallocate them */ + node = self->first; + while (node) { + delete_node = node; + node = node->next; + Py_DECREF(delete_node); + } + + if (self->decref_factory) { + Py_DECREF(self->factory); + } + Py_DECREF(self->mapping); + + Py_TYPE(self)->tp_free((PyObject*)self); +} + +PyObject* pysqlite_cache_get(pysqlite_Cache* self, PyObject* args) +{ + PyObject* key = args; + pysqlite_Node* node; + pysqlite_Node* ptr; + PyObject* data; + + node = (pysqlite_Node*)PyDict_GetItem(self->mapping, key); + if (node) { + /* an entry for this key already exists in the cache */ + + /* increase usage counter of the node found */ + if (node->count < LONG_MAX) { + node->count++; + } + + /* if necessary, reorder entries in the cache by swapping positions */ + if (node->prev && node->count > node->prev->count) { + ptr = node->prev; + + while (ptr->prev && node->count > ptr->prev->count) { + ptr = ptr->prev; + } + + if (node->next) { + node->next->prev = node->prev; + } else { + self->last = node->prev; + } + if (node->prev) { + node->prev->next = node->next; + } + if (ptr->prev) { + ptr->prev->next = node; + } else { + self->first = node; + } + + node->next = ptr; + node->prev = ptr->prev; + if (!node->prev) { + self->first = node; + } + ptr->prev = node; + } + } else { + /* There is no entry for this key in the cache, yet. We'll insert a new + * entry in the cache, and make space if necessary by throwing the + * least used item out of the cache. */ + + if (PyDict_Size(self->mapping) == self->size) { + if (self->last) { + node = self->last; + + if (PyDict_DelItem(self->mapping, self->last->key) != 0) { + return NULL; + } + + if (node->prev) { + node->prev->next = NULL; + } + self->last = node->prev; + node->prev = NULL; + + Py_DECREF(node); + } + } + + data = PyObject_CallFunction(self->factory, "O", key); + + if (!data) { + return NULL; + } + + node = pysqlite_new_node(key, data); + if (!node) { + return NULL; + } + node->prev = self->last; + + Py_DECREF(data); + + if (PyDict_SetItem(self->mapping, key, (PyObject*)node) != 0) { + Py_DECREF(node); + return NULL; + } + + if (self->last) { + self->last->next = node; + } else { + self->first = node; + } + self->last = node; + } + + Py_INCREF(node->data); + return node->data; +} + +PyObject* pysqlite_cache_display(pysqlite_Cache* self, PyObject* args) +{ + pysqlite_Node* ptr; + PyObject* prevkey; + PyObject* nextkey; + PyObject* display_str; + + ptr = self->first; + + while (ptr) { + if (ptr->prev) { + prevkey = ptr->prev->key; + } else { + prevkey = Py_None; + } + + if (ptr->next) { + nextkey = ptr->next->key; + } else { + nextkey = Py_None; + } + + display_str = PyUnicode_FromFormat("%S <- %S -> %S\n", + prevkey, ptr->key, nextkey); + if (!display_str) { + return NULL; + } + PyObject_Print(display_str, stdout, Py_PRINT_RAW); + Py_DECREF(display_str); + + ptr = ptr->next; + } + + Py_INCREF(Py_None); + return Py_None; +} + +static PyMethodDef cache_methods[] = { + {"get", (PyCFunction)pysqlite_cache_get, METH_O, + PyDoc_STR("Gets an entry from the cache or calls the factory function to produce one.")}, + {"display", (PyCFunction)pysqlite_cache_display, METH_NOARGS, + PyDoc_STR("For debugging only.")}, + {NULL, NULL} +}; + +PyTypeObject pysqlite_NodeType = { + PyVarObject_HEAD_INIT(NULL, 0) + MODULE_NAME "Node", /* tp_name */ + sizeof(pysqlite_Node), /* tp_basicsize */ + 0, /* tp_itemsize */ + (destructor)pysqlite_node_dealloc, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_reserved */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + 0, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, /* tp_flags */ + 0, /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + 0, /* tp_methods */ + 0, /* tp_members */ + 0, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + (initproc)0, /* tp_init */ + 0, /* tp_alloc */ + 0, /* tp_new */ + 0 /* tp_free */ +}; + +PyTypeObject pysqlite_CacheType = { + PyVarObject_HEAD_INIT(NULL, 0) + MODULE_NAME ".Cache", /* tp_name */ + sizeof(pysqlite_Cache), /* tp_basicsize */ + 0, /* tp_itemsize */ + (destructor)pysqlite_cache_dealloc, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_reserved */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + 0, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, /* tp_flags */ + 0, /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + cache_methods, /* tp_methods */ + 0, /* tp_members */ + 0, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + (initproc)pysqlite_cache_init, /* tp_init */ + 0, /* tp_alloc */ + 0, /* tp_new */ + 0 /* tp_free */ +}; + +extern int pysqlite_cache_setup_types(void) +{ + int rc; + + pysqlite_NodeType.tp_new = PyType_GenericNew; + pysqlite_CacheType.tp_new = PyType_GenericNew; + + rc = PyType_Ready(&pysqlite_NodeType); + if (rc < 0) { + return rc; + } + + rc = PyType_Ready(&pysqlite_CacheType); + return rc; +} diff --git a/src/pysqlcipher3/c/cache.h b/src/pysqlcipher3/c/cache.h new file mode 100644 index 0000000..a133903 --- /dev/null +++ b/src/pysqlcipher3/c/cache.h @@ -0,0 +1,73 @@ +/* cache.h - definitions for the LRU cache + * + * Copyright (C) 2004-2010 Gerhard Häring + * + * This file is part of pysqlite. + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + */ + +#ifndef PYSQLITE_CACHE_H +#define PYSQLITE_CACHE_H +#include "Python.h" + +/* The LRU cache is implemented as a combination of a doubly-linked with a + * dictionary. The list items are of type 'Node' and the dictionary has the + * nodes as values. */ + +typedef struct _pysqlite_Node +{ + PyObject_HEAD + PyObject* key; + PyObject* data; + long count; + struct _pysqlite_Node* prev; + struct _pysqlite_Node* next; +} pysqlite_Node; + +typedef struct +{ + PyObject_HEAD + int size; + + /* a dictionary mapping keys to Node entries */ + PyObject* mapping; + + /* the factory callable */ + PyObject* factory; + + pysqlite_Node* first; + pysqlite_Node* last; + + /* if set, decrement the factory function when the Cache is deallocated. + * this is almost always desirable, but not in the pysqlite context */ + int decref_factory; +} pysqlite_Cache; + +extern PyTypeObject pysqlite_NodeType; +extern PyTypeObject pysqlite_CacheType; + +int pysqlite_node_init(pysqlite_Node* self, PyObject* args, PyObject* kwargs); +void pysqlite_node_dealloc(pysqlite_Node* self); + +int pysqlite_cache_init(pysqlite_Cache* self, PyObject* args, PyObject* kwargs); +void pysqlite_cache_dealloc(pysqlite_Cache* self); +PyObject* pysqlite_cache_get(pysqlite_Cache* self, PyObject* args); + +int pysqlite_cache_setup_types(void); + +#endif diff --git a/src/pysqlcipher3/c/connection.c b/src/pysqlcipher3/c/connection.c new file mode 100644 index 0000000..7e5f0c7 --- /dev/null +++ b/src/pysqlcipher3/c/connection.c @@ -0,0 +1,1741 @@ +/* connection.c - the connection type + * + * Copyright (C) 2004-2010 Gerhard Häring + * + * This file is part of pysqlite. + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + */ + +#include "cache.h" +#include "module.h" +#include "structmember.h" +#include "connection.h" +#include "statement.h" +#include "cursor.h" +#include "prepare_protocol.h" +#include "util.h" + +#include "pythread.h" + +#define ACTION_FINALIZE 1 +#define ACTION_RESET 2 + +#if SQLITE_VERSION_NUMBER >= 3003008 +#ifndef SQLITE_OMIT_LOAD_EXTENSION +#define HAVE_LOAD_EXTENSION +#endif +#endif + +_Py_IDENTIFIER(cursor); + +static int pysqlite_connection_set_isolation_level(pysqlite_Connection* self, PyObject* isolation_level); +static void _pysqlite_drop_unused_cursor_references(pysqlite_Connection* self); + + +static void _sqlite3_result_error(sqlite3_context* ctx, const char* errmsg, int len) +{ + /* in older SQLite versions, calling sqlite3_result_error in callbacks + * triggers a bug in SQLite that leads either to irritating results or + * segfaults, depending on the SQLite version */ +#if SQLITE_VERSION_NUMBER >= 3003003 + sqlite3_result_error(ctx, errmsg, len); +#else + PyErr_SetString(pysqlite_OperationalError, errmsg); +#endif +} + +int pysqlite_connection_init(pysqlite_Connection* self, PyObject* args, PyObject* kwargs) +{ + static char *kwlist[] = { + "database", "timeout", "detect_types", "isolation_level", + "check_same_thread", "factory", "cached_statements", "uri", + NULL + }; + + char* database; + int detect_types = 0; + PyObject* isolation_level = NULL; + PyObject* factory = NULL; + int check_same_thread = 1; + int cached_statements = 100; + int uri = 0; + double timeout = 5.0; + int rc; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s|diOiOip", kwlist, + &database, &timeout, &detect_types, + &isolation_level, &check_same_thread, + &factory, &cached_statements, &uri)) + { + return -1; + } + + self->initialized = 1; + + self->begin_statement = NULL; + + self->statement_cache = NULL; + self->statements = NULL; + self->cursors = NULL; + + Py_INCREF(Py_None); + self->row_factory = Py_None; + + Py_INCREF(&PyUnicode_Type); + self->text_factory = (PyObject*)&PyUnicode_Type; + +#ifdef SQLITE_OPEN_URI + Py_BEGIN_ALLOW_THREADS + rc = sqlite3_open_v2(database, &self->db, + SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | + (uri ? SQLITE_OPEN_URI : 0), NULL); +#else + if (uri) { + PyErr_SetString(pysqlite_NotSupportedError, "URIs not supported"); + return -1; + } + Py_BEGIN_ALLOW_THREADS + rc = sqlite3_open(database, &self->db); +#endif + Py_END_ALLOW_THREADS + + if (rc != SQLITE_OK) { + _pysqlite_seterror(self->db, NULL); + return -1; + } + + if (!isolation_level) { + isolation_level = PyUnicode_FromString(""); + if (!isolation_level) { + return -1; + } + } else { + Py_INCREF(isolation_level); + } + self->isolation_level = NULL; + if (pysqlite_connection_set_isolation_level(self, isolation_level) < 0) { + Py_DECREF(isolation_level); + return -1; + } + Py_DECREF(isolation_level); + + self->statement_cache = (pysqlite_Cache*)PyObject_CallFunction((PyObject*)&pysqlite_CacheType, "Oi", self, cached_statements); + if (PyErr_Occurred()) { + return -1; + } + + self->created_statements = 0; + self->created_cursors = 0; + + /* Create lists of weak references to statements/cursors */ + self->statements = PyList_New(0); + self->cursors = PyList_New(0); + if (!self->statements || !self->cursors) { + return -1; + } + + /* By default, the Cache class INCREFs the factory in its initializer, and + * decrefs it in its deallocator method. Since this would create a circular + * reference here, we're breaking it by decrementing self, and telling the + * cache class to not decref the factory (self) in its deallocator. + */ + self->statement_cache->decref_factory = 0; + Py_DECREF(self); + + self->inTransaction = 0; + self->detect_types = detect_types; + self->timeout = timeout; + (void)sqlite3_busy_timeout(self->db, (int)(timeout*1000)); +#ifdef WITH_THREAD + self->thread_ident = PyThread_get_thread_ident(); +#endif + self->check_same_thread = check_same_thread; + + self->function_pinboard = PyDict_New(); + if (!self->function_pinboard) { + return -1; + } + + self->collations = PyDict_New(); + if (!self->collations) { + return -1; + } + + self->Warning = pysqlite_Warning; + self->Error = pysqlite_Error; + self->InterfaceError = pysqlite_InterfaceError; + self->DatabaseError = pysqlite_DatabaseError; + self->DataError = pysqlite_DataError; + self->OperationalError = pysqlite_OperationalError; + self->IntegrityError = pysqlite_IntegrityError; + self->InternalError = pysqlite_InternalError; + self->ProgrammingError = pysqlite_ProgrammingError; + self->NotSupportedError = pysqlite_NotSupportedError; + + return 0; +} + +/* Empty the entire statement cache of this connection */ +void pysqlite_flush_statement_cache(pysqlite_Connection* self) +{ + pysqlite_Node* node; + pysqlite_Statement* statement; + + node = self->statement_cache->first; + + while (node) { + statement = (pysqlite_Statement*)(node->data); + (void)pysqlite_statement_finalize(statement); + node = node->next; + } + + Py_DECREF(self->statement_cache); + self->statement_cache = (pysqlite_Cache*)PyObject_CallFunction((PyObject*)&pysqlite_CacheType, "O", self); + Py_DECREF(self); + self->statement_cache->decref_factory = 0; +} + +/* action in (ACTION_RESET, ACTION_FINALIZE) */ +void pysqlite_do_all_statements(pysqlite_Connection* self, int action, int reset_cursors) +{ + int i; + PyObject* weakref; + PyObject* statement; + pysqlite_Cursor* cursor; + + for (i = 0; i < PyList_Size(self->statements); i++) { + weakref = PyList_GetItem(self->statements, i); + statement = PyWeakref_GetObject(weakref); + if (statement != Py_None) { + Py_INCREF(statement); + if (action == ACTION_RESET) { + (void)pysqlite_statement_reset((pysqlite_Statement*)statement); + } else { + (void)pysqlite_statement_finalize((pysqlite_Statement*)statement); + } + Py_DECREF(statement); + } + } + + if (reset_cursors) { + for (i = 0; i < PyList_Size(self->cursors); i++) { + weakref = PyList_GetItem(self->cursors, i); + cursor = (pysqlite_Cursor*)PyWeakref_GetObject(weakref); + if ((PyObject*)cursor != Py_None) { + cursor->reset = 1; + } + } + } +} + +void pysqlite_connection_dealloc(pysqlite_Connection* self) +{ + Py_XDECREF(self->statement_cache); + + /* Clean up if user has not called .close() explicitly. */ + if (self->db) { + Py_BEGIN_ALLOW_THREADS + sqlite3_close(self->db); + Py_END_ALLOW_THREADS + } + + if (self->begin_statement) { + PyMem_Free(self->begin_statement); + } + Py_XDECREF(self->isolation_level); + Py_XDECREF(self->function_pinboard); + Py_XDECREF(self->row_factory); + Py_XDECREF(self->text_factory); + Py_XDECREF(self->collations); + Py_XDECREF(self->statements); + Py_XDECREF(self->cursors); + + Py_TYPE(self)->tp_free((PyObject*)self); +} + +/* + * Registers a cursor with the connection. + * + * 0 => error; 1 => ok + */ +int pysqlite_connection_register_cursor(pysqlite_Connection* connection, PyObject* cursor) +{ + PyObject* weakref; + + weakref = PyWeakref_NewRef((PyObject*)cursor, NULL); + if (!weakref) { + goto error; + } + + if (PyList_Append(connection->cursors, weakref) != 0) { + Py_CLEAR(weakref); + goto error; + } + + Py_DECREF(weakref); + + return 1; +error: + return 0; +} + +PyObject* pysqlite_connection_cursor(pysqlite_Connection* self, PyObject* args, PyObject* kwargs) +{ + static char *kwlist[] = {"factory", NULL, NULL}; + PyObject* factory = NULL; + PyObject* cursor; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|O", kwlist, + &factory)) { + return NULL; + } + + if (!pysqlite_check_thread(self) || !pysqlite_check_connection(self)) { + return NULL; + } + + if (factory == NULL) { + factory = (PyObject*)&pysqlite_CursorType; + } + + cursor = PyObject_CallFunction(factory, "O", self); + + _pysqlite_drop_unused_cursor_references(self); + + if (cursor && self->row_factory != Py_None) { + Py_XDECREF(((pysqlite_Cursor*)cursor)->row_factory); + Py_INCREF(self->row_factory); + ((pysqlite_Cursor*)cursor)->row_factory = self->row_factory; + } + + return cursor; +} + +PyObject* pysqlite_connection_close(pysqlite_Connection* self, PyObject* args) +{ + int rc; + + if (!pysqlite_check_thread(self)) { + return NULL; + } + + pysqlite_do_all_statements(self, ACTION_FINALIZE, 1); + + if (self->db) { + Py_BEGIN_ALLOW_THREADS + rc = sqlite3_close(self->db); + Py_END_ALLOW_THREADS + + if (rc != SQLITE_OK) { + _pysqlite_seterror(self->db, NULL); + return NULL; + } else { + self->db = NULL; + } + } + + Py_INCREF(Py_None); + return Py_None; +} + +/* + * Checks if a connection object is usable (i. e. not closed). + * + * 0 => error; 1 => ok + */ +int pysqlite_check_connection(pysqlite_Connection* con) +{ + if (!con->initialized) { + PyErr_SetString(pysqlite_ProgrammingError, "Base Connection.__init__ not called."); + return 0; + } + + if (!con->db) { + PyErr_SetString(pysqlite_ProgrammingError, "Cannot operate on a closed database."); + return 0; + } else { + return 1; + } +} + +PyObject* _pysqlite_connection_begin(pysqlite_Connection* self) +{ + int rc; + const char* tail; + sqlite3_stmt* statement; + + Py_BEGIN_ALLOW_THREADS + rc = sqlite3_prepare(self->db, self->begin_statement, -1, &statement, &tail); + Py_END_ALLOW_THREADS + + if (rc != SQLITE_OK) { + _pysqlite_seterror(self->db, statement); + goto error; + } + + rc = pysqlite_step(statement, self); + if (rc == SQLITE_DONE) { + self->inTransaction = 1; + } else { + _pysqlite_seterror(self->db, statement); + } + + Py_BEGIN_ALLOW_THREADS + rc = sqlite3_finalize(statement); + Py_END_ALLOW_THREADS + + if (rc != SQLITE_OK && !PyErr_Occurred()) { + _pysqlite_seterror(self->db, NULL); + } + +error: + if (PyErr_Occurred()) { + return NULL; + } else { + Py_INCREF(Py_None); + return Py_None; + } +} + +PyObject* pysqlite_connection_commit(pysqlite_Connection* self, PyObject* args) +{ + int rc; + const char* tail; + sqlite3_stmt* statement; + + if (!pysqlite_check_thread(self) || !pysqlite_check_connection(self)) { + return NULL; + } + + if (self->inTransaction) { + pysqlite_do_all_statements(self, ACTION_RESET, 0); + + Py_BEGIN_ALLOW_THREADS + rc = sqlite3_prepare(self->db, "COMMIT", -1, &statement, &tail); + Py_END_ALLOW_THREADS + if (rc != SQLITE_OK) { + _pysqlite_seterror(self->db, NULL); + goto error; + } + + rc = pysqlite_step(statement, self); + if (rc == SQLITE_DONE) { + self->inTransaction = 0; + } else { + _pysqlite_seterror(self->db, statement); + } + + Py_BEGIN_ALLOW_THREADS + rc = sqlite3_finalize(statement); + Py_END_ALLOW_THREADS + if (rc != SQLITE_OK && !PyErr_Occurred()) { + _pysqlite_seterror(self->db, NULL); + } + + } + +error: + if (PyErr_Occurred()) { + return NULL; + } else { + Py_INCREF(Py_None); + return Py_None; + } +} + +PyObject* pysqlite_connection_rollback(pysqlite_Connection* self, PyObject* args) +{ + int rc; + const char* tail; + sqlite3_stmt* statement; + + if (!pysqlite_check_thread(self) || !pysqlite_check_connection(self)) { + return NULL; + } + + if (self->inTransaction) { + pysqlite_do_all_statements(self, ACTION_RESET, 1); + + Py_BEGIN_ALLOW_THREADS + rc = sqlite3_prepare(self->db, "ROLLBACK", -1, &statement, &tail); + Py_END_ALLOW_THREADS + if (rc != SQLITE_OK) { + _pysqlite_seterror(self->db, NULL); + goto error; + } + + rc = pysqlite_step(statement, self); + if (rc == SQLITE_DONE) { + self->inTransaction = 0; + } else { + _pysqlite_seterror(self->db, statement); + } + + Py_BEGIN_ALLOW_THREADS + rc = sqlite3_finalize(statement); + Py_END_ALLOW_THREADS + if (rc != SQLITE_OK && !PyErr_Occurred()) { + _pysqlite_seterror(self->db, NULL); + } + + } + +error: + if (PyErr_Occurred()) { + return NULL; + } else { + Py_INCREF(Py_None); + return Py_None; + } +} + +static int +_pysqlite_set_result(sqlite3_context* context, PyObject* py_val) +{ + if (py_val == Py_None) { + sqlite3_result_null(context); + } else if (PyLong_Check(py_val)) { + sqlite_int64 value = _pysqlite_long_as_int64(py_val); + if (value == -1 && PyErr_Occurred()) + return -1; + sqlite3_result_int64(context, value); + } else if (PyFloat_Check(py_val)) { + sqlite3_result_double(context, PyFloat_AsDouble(py_val)); + } else if (PyUnicode_Check(py_val)) { + const char *str = _PyUnicode_AsString(py_val); + if (str == NULL) + return -1; + sqlite3_result_text(context, str, -1, SQLITE_TRANSIENT); + } else if (PyObject_CheckBuffer(py_val)) { + const char* buffer; + Py_ssize_t buflen; + if (PyObject_AsCharBuffer(py_val, &buffer, &buflen) != 0) { + PyErr_SetString(PyExc_ValueError, + "could not convert BLOB to buffer"); + return -1; + } + if (buflen > INT_MAX) { + PyErr_SetString(PyExc_OverflowError, + "BLOB longer than INT_MAX bytes"); + return -1; + } + sqlite3_result_blob(context, buffer, (int)buflen, SQLITE_TRANSIENT); + } else { + return -1; + } + return 0; +} + +PyObject* _pysqlite_build_py_params(sqlite3_context *context, int argc, sqlite3_value** argv) +{ + PyObject* args; + int i; + sqlite3_value* cur_value; + PyObject* cur_py_value; + const char* val_str; + Py_ssize_t buflen; + + args = PyTuple_New(argc); + if (!args) { + return NULL; + } + + for (i = 0; i < argc; i++) { + cur_value = argv[i]; + switch (sqlite3_value_type(argv[i])) { + case SQLITE_INTEGER: + cur_py_value = _pysqlite_long_from_int64(sqlite3_value_int64(cur_value)); + break; + case SQLITE_FLOAT: + cur_py_value = PyFloat_FromDouble(sqlite3_value_double(cur_value)); + break; + case SQLITE_TEXT: + val_str = (const char*)sqlite3_value_text(cur_value); + cur_py_value = PyUnicode_FromString(val_str); + /* TODO: have a way to show errors here */ + if (!cur_py_value) { + PyErr_Clear(); + Py_INCREF(Py_None); + cur_py_value = Py_None; + } + break; + case SQLITE_BLOB: + buflen = sqlite3_value_bytes(cur_value); + cur_py_value = PyBytes_FromStringAndSize( + sqlite3_value_blob(cur_value), buflen); + break; + case SQLITE_NULL: + default: + Py_INCREF(Py_None); + cur_py_value = Py_None; + } + + if (!cur_py_value) { + Py_DECREF(args); + return NULL; + } + + PyTuple_SetItem(args, i, cur_py_value); + + } + + return args; +} + +void _pysqlite_func_callback(sqlite3_context* context, int argc, sqlite3_value** argv) +{ + PyObject* args; + PyObject* py_func; + PyObject* py_retval = NULL; + int ok; + +#ifdef WITH_THREAD + PyGILState_STATE threadstate; + + threadstate = PyGILState_Ensure(); +#endif + + py_func = (PyObject*)sqlite3_user_data(context); + + args = _pysqlite_build_py_params(context, argc, argv); + if (args) { + py_retval = PyObject_CallObject(py_func, args); + Py_DECREF(args); + } + + ok = 0; + if (py_retval) { + ok = _pysqlite_set_result(context, py_retval) == 0; + Py_DECREF(py_retval); + } + if (!ok) { + if (_enable_callback_tracebacks) { + PyErr_Print(); + } else { + PyErr_Clear(); + } + _sqlite3_result_error(context, "user-defined function raised exception", -1); + } + +#ifdef WITH_THREAD + PyGILState_Release(threadstate); +#endif +} + +static void _pysqlite_step_callback(sqlite3_context *context, int argc, sqlite3_value** params) +{ + PyObject* args; + PyObject* function_result = NULL; + PyObject* aggregate_class; + PyObject** aggregate_instance; + PyObject* stepmethod = NULL; + +#ifdef WITH_THREAD + PyGILState_STATE threadstate; + + threadstate = PyGILState_Ensure(); +#endif + + aggregate_class = (PyObject*)sqlite3_user_data(context); + + aggregate_instance = (PyObject**)sqlite3_aggregate_context(context, sizeof(PyObject*)); + + if (*aggregate_instance == 0) { + *aggregate_instance = PyObject_CallFunction(aggregate_class, ""); + + if (PyErr_Occurred()) { + *aggregate_instance = 0; + if (_enable_callback_tracebacks) { + PyErr_Print(); + } else { + PyErr_Clear(); + } + _sqlite3_result_error(context, "user-defined aggregate's '__init__' method raised error", -1); + goto error; + } + } + + stepmethod = PyObject_GetAttrString(*aggregate_instance, "step"); + if (!stepmethod) { + goto error; + } + + args = _pysqlite_build_py_params(context, argc, params); + if (!args) { + goto error; + } + + function_result = PyObject_CallObject(stepmethod, args); + Py_DECREF(args); + + if (!function_result) { + if (_enable_callback_tracebacks) { + PyErr_Print(); + } else { + PyErr_Clear(); + } + _sqlite3_result_error(context, "user-defined aggregate's 'step' method raised error", -1); + } + +error: + Py_XDECREF(stepmethod); + Py_XDECREF(function_result); + +#ifdef WITH_THREAD + PyGILState_Release(threadstate); +#endif +} + +void _pysqlite_final_callback(sqlite3_context* context) +{ + PyObject* function_result; + PyObject** aggregate_instance; + _Py_IDENTIFIER(finalize); + int ok; + PyObject *exception, *value, *tb; + int restore; + +#ifdef WITH_THREAD + PyGILState_STATE threadstate; + + threadstate = PyGILState_Ensure(); +#endif + + aggregate_instance = (PyObject**)sqlite3_aggregate_context(context, sizeof(PyObject*)); + if (!*aggregate_instance) { + /* this branch is executed if there was an exception in the aggregate's + * __init__ */ + + goto error; + } + + /* Keep the exception (if any) of the last call to step() */ + PyErr_Fetch(&exception, &value, &tb); + restore = 1; + + function_result = _PyObject_CallMethodId(*aggregate_instance, &PyId_finalize, ""); + + Py_DECREF(*aggregate_instance); + + ok = 0; + if (function_result) { + ok = _pysqlite_set_result(context, function_result) == 0; + Py_DECREF(function_result); + } + if (!ok) { + if (_enable_callback_tracebacks) { + PyErr_Print(); + } else { + PyErr_Clear(); + } + _sqlite3_result_error(context, "user-defined aggregate's 'finalize' method raised error", -1); +#if SQLITE_VERSION_NUMBER < 3003003 + /* with old SQLite versions, _sqlite3_result_error() sets a new Python + exception, so don't restore the previous exception */ + restore = 0; +#endif + } + + if (restore) { + /* Restore the exception (if any) of the last call to step(), + but clear also the current exception if finalize() failed */ + PyErr_Restore(exception, value, tb); + } + +error: +#ifdef WITH_THREAD + PyGILState_Release(threadstate); +#endif + /* explicit return to avoid a compilation error if WITH_THREAD + is not defined */ + return; +} + +static void _pysqlite_drop_unused_statement_references(pysqlite_Connection* self) +{ + PyObject* new_list; + PyObject* weakref; + int i; + + /* we only need to do this once in a while */ + if (self->created_statements++ < 200) { + return; + } + + self->created_statements = 0; + + new_list = PyList_New(0); + if (!new_list) { + return; + } + + for (i = 0; i < PyList_Size(self->statements); i++) { + weakref = PyList_GetItem(self->statements, i); + if (PyWeakref_GetObject(weakref) != Py_None) { + if (PyList_Append(new_list, weakref) != 0) { + Py_DECREF(new_list); + return; + } + } + } + + Py_DECREF(self->statements); + self->statements = new_list; +} + +static void _pysqlite_drop_unused_cursor_references(pysqlite_Connection* self) +{ + PyObject* new_list; + PyObject* weakref; + int i; + + /* we only need to do this once in a while */ + if (self->created_cursors++ < 200) { + return; + } + + self->created_cursors = 0; + + new_list = PyList_New(0); + if (!new_list) { + return; + } + + for (i = 0; i < PyList_Size(self->cursors); i++) { + weakref = PyList_GetItem(self->cursors, i); + if (PyWeakref_GetObject(weakref) != Py_None) { + if (PyList_Append(new_list, weakref) != 0) { + Py_DECREF(new_list); + return; + } + } + } + + Py_DECREF(self->cursors); + self->cursors = new_list; +} + +PyObject* pysqlite_connection_create_function(pysqlite_Connection* self, PyObject* args, PyObject* kwargs) +{ + static char *kwlist[] = {"name", "narg", "func", NULL, NULL}; + + PyObject* func; + char* name; + int narg; + int rc; + + if (!pysqlite_check_thread(self) || !pysqlite_check_connection(self)) { + return NULL; + } + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "siO", kwlist, + &name, &narg, &func)) + { + return NULL; + } + + rc = sqlite3_create_function(self->db, name, narg, SQLITE_UTF8, (void*)func, _pysqlite_func_callback, NULL, NULL); + + if (rc != SQLITE_OK) { + /* Workaround for SQLite bug: no error code or string is available here */ + PyErr_SetString(pysqlite_OperationalError, "Error creating function"); + return NULL; + } else { + if (PyDict_SetItem(self->function_pinboard, func, Py_None) == -1) + return NULL; + + Py_INCREF(Py_None); + return Py_None; + } +} + +PyObject* pysqlite_connection_create_aggregate(pysqlite_Connection* self, PyObject* args, PyObject* kwargs) +{ + PyObject* aggregate_class; + + int n_arg; + char* name; + static char *kwlist[] = { "name", "n_arg", "aggregate_class", NULL }; + int rc; + + if (!pysqlite_check_thread(self) || !pysqlite_check_connection(self)) { + return NULL; + } + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "siO:create_aggregate", + kwlist, &name, &n_arg, &aggregate_class)) { + return NULL; + } + + rc = sqlite3_create_function(self->db, name, n_arg, SQLITE_UTF8, (void*)aggregate_class, 0, &_pysqlite_step_callback, &_pysqlite_final_callback); + if (rc != SQLITE_OK) { + /* Workaround for SQLite bug: no error code or string is available here */ + PyErr_SetString(pysqlite_OperationalError, "Error creating aggregate"); + return NULL; + } else { + if (PyDict_SetItem(self->function_pinboard, aggregate_class, Py_None) == -1) + return NULL; + + Py_INCREF(Py_None); + return Py_None; + } +} + +static int _authorizer_callback(void* user_arg, int action, const char* arg1, const char* arg2 , const char* dbname, const char* access_attempt_source) +{ + PyObject *ret; + int rc; +#ifdef WITH_THREAD + PyGILState_STATE gilstate; + + gilstate = PyGILState_Ensure(); +#endif + + ret = PyObject_CallFunction((PyObject*)user_arg, "issss", action, arg1, arg2, dbname, access_attempt_source); + + if (ret == NULL) { + if (_enable_callback_tracebacks) + PyErr_Print(); + else + PyErr_Clear(); + + rc = SQLITE_DENY; + } + else { + if (PyLong_Check(ret)) { + rc = _PyLong_AsInt(ret); + if (rc == -1 && PyErr_Occurred()) { + if (_enable_callback_tracebacks) + PyErr_Print(); + else + PyErr_Clear(); + rc = SQLITE_DENY; + } + } + else { + rc = SQLITE_DENY; + } + Py_DECREF(ret); + } + +#ifdef WITH_THREAD + PyGILState_Release(gilstate); +#endif + return rc; +} + +static int _progress_handler(void* user_arg) +{ + int rc; + PyObject *ret; +#ifdef WITH_THREAD + PyGILState_STATE gilstate; + + gilstate = PyGILState_Ensure(); +#endif + ret = PyObject_CallFunction((PyObject*)user_arg, ""); + + if (!ret) { + if (_enable_callback_tracebacks) { + PyErr_Print(); + } else { + PyErr_Clear(); + } + + /* abort query if error occurred */ + rc = 1; + } else { + rc = (int)PyObject_IsTrue(ret); + Py_DECREF(ret); + } + +#ifdef WITH_THREAD + PyGILState_Release(gilstate); +#endif + return rc; +} + +static void _trace_callback(void* user_arg, const char* statement_string) +{ + PyObject *py_statement = NULL; + PyObject *ret = NULL; + +#ifdef WITH_THREAD + PyGILState_STATE gilstate; + + gilstate = PyGILState_Ensure(); +#endif + py_statement = PyUnicode_DecodeUTF8(statement_string, + strlen(statement_string), "replace"); + if (py_statement) { + ret = PyObject_CallFunctionObjArgs((PyObject*)user_arg, py_statement, NULL); + Py_DECREF(py_statement); + } + + if (ret) { + Py_DECREF(ret); + } else { + if (_enable_callback_tracebacks) { + PyErr_Print(); + } else { + PyErr_Clear(); + } + } + +#ifdef WITH_THREAD + PyGILState_Release(gilstate); +#endif +} + +static PyObject* pysqlite_connection_set_authorizer(pysqlite_Connection* self, PyObject* args, PyObject* kwargs) +{ + PyObject* authorizer_cb; + + static char *kwlist[] = { "authorizer_callback", NULL }; + int rc; + + if (!pysqlite_check_thread(self) || !pysqlite_check_connection(self)) { + return NULL; + } + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O:set_authorizer", + kwlist, &authorizer_cb)) { + return NULL; + } + + rc = sqlite3_set_authorizer(self->db, _authorizer_callback, (void*)authorizer_cb); + + if (rc != SQLITE_OK) { + PyErr_SetString(pysqlite_OperationalError, "Error setting authorizer callback"); + return NULL; + } else { + if (PyDict_SetItem(self->function_pinboard, authorizer_cb, Py_None) == -1) + return NULL; + + Py_INCREF(Py_None); + return Py_None; + } +} + +static PyObject* pysqlite_connection_set_progress_handler(pysqlite_Connection* self, PyObject* args, PyObject* kwargs) +{ + PyObject* progress_handler; + int n; + + static char *kwlist[] = { "progress_handler", "n", NULL }; + + if (!pysqlite_check_thread(self) || !pysqlite_check_connection(self)) { + return NULL; + } + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "Oi:set_progress_handler", + kwlist, &progress_handler, &n)) { + return NULL; + } + + if (progress_handler == Py_None) { + /* None clears the progress handler previously set */ + sqlite3_progress_handler(self->db, 0, 0, (void*)0); + } else { + sqlite3_progress_handler(self->db, n, _progress_handler, progress_handler); + if (PyDict_SetItem(self->function_pinboard, progress_handler, Py_None) == -1) + return NULL; + } + + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject* pysqlite_connection_set_trace_callback(pysqlite_Connection* self, PyObject* args, PyObject* kwargs) +{ + PyObject* trace_callback; + + static char *kwlist[] = { "trace_callback", NULL }; + + if (!pysqlite_check_thread(self) || !pysqlite_check_connection(self)) { + return NULL; + } + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O:set_trace_callback", + kwlist, &trace_callback)) { + return NULL; + } + + if (trace_callback == Py_None) { + /* None clears the trace callback previously set */ + sqlite3_trace(self->db, 0, (void*)0); + } else { + if (PyDict_SetItem(self->function_pinboard, trace_callback, Py_None) == -1) + return NULL; + sqlite3_trace(self->db, _trace_callback, trace_callback); + } + + Py_INCREF(Py_None); + return Py_None; +} + +#ifdef HAVE_LOAD_EXTENSION +static PyObject* pysqlite_enable_load_extension(pysqlite_Connection* self, PyObject* args) +{ + int rc; + int onoff; + + if (!pysqlite_check_thread(self) || !pysqlite_check_connection(self)) { + return NULL; + } + + if (!PyArg_ParseTuple(args, "i", &onoff)) { + return NULL; + } + + rc = sqlite3_enable_load_extension(self->db, onoff); + + if (rc != SQLITE_OK) { + PyErr_SetString(pysqlite_OperationalError, "Error enabling load extension"); + return NULL; + } else { + Py_INCREF(Py_None); + return Py_None; + } +} + +static PyObject* pysqlite_load_extension(pysqlite_Connection* self, PyObject* args) +{ + int rc; + char* extension_name; + char* errmsg; + + if (!pysqlite_check_thread(self) || !pysqlite_check_connection(self)) { + return NULL; + } + + if (!PyArg_ParseTuple(args, "s", &extension_name)) { + return NULL; + } + + rc = sqlite3_load_extension(self->db, extension_name, 0, &errmsg); + if (rc != 0) { + PyErr_SetString(pysqlite_OperationalError, errmsg); + return NULL; + } else { + Py_INCREF(Py_None); + return Py_None; + } +} +#endif + +int pysqlite_check_thread(pysqlite_Connection* self) +{ +#ifdef WITH_THREAD + if (self->check_same_thread) { + if (PyThread_get_thread_ident() != self->thread_ident) { + PyErr_Format(pysqlite_ProgrammingError, + "SQLite objects created in a thread can only be used in that same thread." + "The object was created in thread id %ld and this is thread id %ld", + self->thread_ident, PyThread_get_thread_ident()); + return 0; + } + + } +#endif + return 1; +} + +static PyObject* pysqlite_connection_get_isolation_level(pysqlite_Connection* self, void* unused) +{ + Py_INCREF(self->isolation_level); + return self->isolation_level; +} + +static PyObject* pysqlite_connection_get_total_changes(pysqlite_Connection* self, void* unused) +{ + if (!pysqlite_check_connection(self)) { + return NULL; + } else { + return Py_BuildValue("i", sqlite3_total_changes(self->db)); + } +} + +static int pysqlite_connection_set_isolation_level(pysqlite_Connection* self, PyObject* isolation_level) +{ + PyObject* res; + PyObject* begin_statement; + static PyObject* begin_word; + + Py_XDECREF(self->isolation_level); + + if (self->begin_statement) { + PyMem_Free(self->begin_statement); + self->begin_statement = NULL; + } + + if (isolation_level == Py_None) { + Py_INCREF(Py_None); + self->isolation_level = Py_None; + + res = pysqlite_connection_commit(self, NULL); + if (!res) { + return -1; + } + Py_DECREF(res); + + self->inTransaction = 0; + } else { + const char *statement; + Py_ssize_t size; + + Py_INCREF(isolation_level); + self->isolation_level = isolation_level; + + if (!begin_word) { + begin_word = PyUnicode_FromString("BEGIN "); + if (!begin_word) return -1; + } + begin_statement = PyUnicode_Concat(begin_word, isolation_level); + if (!begin_statement) { + return -1; + } + + statement = PyUnicode_AsUTF8AndSize(begin_statement, &size); + if (!statement) { + Py_DECREF(begin_statement); + return -1; + } + self->begin_statement = PyMem_Malloc(size + 2); + if (!self->begin_statement) { + Py_DECREF(begin_statement); + return -1; + } + + strcpy(self->begin_statement, statement); + Py_DECREF(begin_statement); + } + + return 0; +} + +PyObject* pysqlite_connection_call(pysqlite_Connection* self, PyObject* args, PyObject* kwargs) +{ + PyObject* sql; + pysqlite_Statement* statement; + PyObject* weakref; + int rc; + + if (!pysqlite_check_thread(self) || !pysqlite_check_connection(self)) { + return NULL; + } + + if (!PyArg_ParseTuple(args, "O", &sql)) + return NULL; + + _pysqlite_drop_unused_statement_references(self); + + statement = PyObject_New(pysqlite_Statement, &pysqlite_StatementType); + if (!statement) { + return NULL; + } + + statement->db = NULL; + statement->st = NULL; + statement->sql = NULL; + statement->in_use = 0; + statement->in_weakreflist = NULL; + + rc = pysqlite_statement_create(statement, self, sql); + if (rc != SQLITE_OK) { + if (rc == PYSQLITE_TOO_MUCH_SQL) { + PyErr_SetString(pysqlite_Warning, "You can only execute one statement at a time."); + } else if (rc == PYSQLITE_SQL_WRONG_TYPE) { + if (PyErr_ExceptionMatches(PyExc_TypeError)) + PyErr_SetString(pysqlite_Warning, "SQL is of wrong type. Must be string."); + } else { + (void)pysqlite_statement_reset(statement); + _pysqlite_seterror(self->db, NULL); + } + goto error; + } + + weakref = PyWeakref_NewRef((PyObject*)statement, NULL); + if (weakref == NULL) + goto error; + if (PyList_Append(self->statements, weakref) != 0) { + Py_DECREF(weakref); + goto error; + } + Py_DECREF(weakref); + + return (PyObject*)statement; + +error: + Py_DECREF(statement); + return NULL; +} + +PyObject* pysqlite_connection_execute(pysqlite_Connection* self, PyObject* args, PyObject* kwargs) +{ + PyObject* cursor = 0; + PyObject* result = 0; + PyObject* method = 0; + + cursor = _PyObject_CallMethodId((PyObject*)self, &PyId_cursor, ""); + if (!cursor) { + goto error; + } + + method = PyObject_GetAttrString(cursor, "execute"); + if (!method) { + Py_CLEAR(cursor); + goto error; + } + + result = PyObject_CallObject(method, args); + if (!result) { + Py_CLEAR(cursor); + } + +error: + Py_XDECREF(result); + Py_XDECREF(method); + + return cursor; +} + +PyObject* pysqlite_connection_executemany(pysqlite_Connection* self, PyObject* args, PyObject* kwargs) +{ + PyObject* cursor = 0; + PyObject* result = 0; + PyObject* method = 0; + + cursor = _PyObject_CallMethodId((PyObject*)self, &PyId_cursor, ""); + if (!cursor) { + goto error; + } + + method = PyObject_GetAttrString(cursor, "executemany"); + if (!method) { + Py_CLEAR(cursor); + goto error; + } + + result = PyObject_CallObject(method, args); + if (!result) { + Py_CLEAR(cursor); + } + +error: + Py_XDECREF(result); + Py_XDECREF(method); + + return cursor; +} + +PyObject* pysqlite_connection_executescript(pysqlite_Connection* self, PyObject* args, PyObject* kwargs) +{ + PyObject* cursor = 0; + PyObject* result = 0; + PyObject* method = 0; + + cursor = _PyObject_CallMethodId((PyObject*)self, &PyId_cursor, ""); + if (!cursor) { + goto error; + } + + method = PyObject_GetAttrString(cursor, "executescript"); + if (!method) { + Py_CLEAR(cursor); + goto error; + } + + result = PyObject_CallObject(method, args); + if (!result) { + Py_CLEAR(cursor); + } + +error: + Py_XDECREF(result); + Py_XDECREF(method); + + return cursor; +} + +/* ------------------------- COLLATION CODE ------------------------ */ + +static int +pysqlite_collation_callback( + void* context, + int text1_length, const void* text1_data, + int text2_length, const void* text2_data) +{ + PyObject* callback = (PyObject*)context; + PyObject* string1 = 0; + PyObject* string2 = 0; +#ifdef WITH_THREAD + PyGILState_STATE gilstate; +#endif + PyObject* retval = NULL; + long longval; + int result = 0; +#ifdef WITH_THREAD + gilstate = PyGILState_Ensure(); +#endif + + if (PyErr_Occurred()) { + goto finally; + } + + string1 = PyUnicode_FromStringAndSize((const char*)text1_data, text1_length); + string2 = PyUnicode_FromStringAndSize((const char*)text2_data, text2_length); + + if (!string1 || !string2) { + goto finally; /* failed to allocate strings */ + } + + retval = PyObject_CallFunctionObjArgs(callback, string1, string2, NULL); + + if (!retval) { + /* execution failed */ + goto finally; + } + + longval = PyLong_AsLongAndOverflow(retval, &result); + if (longval == -1 && PyErr_Occurred()) { + PyErr_Clear(); + result = 0; + } + else if (!result) { + if (longval > 0) + result = 1; + else if (longval < 0) + result = -1; + } + +finally: + Py_XDECREF(string1); + Py_XDECREF(string2); + Py_XDECREF(retval); +#ifdef WITH_THREAD + PyGILState_Release(gilstate); +#endif + return result; +} + +static PyObject * +pysqlite_connection_interrupt(pysqlite_Connection* self, PyObject* args) +{ + PyObject* retval = NULL; + + if (!pysqlite_check_connection(self)) { + goto finally; + } + + sqlite3_interrupt(self->db); + + Py_INCREF(Py_None); + retval = Py_None; + +finally: + return retval; +} + +/* Function author: Paul Kippes + * Class method of Connection to call the Python function _iterdump + * of the sqlite3 module. + */ +static PyObject * +pysqlite_connection_iterdump(pysqlite_Connection* self, PyObject* args) +{ + PyObject* retval = NULL; + PyObject* module = NULL; + PyObject* module_dict; + PyObject* pyfn_iterdump; + + if (!pysqlite_check_connection(self)) { + goto finally; + } + + module = PyImport_ImportModule("pysqlcipher3.dump"); + if (!module) { + goto finally; + } + + module_dict = PyModule_GetDict(module); + if (!module_dict) { + goto finally; + } + + pyfn_iterdump = PyDict_GetItemString(module_dict, "_iterdump"); + if (!pyfn_iterdump) { + PyErr_SetString(pysqlite_OperationalError, "Failed to obtain _iterdump() reference"); + goto finally; + } + + args = PyTuple_New(1); + if (!args) { + goto finally; + } + Py_INCREF(self); + PyTuple_SetItem(args, 0, (PyObject*)self); + retval = PyObject_CallObject(pyfn_iterdump, args); + +finally: + Py_XDECREF(args); + Py_XDECREF(module); + return retval; +} + +static PyObject * +pysqlite_connection_create_collation(pysqlite_Connection* self, PyObject* args) +{ + PyObject* callable; + PyObject* uppercase_name = 0; + PyObject* name; + PyObject* retval; + Py_ssize_t i, len; + _Py_IDENTIFIER(upper); + char *uppercase_name_str; + int rc; + unsigned int kind; + void *data; + + if (!pysqlite_check_thread(self) || !pysqlite_check_connection(self)) { + goto finally; + } + + if (!PyArg_ParseTuple(args, "O!O:create_collation(name, callback)", &PyUnicode_Type, &name, &callable)) { + goto finally; + } + + uppercase_name = _PyObject_CallMethodId(name, &PyId_upper, ""); + if (!uppercase_name) { + goto finally; + } + + if (PyUnicode_READY(uppercase_name)) + goto finally; + len = PyUnicode_GET_LENGTH(uppercase_name); + kind = PyUnicode_KIND(uppercase_name); + data = PyUnicode_DATA(uppercase_name); + for (i=0; i= '0' && ch <= '9') + || (ch >= 'A' && ch <= 'Z') + || (ch == '_')) + { + continue; + } else { + PyErr_SetString(pysqlite_ProgrammingError, "invalid character in collation name"); + goto finally; + } + } + + uppercase_name_str = _PyUnicode_AsString(uppercase_name); + if (!uppercase_name_str) + goto finally; + + if (callable != Py_None && !PyCallable_Check(callable)) { + PyErr_SetString(PyExc_TypeError, "parameter must be callable"); + goto finally; + } + + if (callable != Py_None) { + if (PyDict_SetItem(self->collations, uppercase_name, callable) == -1) + goto finally; + } else { + if (PyDict_DelItem(self->collations, uppercase_name) == -1) + goto finally; + } + + rc = sqlite3_create_collation(self->db, + uppercase_name_str, + SQLITE_UTF8, + (callable != Py_None) ? callable : NULL, + (callable != Py_None) ? pysqlite_collation_callback : NULL); + if (rc != SQLITE_OK) { + PyDict_DelItem(self->collations, uppercase_name); + _pysqlite_seterror(self->db, NULL); + goto finally; + } + +finally: + Py_XDECREF(uppercase_name); + + if (PyErr_Occurred()) { + retval = NULL; + } else { + Py_INCREF(Py_None); + retval = Py_None; + } + + return retval; +} + +/* Called when the connection is used as a context manager. Returns itself as a + * convenience to the caller. */ +static PyObject * +pysqlite_connection_enter(pysqlite_Connection* self, PyObject* args) +{ + Py_INCREF(self); + return (PyObject*)self; +} + +/** Called when the connection is used as a context manager. If there was any + * exception, a rollback takes place; otherwise we commit. */ +static PyObject * +pysqlite_connection_exit(pysqlite_Connection* self, PyObject* args) +{ + PyObject* exc_type, *exc_value, *exc_tb; + char* method_name; + PyObject* result; + + if (!PyArg_ParseTuple(args, "OOO", &exc_type, &exc_value, &exc_tb)) { + return NULL; + } + + if (exc_type == Py_None && exc_value == Py_None && exc_tb == Py_None) { + method_name = "commit"; + } else { + method_name = "rollback"; + } + + result = PyObject_CallMethod((PyObject*)self, method_name, ""); + if (!result) { + return NULL; + } + Py_DECREF(result); + + Py_RETURN_FALSE; +} + +static char connection_doc[] = +PyDoc_STR("SQLite database connection object."); + +static PyGetSetDef connection_getset[] = { + {"isolation_level", (getter)pysqlite_connection_get_isolation_level, (setter)pysqlite_connection_set_isolation_level}, + {"total_changes", (getter)pysqlite_connection_get_total_changes, (setter)0}, + {NULL} +}; + +static PyMethodDef connection_methods[] = { + {"cursor", (PyCFunction)pysqlite_connection_cursor, METH_VARARGS|METH_KEYWORDS, + PyDoc_STR("Return a cursor for the connection.")}, + {"close", (PyCFunction)pysqlite_connection_close, METH_NOARGS, + PyDoc_STR("Closes the connection.")}, + {"commit", (PyCFunction)pysqlite_connection_commit, METH_NOARGS, + PyDoc_STR("Commit the current transaction.")}, + {"rollback", (PyCFunction)pysqlite_connection_rollback, METH_NOARGS, + PyDoc_STR("Roll back the current transaction.")}, + {"create_function", (PyCFunction)pysqlite_connection_create_function, METH_VARARGS|METH_KEYWORDS, + PyDoc_STR("Creates a new function. Non-standard.")}, + {"create_aggregate", (PyCFunction)pysqlite_connection_create_aggregate, METH_VARARGS|METH_KEYWORDS, + PyDoc_STR("Creates a new aggregate. Non-standard.")}, + {"set_authorizer", (PyCFunction)pysqlite_connection_set_authorizer, METH_VARARGS|METH_KEYWORDS, + PyDoc_STR("Sets authorizer callback. Non-standard.")}, + #ifdef HAVE_LOAD_EXTENSION + {"enable_load_extension", (PyCFunction)pysqlite_enable_load_extension, METH_VARARGS, + PyDoc_STR("Enable dynamic loading of SQLite extension modules. Non-standard.")}, + {"load_extension", (PyCFunction)pysqlite_load_extension, METH_VARARGS, + PyDoc_STR("Load SQLite extension module. Non-standard.")}, + #endif + {"set_progress_handler", (PyCFunction)pysqlite_connection_set_progress_handler, METH_VARARGS|METH_KEYWORDS, + PyDoc_STR("Sets progress handler callback. Non-standard.")}, + {"set_trace_callback", (PyCFunction)pysqlite_connection_set_trace_callback, METH_VARARGS|METH_KEYWORDS, + PyDoc_STR("Sets a trace callback called for each SQL statement (passed as unicode). Non-standard.")}, + {"execute", (PyCFunction)pysqlite_connection_execute, METH_VARARGS, + PyDoc_STR("Executes a SQL statement. Non-standard.")}, + {"executemany", (PyCFunction)pysqlite_connection_executemany, METH_VARARGS, + PyDoc_STR("Repeatedly executes a SQL statement. Non-standard.")}, + {"executescript", (PyCFunction)pysqlite_connection_executescript, METH_VARARGS, + PyDoc_STR("Executes a multiple SQL statements at once. Non-standard.")}, + {"create_collation", (PyCFunction)pysqlite_connection_create_collation, METH_VARARGS, + PyDoc_STR("Creates a collation function. Non-standard.")}, + {"interrupt", (PyCFunction)pysqlite_connection_interrupt, METH_NOARGS, + PyDoc_STR("Abort any pending database operation. Non-standard.")}, + {"iterdump", (PyCFunction)pysqlite_connection_iterdump, METH_NOARGS, + PyDoc_STR("Returns iterator to the dump of the database in an SQL text format. Non-standard.")}, + {"__enter__", (PyCFunction)pysqlite_connection_enter, METH_NOARGS, + PyDoc_STR("For context manager. Non-standard.")}, + {"__exit__", (PyCFunction)pysqlite_connection_exit, METH_VARARGS, + PyDoc_STR("For context manager. Non-standard.")}, + {NULL, NULL} +}; + +static struct PyMemberDef connection_members[] = +{ + {"Warning", T_OBJECT, offsetof(pysqlite_Connection, Warning), READONLY}, + {"Error", T_OBJECT, offsetof(pysqlite_Connection, Error), READONLY}, + {"InterfaceError", T_OBJECT, offsetof(pysqlite_Connection, InterfaceError), READONLY}, + {"DatabaseError", T_OBJECT, offsetof(pysqlite_Connection, DatabaseError), READONLY}, + {"DataError", T_OBJECT, offsetof(pysqlite_Connection, DataError), READONLY}, + {"OperationalError", T_OBJECT, offsetof(pysqlite_Connection, OperationalError), READONLY}, + {"IntegrityError", T_OBJECT, offsetof(pysqlite_Connection, IntegrityError), READONLY}, + {"InternalError", T_OBJECT, offsetof(pysqlite_Connection, InternalError), READONLY}, + {"ProgrammingError", T_OBJECT, offsetof(pysqlite_Connection, ProgrammingError), READONLY}, + {"NotSupportedError", T_OBJECT, offsetof(pysqlite_Connection, NotSupportedError), READONLY}, + {"row_factory", T_OBJECT, offsetof(pysqlite_Connection, row_factory)}, + {"text_factory", T_OBJECT, offsetof(pysqlite_Connection, text_factory)}, + {"in_transaction", T_BOOL, offsetof(pysqlite_Connection, inTransaction), READONLY}, + {NULL} +}; + +PyTypeObject pysqlite_ConnectionType = { + PyVarObject_HEAD_INIT(NULL, 0) + MODULE_NAME ".Connection", /* tp_name */ + sizeof(pysqlite_Connection), /* tp_basicsize */ + 0, /* tp_itemsize */ + (destructor)pysqlite_connection_dealloc, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_reserved */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + (ternaryfunc)pysqlite_connection_call, /* tp_call */ + 0, /* tp_str */ + 0, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, /* tp_flags */ + connection_doc, /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + connection_methods, /* tp_methods */ + connection_members, /* tp_members */ + connection_getset, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + (initproc)pysqlite_connection_init, /* tp_init */ + 0, /* tp_alloc */ + 0, /* tp_new */ + 0 /* tp_free */ +}; + +extern int pysqlite_connection_setup_types(void) +{ + pysqlite_ConnectionType.tp_new = PyType_GenericNew; + return PyType_Ready(&pysqlite_ConnectionType); +} diff --git a/src/pysqlcipher3/c/connection.h b/src/pysqlcipher3/c/connection.h new file mode 100644 index 0000000..9db8901 --- /dev/null +++ b/src/pysqlcipher3/c/connection.h @@ -0,0 +1,132 @@ +/* connection.h - definitions for the connection type + * + * Copyright (C) 2004-2010 Gerhard Häring + * + * This file is part of pysqlite. + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + */ + +#ifndef PYSQLITE_CONNECTION_H +#define PYSQLITE_CONNECTION_H +#include "Python.h" +#include "pythread.h" +#include "structmember.h" + +#include "cache.h" +#include "module.h" + +#include "sqlcipher/sqlite3.h" + +typedef struct +{ + PyObject_HEAD + sqlite3* db; + + /* 1 if we are currently within a transaction, i. e. if a BEGIN has been + * issued */ + char inTransaction; + + /* the type detection mode. Only 0, PARSE_DECLTYPES, PARSE_COLNAMES or a + * bitwise combination thereof makes sense */ + int detect_types; + + /* the timeout value in seconds for database locks */ + double timeout; + + /* for internal use in the timeout handler: when did the timeout handler + * first get called with count=0? */ + double timeout_started; + + /* None for autocommit, otherwise a PyString with the isolation level */ + PyObject* isolation_level; + + /* NULL for autocommit, otherwise a string with the BEGIN statement; will be + * freed in connection destructor */ + char* begin_statement; + + /* 1 if a check should be performed for each API call if the connection is + * used from the same thread it was created in */ + int check_same_thread; + + int initialized; + + /* thread identification of the thread the connection was created in */ + long thread_ident; + + pysqlite_Cache* statement_cache; + + /* Lists of weak references to statements and cursors used within this connection */ + PyObject* statements; + PyObject* cursors; + + /* Counters for how many statements/cursors were created in the connection. May be + * reset to 0 at certain intervals */ + int created_statements; + int created_cursors; + + PyObject* row_factory; + + /* Determines how bytestrings from SQLite are converted to Python objects: + * - PyUnicode_Type: Python Unicode objects are constructed from UTF-8 bytestrings + * - PyBytes_Type: The bytestrings are returned as-is. + * - Any custom callable: Any object returned from the callable called with the bytestring + * as single parameter. + */ + PyObject* text_factory; + + /* remember references to functions/classes used in + * create_function/create/aggregate, use these as dictionary keys, so we + * can keep the total system refcount constant by clearing that dictionary + * in connection_dealloc */ + PyObject* function_pinboard; + + /* a dictionary of registered collation name => collation callable mappings */ + PyObject* collations; + + /* Exception objects */ + PyObject* Warning; + PyObject* Error; + PyObject* InterfaceError; + PyObject* DatabaseError; + PyObject* DataError; + PyObject* OperationalError; + PyObject* IntegrityError; + PyObject* InternalError; + PyObject* ProgrammingError; + PyObject* NotSupportedError; +} pysqlite_Connection; + +extern PyTypeObject pysqlite_ConnectionType; + +PyObject* pysqlite_connection_alloc(PyTypeObject* type, int aware); +void pysqlite_connection_dealloc(pysqlite_Connection* self); +PyObject* pysqlite_connection_cursor(pysqlite_Connection* self, PyObject* args, PyObject* kwargs); +PyObject* pysqlite_connection_close(pysqlite_Connection* self, PyObject* args); +PyObject* _pysqlite_connection_begin(pysqlite_Connection* self); +PyObject* pysqlite_connection_commit(pysqlite_Connection* self, PyObject* args); +PyObject* pysqlite_connection_rollback(pysqlite_Connection* self, PyObject* args); +PyObject* pysqlite_connection_new(PyTypeObject* type, PyObject* args, PyObject* kw); +int pysqlite_connection_init(pysqlite_Connection* self, PyObject* args, PyObject* kwargs); + +int pysqlite_connection_register_cursor(pysqlite_Connection* connection, PyObject* cursor); +int pysqlite_check_thread(pysqlite_Connection* self); +int pysqlite_check_connection(pysqlite_Connection* con); + +int pysqlite_connection_setup_types(void); + +#endif diff --git a/src/pysqlcipher3/c/cursor.c b/src/pysqlcipher3/c/cursor.c new file mode 100644 index 0000000..3a560ad --- /dev/null +++ b/src/pysqlcipher3/c/cursor.c @@ -0,0 +1,1109 @@ +/* cursor.c - the cursor type + * + * Copyright (C) 2004-2010 Gerhard Häring + * + * This file is part of pysqlite. + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + */ + +#include "cursor.h" +#include "module.h" +#include "util.h" + +PyObject* pysqlite_cursor_iternext(pysqlite_Cursor* self); + +static char* errmsg_fetch_across_rollback = "Cursor needed to be reset because of commit/rollback and can no longer be fetched from."; + +static pysqlite_StatementKind detect_statement_type(const char* statement) +{ + char buf[20]; + const char* src; + char* dst; + + src = statement; + /* skip over whitepace */ + while (*src == '\r' || *src == '\n' || *src == ' ' || *src == '\t') { + src++; + } + + if (*src == 0) + return STATEMENT_INVALID; + + dst = buf; + *dst = 0; + while (Py_ISALPHA(*src) && dst - buf < sizeof(buf) - 2) { + *dst++ = Py_TOLOWER(*src++); + } + + *dst = 0; + + if (!strcmp(buf, "select")) { + return STATEMENT_SELECT; + } else if (!strcmp(buf, "insert")) { + return STATEMENT_INSERT; + } else if (!strcmp(buf, "update")) { + return STATEMENT_UPDATE; + } else if (!strcmp(buf, "delete")) { + return STATEMENT_DELETE; + } else if (!strcmp(buf, "replace")) { + return STATEMENT_REPLACE; + } else { + return STATEMENT_OTHER; + } +} + +static int pysqlite_cursor_init(pysqlite_Cursor* self, PyObject* args, PyObject* kwargs) +{ + pysqlite_Connection* connection; + + if (!PyArg_ParseTuple(args, "O!", &pysqlite_ConnectionType, &connection)) + { + return -1; + } + + Py_INCREF(connection); + self->connection = connection; + self->statement = NULL; + self->next_row = NULL; + self->in_weakreflist = NULL; + + self->row_cast_map = PyList_New(0); + if (!self->row_cast_map) { + return -1; + } + + Py_INCREF(Py_None); + self->description = Py_None; + + Py_INCREF(Py_None); + self->lastrowid= Py_None; + + self->arraysize = 1; + self->closed = 0; + self->reset = 0; + + self->rowcount = -1L; + + Py_INCREF(Py_None); + self->row_factory = Py_None; + + if (!pysqlite_check_thread(self->connection)) { + return -1; + } + + if (!pysqlite_connection_register_cursor(connection, (PyObject*)self)) { + return -1; + } + + self->initialized = 1; + + return 0; +} + +static void pysqlite_cursor_dealloc(pysqlite_Cursor* self) +{ + /* Reset the statement if the user has not closed the cursor */ + if (self->statement) { + pysqlite_statement_reset(self->statement); + Py_DECREF(self->statement); + } + + Py_XDECREF(self->connection); + Py_XDECREF(self->row_cast_map); + Py_XDECREF(self->description); + Py_XDECREF(self->lastrowid); + Py_XDECREF(self->row_factory); + Py_XDECREF(self->next_row); + + if (self->in_weakreflist != NULL) { + PyObject_ClearWeakRefs((PyObject*)self); + } + + Py_TYPE(self)->tp_free((PyObject*)self); +} + +PyObject* _pysqlite_get_converter(PyObject* key) +{ + PyObject* upcase_key; + PyObject* retval; + _Py_IDENTIFIER(upper); + + upcase_key = _PyObject_CallMethodId(key, &PyId_upper, ""); + if (!upcase_key) { + return NULL; + } + + retval = PyDict_GetItem(converters, upcase_key); + Py_DECREF(upcase_key); + + return retval; +} + +int pysqlite_build_row_cast_map(pysqlite_Cursor* self) +{ + int i; + const char* type_start = (const char*)-1; + const char* pos; + + const char* colname; + const char* decltype; + PyObject* py_decltype; + PyObject* converter; + PyObject* key; + + if (!self->connection->detect_types) { + return 0; + } + + Py_XDECREF(self->row_cast_map); + self->row_cast_map = PyList_New(0); + + for (i = 0; i < sqlite3_column_count(self->statement->st); i++) { + converter = NULL; + + if (self->connection->detect_types & PARSE_COLNAMES) { + colname = sqlite3_column_name(self->statement->st, i); + if (colname) { + for (pos = colname; *pos != 0; pos++) { + if (*pos == '[') { + type_start = pos + 1; + } else if (*pos == ']' && type_start != (const char*)-1) { + key = PyUnicode_FromStringAndSize(type_start, pos - type_start); + if (!key) { + /* creating a string failed, but it is too complicated + * to propagate the error here, we just assume there is + * no converter and proceed */ + break; + } + + converter = _pysqlite_get_converter(key); + Py_DECREF(key); + break; + } + } + } + } + + if (!converter && self->connection->detect_types & PARSE_DECLTYPES) { + decltype = sqlite3_column_decltype(self->statement->st, i); + if (decltype) { + for (pos = decltype;;pos++) { + /* Converter names are split at '(' and blanks. + * This allows 'INTEGER NOT NULL' to be treated as 'INTEGER' and + * 'NUMBER(10)' to be treated as 'NUMBER', for example. + * In other words, it will work as people expect it to work.*/ + if (*pos == ' ' || *pos == '(' || *pos == 0) { + py_decltype = PyUnicode_FromStringAndSize(decltype, pos - decltype); + if (!py_decltype) { + return -1; + } + break; + } + } + + converter = _pysqlite_get_converter(py_decltype); + Py_DECREF(py_decltype); + } + } + + if (!converter) { + converter = Py_None; + } + + if (PyList_Append(self->row_cast_map, converter) != 0) { + if (converter != Py_None) { + Py_DECREF(converter); + } + Py_CLEAR(self->row_cast_map); + + return -1; + } + } + + return 0; +} + +PyObject* _pysqlite_build_column_name(const char* colname) +{ + const char* pos; + + if (!colname) { + Py_INCREF(Py_None); + return Py_None; + } + + for (pos = colname;; pos++) { + if (*pos == 0 || *pos == '[') { + if ((*pos == '[') && (pos > colname) && (*(pos-1) == ' ')) { + pos--; + } + return PyUnicode_FromStringAndSize(colname, pos - colname); + } + } +} + +/* + * Returns a row from the currently active SQLite statement + * + * Precondidition: + * - sqlite3_step() has been called before and it returned SQLITE_ROW. + */ +PyObject* _pysqlite_fetch_one_row(pysqlite_Cursor* self) +{ + int i, numcols; + PyObject* row; + PyObject* item = NULL; + int coltype; + PyObject* converter; + PyObject* converted; + Py_ssize_t nbytes; + PyObject* buffer; + const char* val_str; + char buf[200]; + const char* colname; + PyObject* buf_bytes; + PyObject* error_obj; + + if (self->reset) { + PyErr_SetString(pysqlite_InterfaceError, errmsg_fetch_across_rollback); + return NULL; + } + + Py_BEGIN_ALLOW_THREADS + numcols = sqlite3_data_count(self->statement->st); + Py_END_ALLOW_THREADS + + row = PyTuple_New(numcols); + if (!row) + return NULL; + + for (i = 0; i < numcols; i++) { + if (self->connection->detect_types) { + converter = PyList_GetItem(self->row_cast_map, i); + if (!converter) { + converter = Py_None; + } + } else { + converter = Py_None; + } + + if (converter != Py_None) { + nbytes = sqlite3_column_bytes(self->statement->st, i); + val_str = (const char*)sqlite3_column_blob(self->statement->st, i); + if (!val_str) { + Py_INCREF(Py_None); + converted = Py_None; + } else { + item = PyBytes_FromStringAndSize(val_str, nbytes); + if (!item) + goto error; + converted = PyObject_CallFunction(converter, "O", item); + Py_DECREF(item); + if (!converted) + break; + } + } else { + Py_BEGIN_ALLOW_THREADS + coltype = sqlite3_column_type(self->statement->st, i); + Py_END_ALLOW_THREADS + if (coltype == SQLITE_NULL) { + Py_INCREF(Py_None); + converted = Py_None; + } else if (coltype == SQLITE_INTEGER) { + converted = _pysqlite_long_from_int64(sqlite3_column_int64(self->statement->st, i)); + } else if (coltype == SQLITE_FLOAT) { + converted = PyFloat_FromDouble(sqlite3_column_double(self->statement->st, i)); + } else if (coltype == SQLITE_TEXT) { + val_str = (const char*)sqlite3_column_text(self->statement->st, i); + nbytes = sqlite3_column_bytes(self->statement->st, i); + if (self->connection->text_factory == (PyObject*)&PyUnicode_Type) { + converted = PyUnicode_FromStringAndSize(val_str, nbytes); + if (!converted) { +#ifdef Py_DEBUG + /* in debug mode, type_call() fails with an assertion + error if an exception is set when it is called */ + PyErr_Clear(); +#endif + colname = sqlite3_column_name(self->statement->st, i); + if (!colname) { + colname = ""; + } + PyOS_snprintf(buf, sizeof(buf) - 1, "Could not decode to UTF-8 column '%s' with text '%s'", + colname , val_str); + buf_bytes = PyByteArray_FromStringAndSize(buf, strlen(buf)); + if (!buf_bytes) { + PyErr_SetString(pysqlite_OperationalError, "Could not decode to UTF-8"); + } else { + error_obj = PyUnicode_FromEncodedObject(buf_bytes, "ascii", "replace"); + if (!error_obj) { + PyErr_SetString(pysqlite_OperationalError, "Could not decode to UTF-8"); + } else { + PyErr_SetObject(pysqlite_OperationalError, error_obj); + Py_DECREF(error_obj); + } + Py_DECREF(buf_bytes); + } + } + } else if (self->connection->text_factory == (PyObject*)&PyBytes_Type) { + converted = PyBytes_FromStringAndSize(val_str, nbytes); + } else if (self->connection->text_factory == (PyObject*)&PyByteArray_Type) { + converted = PyByteArray_FromStringAndSize(val_str, nbytes); + } else { + converted = PyObject_CallFunction(self->connection->text_factory, "y#", val_str, nbytes); + } + } else { + /* coltype == SQLITE_BLOB */ + nbytes = sqlite3_column_bytes(self->statement->st, i); + buffer = PyBytes_FromStringAndSize( + sqlite3_column_blob(self->statement->st, i), nbytes); + if (!buffer) + break; + converted = buffer; + } + } + + if (converted) { + PyTuple_SetItem(row, i, converted); + } else { + Py_INCREF(Py_None); + PyTuple_SetItem(row, i, Py_None); + } + } + + if (PyErr_Occurred()) + goto error; + + return row; + +error: + Py_DECREF(row); + return NULL; +} + +/* + * Checks if a cursor object is usable. + * + * 0 => error; 1 => ok + */ +static int check_cursor(pysqlite_Cursor* cur) +{ + if (!cur->initialized) { + PyErr_SetString(pysqlite_ProgrammingError, "Base Cursor.__init__ not called."); + return 0; + } + + if (cur->closed) { + PyErr_SetString(pysqlite_ProgrammingError, "Cannot operate on a closed cursor."); + return 0; + } + + if (cur->locked) { + PyErr_SetString(pysqlite_ProgrammingError, "Recursive use of cursors not allowed."); + return 0; + } + + return pysqlite_check_thread(cur->connection) && pysqlite_check_connection(cur->connection); +} + +PyObject* _pysqlite_query_execute(pysqlite_Cursor* self, int multiple, PyObject* args) +{ + PyObject* operation; + const char* operation_cstr; + Py_ssize_t operation_len; + PyObject* parameters_list = NULL; + PyObject* parameters_iter = NULL; + PyObject* parameters = NULL; + int i; + int rc; + PyObject* func_args; + PyObject* result; + int numcols; + int statement_type; + PyObject* descriptor; + PyObject* second_argument = NULL; + + if (!check_cursor(self)) { + goto error; + } + + self->locked = 1; + self->reset = 0; + + Py_CLEAR(self->next_row); + + if (multiple) { + /* executemany() */ + if (!PyArg_ParseTuple(args, "OO", &operation, &second_argument)) { + goto error; + } + + if (!PyUnicode_Check(operation)) { + PyErr_SetString(PyExc_ValueError, "operation parameter must be str"); + goto error; + } + + if (PyIter_Check(second_argument)) { + /* iterator */ + Py_INCREF(second_argument); + parameters_iter = second_argument; + } else { + /* sequence */ + parameters_iter = PyObject_GetIter(second_argument); + if (!parameters_iter) { + goto error; + } + } + } else { + /* execute() */ + if (!PyArg_ParseTuple(args, "O|O", &operation, &second_argument)) { + goto error; + } + + if (!PyUnicode_Check(operation)) { + PyErr_SetString(PyExc_ValueError, "operation parameter must be str"); + goto error; + } + + parameters_list = PyList_New(0); + if (!parameters_list) { + goto error; + } + + if (second_argument == NULL) { + second_argument = PyTuple_New(0); + if (!second_argument) { + goto error; + } + } else { + Py_INCREF(second_argument); + } + if (PyList_Append(parameters_list, second_argument) != 0) { + Py_DECREF(second_argument); + goto error; + } + Py_DECREF(second_argument); + + parameters_iter = PyObject_GetIter(parameters_list); + if (!parameters_iter) { + goto error; + } + } + + if (self->statement != NULL) { + /* There is an active statement */ + pysqlite_statement_reset(self->statement); + } + + operation_cstr = PyUnicode_AsUTF8AndSize(operation, &operation_len); + if (operation_cstr == NULL) + goto error; + + /* reset description and rowcount */ + Py_DECREF(self->description); + Py_INCREF(Py_None); + self->description = Py_None; + self->rowcount = -1L; + + func_args = PyTuple_New(1); + if (!func_args) { + goto error; + } + Py_INCREF(operation); + if (PyTuple_SetItem(func_args, 0, operation) != 0) { + goto error; + } + + if (self->statement) { + (void)pysqlite_statement_reset(self->statement); + Py_DECREF(self->statement); + } + + self->statement = (pysqlite_Statement*)pysqlite_cache_get(self->connection->statement_cache, func_args); + Py_DECREF(func_args); + + if (!self->statement) { + goto error; + } + + if (self->statement->in_use) { + Py_DECREF(self->statement); + self->statement = PyObject_New(pysqlite_Statement, &pysqlite_StatementType); + if (!self->statement) { + goto error; + } + rc = pysqlite_statement_create(self->statement, self->connection, operation); + if (rc != SQLITE_OK) { + Py_CLEAR(self->statement); + goto error; + } + } + + pysqlite_statement_reset(self->statement); + pysqlite_statement_mark_dirty(self->statement); + + statement_type = detect_statement_type(operation_cstr); + if (self->connection->begin_statement) { + switch (statement_type) { + case STATEMENT_UPDATE: + case STATEMENT_DELETE: + case STATEMENT_INSERT: + case STATEMENT_REPLACE: + if (!self->connection->inTransaction) { + result = _pysqlite_connection_begin(self->connection); + if (!result) { + goto error; + } + Py_DECREF(result); + } + break; + case STATEMENT_OTHER: + /* it's a DDL statement or something similar + - we better COMMIT first so it works for all cases */ + if (self->connection->inTransaction) { + result = pysqlite_connection_commit(self->connection, NULL); + if (!result) { + goto error; + } + Py_DECREF(result); + } + break; + case STATEMENT_SELECT: + if (multiple) { + PyErr_SetString(pysqlite_ProgrammingError, + "You cannot execute SELECT statements in executemany()."); + goto error; + } + break; + } + } + + + while (1) { + parameters = PyIter_Next(parameters_iter); + if (!parameters) { + break; + } + + pysqlite_statement_mark_dirty(self->statement); + + pysqlite_statement_bind_parameters(self->statement, parameters); + if (PyErr_Occurred()) { + goto error; + } + + /* Keep trying the SQL statement until the schema stops changing. */ + while (1) { + /* Actually execute the SQL statement. */ + rc = pysqlite_step(self->statement->st, self->connection); + if (PyErr_Occurred()) { + (void)pysqlite_statement_reset(self->statement); + goto error; + } + if (rc == SQLITE_DONE || rc == SQLITE_ROW) { + /* If it worked, let's get out of the loop */ + break; + } + /* Something went wrong. Re-set the statement and try again. */ + rc = pysqlite_statement_reset(self->statement); + if (rc == SQLITE_SCHEMA) { + /* If this was a result of the schema changing, let's try + again. */ + rc = pysqlite_statement_recompile(self->statement, parameters); + if (rc == SQLITE_OK) { + continue; + } else { + /* If the database gave us an error, promote it to Python. */ + (void)pysqlite_statement_reset(self->statement); + _pysqlite_seterror(self->connection->db, NULL); + goto error; + } + } else { + if (PyErr_Occurred()) { + /* there was an error that occurred in a user-defined callback */ + if (_enable_callback_tracebacks) { + PyErr_Print(); + } else { + PyErr_Clear(); + } + } + (void)pysqlite_statement_reset(self->statement); + _pysqlite_seterror(self->connection->db, NULL); + goto error; + } + } + + if (pysqlite_build_row_cast_map(self) != 0) { + PyErr_SetString(pysqlite_OperationalError, "Error while building row_cast_map"); + goto error; + } + + if (rc == SQLITE_ROW || (rc == SQLITE_DONE && statement_type == STATEMENT_SELECT)) { + if (self->description == Py_None) { + Py_BEGIN_ALLOW_THREADS + numcols = sqlite3_column_count(self->statement->st); + Py_END_ALLOW_THREADS + + Py_DECREF(self->description); + self->description = PyTuple_New(numcols); + if (!self->description) { + goto error; + } + for (i = 0; i < numcols; i++) { + descriptor = PyTuple_New(7); + if (!descriptor) { + goto error; + } + PyTuple_SetItem(descriptor, 0, _pysqlite_build_column_name(sqlite3_column_name(self->statement->st, i))); + Py_INCREF(Py_None); PyTuple_SetItem(descriptor, 1, Py_None); + Py_INCREF(Py_None); PyTuple_SetItem(descriptor, 2, Py_None); + Py_INCREF(Py_None); PyTuple_SetItem(descriptor, 3, Py_None); + Py_INCREF(Py_None); PyTuple_SetItem(descriptor, 4, Py_None); + Py_INCREF(Py_None); PyTuple_SetItem(descriptor, 5, Py_None); + Py_INCREF(Py_None); PyTuple_SetItem(descriptor, 6, Py_None); + PyTuple_SetItem(self->description, i, descriptor); + } + } + } + + if (rc == SQLITE_ROW) { + if (multiple) { + PyErr_SetString(pysqlite_ProgrammingError, "executemany() can only execute DML statements."); + goto error; + } + + self->next_row = _pysqlite_fetch_one_row(self); + if (self->next_row == NULL) + goto error; + } else if (rc == SQLITE_DONE && !multiple) { + pysqlite_statement_reset(self->statement); + Py_CLEAR(self->statement); + } + + switch (statement_type) { + case STATEMENT_UPDATE: + case STATEMENT_DELETE: + case STATEMENT_INSERT: + case STATEMENT_REPLACE: + if (self->rowcount == -1L) { + self->rowcount = 0L; + } + self->rowcount += (long)sqlite3_changes(self->connection->db); + } + + Py_DECREF(self->lastrowid); + if (!multiple && statement_type == STATEMENT_INSERT) { + sqlite_int64 lastrowid; + Py_BEGIN_ALLOW_THREADS + lastrowid = sqlite3_last_insert_rowid(self->connection->db); + Py_END_ALLOW_THREADS + self->lastrowid = _pysqlite_long_from_int64(lastrowid); + } else { + Py_INCREF(Py_None); + self->lastrowid = Py_None; + } + + if (multiple) { + pysqlite_statement_reset(self->statement); + } + Py_XDECREF(parameters); + } + +error: + /* just to be sure (implicit ROLLBACKs with ON CONFLICT ROLLBACK/OR + * ROLLBACK could have happened */ + #ifdef SQLITE_VERSION_NUMBER + #if SQLITE_VERSION_NUMBER >= 3002002 + if (self->connection && self->connection->db) + self->connection->inTransaction = !sqlite3_get_autocommit(self->connection->db); + #endif + #endif + + Py_XDECREF(parameters); + Py_XDECREF(parameters_iter); + Py_XDECREF(parameters_list); + + self->locked = 0; + + if (PyErr_Occurred()) { + self->rowcount = -1L; + return NULL; + } else { + Py_INCREF(self); + return (PyObject*)self; + } +} + +PyObject* pysqlite_cursor_execute(pysqlite_Cursor* self, PyObject* args) +{ + return _pysqlite_query_execute(self, 0, args); +} + +PyObject* pysqlite_cursor_executemany(pysqlite_Cursor* self, PyObject* args) +{ + return _pysqlite_query_execute(self, 1, args); +} + +PyObject* pysqlite_cursor_executescript(pysqlite_Cursor* self, PyObject* args) +{ + PyObject* script_obj; + PyObject* script_str = NULL; + const char* script_cstr; + sqlite3_stmt* statement; + int rc; + PyObject* result; + + if (!PyArg_ParseTuple(args, "O", &script_obj)) { + return NULL; + } + + if (!check_cursor(self)) { + return NULL; + } + + self->reset = 0; + + if (PyUnicode_Check(script_obj)) { + script_cstr = _PyUnicode_AsString(script_obj); + if (!script_cstr) { + return NULL; + } + } else { + PyErr_SetString(PyExc_ValueError, "script argument must be unicode."); + return NULL; + } + + /* commit first */ + result = pysqlite_connection_commit(self->connection, NULL); + if (!result) { + goto error; + } + Py_DECREF(result); + + while (1) { + Py_BEGIN_ALLOW_THREADS + rc = sqlite3_prepare(self->connection->db, + script_cstr, + -1, + &statement, + &script_cstr); + Py_END_ALLOW_THREADS + if (rc != SQLITE_OK) { + _pysqlite_seterror(self->connection->db, NULL); + goto error; + } + + /* execute statement, and ignore results of SELECT statements */ + rc = SQLITE_ROW; + while (rc == SQLITE_ROW) { + rc = pysqlite_step(statement, self->connection); + if (PyErr_Occurred()) { + (void)sqlite3_finalize(statement); + goto error; + } + } + + if (rc != SQLITE_DONE) { + (void)sqlite3_finalize(statement); + _pysqlite_seterror(self->connection->db, NULL); + goto error; + } + + rc = sqlite3_finalize(statement); + if (rc != SQLITE_OK) { + _pysqlite_seterror(self->connection->db, NULL); + goto error; + } + + if (*script_cstr == (char)0) { + break; + } + } + +error: + Py_XDECREF(script_str); + + if (PyErr_Occurred()) { + return NULL; + } else { + Py_INCREF(self); + return (PyObject*)self; + } +} + +PyObject* pysqlite_cursor_getiter(pysqlite_Cursor *self) +{ + Py_INCREF(self); + return (PyObject*)self; +} + +PyObject* pysqlite_cursor_iternext(pysqlite_Cursor *self) +{ + PyObject* next_row_tuple; + PyObject* next_row; + int rc; + + if (!check_cursor(self)) { + return NULL; + } + + if (self->reset) { + PyErr_SetString(pysqlite_InterfaceError, errmsg_fetch_across_rollback); + return NULL; + } + + if (!self->next_row) { + if (self->statement) { + (void)pysqlite_statement_reset(self->statement); + Py_CLEAR(self->statement); + } + return NULL; + } + + next_row_tuple = self->next_row; + assert(next_row_tuple != NULL); + self->next_row = NULL; + + if (self->row_factory != Py_None) { + next_row = PyObject_CallFunction(self->row_factory, "OO", self, next_row_tuple); + if (next_row == NULL) { + self->next_row = next_row_tuple; + return NULL; + } + Py_DECREF(next_row_tuple); + } else { + next_row = next_row_tuple; + } + + if (self->statement) { + rc = pysqlite_step(self->statement->st, self->connection); + if (PyErr_Occurred()) { + (void)pysqlite_statement_reset(self->statement); + Py_DECREF(next_row); + return NULL; + } + if (rc != SQLITE_DONE && rc != SQLITE_ROW) { + (void)pysqlite_statement_reset(self->statement); + Py_DECREF(next_row); + _pysqlite_seterror(self->connection->db, NULL); + return NULL; + } + + if (rc == SQLITE_ROW) { + self->next_row = _pysqlite_fetch_one_row(self); + if (self->next_row == NULL) { + (void)pysqlite_statement_reset(self->statement); + return NULL; + } + } + } + + return next_row; +} + +PyObject* pysqlite_cursor_fetchone(pysqlite_Cursor* self, PyObject* args) +{ + PyObject* row; + + row = pysqlite_cursor_iternext(self); + if (!row && !PyErr_Occurred()) { + Py_INCREF(Py_None); + return Py_None; + } + + return row; +} + +PyObject* pysqlite_cursor_fetchmany(pysqlite_Cursor* self, PyObject* args, PyObject* kwargs) +{ + static char *kwlist[] = {"size", NULL, NULL}; + + PyObject* row; + PyObject* list; + int maxrows = self->arraysize; + int counter = 0; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|i:fetchmany", kwlist, &maxrows)) { + return NULL; + } + + list = PyList_New(0); + if (!list) { + return NULL; + } + + /* just make sure we enter the loop */ + row = Py_None; + + while (row) { + row = pysqlite_cursor_iternext(self); + if (row) { + PyList_Append(list, row); + Py_DECREF(row); + } else { + break; + } + + if (++counter == maxrows) { + break; + } + } + + if (PyErr_Occurred()) { + Py_DECREF(list); + return NULL; + } else { + return list; + } +} + +PyObject* pysqlite_cursor_fetchall(pysqlite_Cursor* self, PyObject* args) +{ + PyObject* row; + PyObject* list; + + list = PyList_New(0); + if (!list) { + return NULL; + } + + /* just make sure we enter the loop */ + row = (PyObject*)Py_None; + + while (row) { + row = pysqlite_cursor_iternext(self); + if (row) { + PyList_Append(list, row); + Py_DECREF(row); + } + } + + if (PyErr_Occurred()) { + Py_DECREF(list); + return NULL; + } else { + return list; + } +} + +PyObject* pysqlite_noop(pysqlite_Connection* self, PyObject* args) +{ + /* don't care, return None */ + Py_INCREF(Py_None); + return Py_None; +} + +PyObject* pysqlite_cursor_close(pysqlite_Cursor* self, PyObject* args) +{ + if (!pysqlite_check_thread(self->connection) || !pysqlite_check_connection(self->connection)) { + return NULL; + } + + if (self->statement) { + (void)pysqlite_statement_reset(self->statement); + Py_CLEAR(self->statement); + } + + self->closed = 1; + + Py_INCREF(Py_None); + return Py_None; +} + +static PyMethodDef cursor_methods[] = { + {"execute", (PyCFunction)pysqlite_cursor_execute, METH_VARARGS, + PyDoc_STR("Executes a SQL statement.")}, + {"executemany", (PyCFunction)pysqlite_cursor_executemany, METH_VARARGS, + PyDoc_STR("Repeatedly executes a SQL statement.")}, + {"executescript", (PyCFunction)pysqlite_cursor_executescript, METH_VARARGS, + PyDoc_STR("Executes a multiple SQL statements at once. Non-standard.")}, + {"fetchone", (PyCFunction)pysqlite_cursor_fetchone, METH_NOARGS, + PyDoc_STR("Fetches one row from the resultset.")}, + {"fetchmany", (PyCFunction)pysqlite_cursor_fetchmany, METH_VARARGS|METH_KEYWORDS, + PyDoc_STR("Fetches several rows from the resultset.")}, + {"fetchall", (PyCFunction)pysqlite_cursor_fetchall, METH_NOARGS, + PyDoc_STR("Fetches all rows from the resultset.")}, + {"close", (PyCFunction)pysqlite_cursor_close, METH_NOARGS, + PyDoc_STR("Closes the cursor.")}, + {"setinputsizes", (PyCFunction)pysqlite_noop, METH_VARARGS, + PyDoc_STR("Required by DB-API. Does nothing in pysqlite.")}, + {"setoutputsize", (PyCFunction)pysqlite_noop, METH_VARARGS, + PyDoc_STR("Required by DB-API. Does nothing in pysqlite.")}, + {NULL, NULL} +}; + +static struct PyMemberDef cursor_members[] = +{ + {"connection", T_OBJECT, offsetof(pysqlite_Cursor, connection), READONLY}, + {"description", T_OBJECT, offsetof(pysqlite_Cursor, description), READONLY}, + {"arraysize", T_INT, offsetof(pysqlite_Cursor, arraysize), 0}, + {"lastrowid", T_OBJECT, offsetof(pysqlite_Cursor, lastrowid), READONLY}, + {"rowcount", T_LONG, offsetof(pysqlite_Cursor, rowcount), READONLY}, + {"row_factory", T_OBJECT, offsetof(pysqlite_Cursor, row_factory), 0}, + {NULL} +}; + +static char cursor_doc[] = +PyDoc_STR("SQLite database cursor class."); + +PyTypeObject pysqlite_CursorType = { + PyVarObject_HEAD_INIT(NULL, 0) + MODULE_NAME ".Cursor", /* tp_name */ + sizeof(pysqlite_Cursor), /* tp_basicsize */ + 0, /* tp_itemsize */ + (destructor)pysqlite_cursor_dealloc, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_reserved */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + 0, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, /* tp_flags */ + cursor_doc, /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + offsetof(pysqlite_Cursor, in_weakreflist), /* tp_weaklistoffset */ + (getiterfunc)pysqlite_cursor_getiter, /* tp_iter */ + (iternextfunc)pysqlite_cursor_iternext, /* tp_iternext */ + cursor_methods, /* tp_methods */ + cursor_members, /* tp_members */ + 0, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + (initproc)pysqlite_cursor_init, /* tp_init */ + 0, /* tp_alloc */ + 0, /* tp_new */ + 0 /* tp_free */ +}; + +extern int pysqlite_cursor_setup_types(void) +{ + pysqlite_CursorType.tp_new = PyType_GenericNew; + return PyType_Ready(&pysqlite_CursorType); +} diff --git a/src/pysqlcipher3/c/cursor.h b/src/pysqlcipher3/c/cursor.h new file mode 100644 index 0000000..118ba38 --- /dev/null +++ b/src/pysqlcipher3/c/cursor.h @@ -0,0 +1,75 @@ +/* cursor.h - definitions for the cursor type + * + * Copyright (C) 2004-2010 Gerhard Häring + * + * This file is part of pysqlite. + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + */ + +#ifndef PYSQLITE_CURSOR_H +#define PYSQLITE_CURSOR_H +#include "Python.h" + +#include "statement.h" +#include "connection.h" +#include "module.h" + +typedef struct +{ + PyObject_HEAD + pysqlite_Connection* connection; + PyObject* description; + PyObject* row_cast_map; + int arraysize; + PyObject* lastrowid; + long rowcount; + PyObject* row_factory; + pysqlite_Statement* statement; + int closed; + int reset; + int locked; + int initialized; + + /* the next row to be returned, NULL if no next row available */ + PyObject* next_row; + + PyObject* in_weakreflist; /* List of weak references */ +} pysqlite_Cursor; + +typedef enum { + STATEMENT_INVALID, STATEMENT_INSERT, STATEMENT_DELETE, + STATEMENT_UPDATE, STATEMENT_REPLACE, STATEMENT_SELECT, + STATEMENT_OTHER +} pysqlite_StatementKind; + +extern PyTypeObject pysqlite_CursorType; + +PyObject* pysqlite_cursor_execute(pysqlite_Cursor* self, PyObject* args); +PyObject* pysqlite_cursor_executemany(pysqlite_Cursor* self, PyObject* args); +PyObject* pysqlite_cursor_getiter(pysqlite_Cursor *self); +PyObject* pysqlite_cursor_iternext(pysqlite_Cursor *self); +PyObject* pysqlite_cursor_fetchone(pysqlite_Cursor* self, PyObject* args); +PyObject* pysqlite_cursor_fetchmany(pysqlite_Cursor* self, PyObject* args, PyObject* kwargs); +PyObject* pysqlite_cursor_fetchall(pysqlite_Cursor* self, PyObject* args); +PyObject* pysqlite_noop(pysqlite_Connection* self, PyObject* args); +PyObject* pysqlite_cursor_close(pysqlite_Cursor* self, PyObject* args); + +int pysqlite_cursor_setup_types(void); + +#define UNKNOWN (-1) +#endif diff --git a/src/pysqlcipher3/c/microprotocols.c b/src/pysqlcipher3/c/microprotocols.c new file mode 100644 index 0000000..2261b80 --- /dev/null +++ b/src/pysqlcipher3/c/microprotocols.c @@ -0,0 +1,146 @@ +/* microprotocols.c - minimalist and non-validating protocols implementation + * + * Copyright (C) 2003-2004 Federico Di Gregorio + * + * This file is part of psycopg and was adapted for pysqlite. Federico Di + * Gregorio gave the permission to use it within pysqlite under the following + * license: + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + */ + +#include +#include + +#include "cursor.h" +#include "microprotocols.h" +#include "prepare_protocol.h" + + +/** the adapters registry **/ + +PyObject *psyco_adapters; + +/* pysqlite_microprotocols_init - initialize the adapters dictionary */ + +int +pysqlite_microprotocols_init(PyObject *dict) +{ + /* create adapters dictionary and put it in module namespace */ + if ((psyco_adapters = PyDict_New()) == NULL) { + return -1; + } + + return PyDict_SetItemString(dict, "adapters", psyco_adapters); +} + + +/* pysqlite_microprotocols_add - add a reverse type-caster to the dictionary */ + +int +pysqlite_microprotocols_add(PyTypeObject *type, PyObject *proto, PyObject *cast) +{ + PyObject* key; + int rc; + + if (proto == NULL) proto = (PyObject*)&pysqlite_PrepareProtocolType; + + key = Py_BuildValue("(OO)", (PyObject*)type, proto); + if (!key) { + return -1; + } + + rc = PyDict_SetItem(psyco_adapters, key, cast); + Py_DECREF(key); + + return rc; +} + +/* pysqlite_microprotocols_adapt - adapt an object to the built-in protocol */ + +PyObject * +pysqlite_microprotocols_adapt(PyObject *obj, PyObject *proto, PyObject *alt) +{ + PyObject *adapter, *key; + + /* we don't check for exact type conformance as specified in PEP 246 + because the pysqlite_PrepareProtocolType type is abstract and there is no + way to get a quotable object to be its instance */ + + /* look for an adapter in the registry */ + key = Py_BuildValue("(OO)", (PyObject*)obj->ob_type, proto); + if (!key) { + return NULL; + } + adapter = PyDict_GetItem(psyco_adapters, key); + Py_DECREF(key); + if (adapter) { + PyObject *adapted = PyObject_CallFunctionObjArgs(adapter, obj, NULL); + return adapted; + } + + /* try to have the protocol adapt this object*/ + if (PyObject_HasAttrString(proto, "__adapt__")) { + _Py_IDENTIFIER(__adapt__); + PyObject *adapted = _PyObject_CallMethodId(proto, &PyId___adapt__, "O", obj); + + if (adapted) { + if (adapted != Py_None) { + return adapted; + } else { + Py_DECREF(adapted); + } + } + + if (PyErr_Occurred() && !PyErr_ExceptionMatches(PyExc_TypeError)) + return NULL; + } + + /* and finally try to have the object adapt itself */ + if (PyObject_HasAttrString(obj, "__conform__")) { + _Py_IDENTIFIER(__conform__); + PyObject *adapted = _PyObject_CallMethodId(obj, &PyId___conform__,"O", proto); + + if (adapted) { + if (adapted != Py_None) { + return adapted; + } else { + Py_DECREF(adapted); + } + } + + if (PyErr_Occurred() && !PyErr_ExceptionMatches(PyExc_TypeError)) { + return NULL; + } + } + + /* else set the right exception and return NULL */ + PyErr_SetString(pysqlite_ProgrammingError, "can't adapt"); + return NULL; +} + +/** module-level functions **/ + +PyObject * +pysqlite_adapt(pysqlite_Cursor *self, PyObject *args) +{ + PyObject *obj, *alt = NULL; + PyObject *proto = (PyObject*)&pysqlite_PrepareProtocolType; + + if (!PyArg_ParseTuple(args, "O|OO", &obj, &proto, &alt)) return NULL; + return pysqlite_microprotocols_adapt(obj, proto, alt); +} diff --git a/src/pysqlcipher3/c/microprotocols.h b/src/pysqlcipher3/c/microprotocols.h new file mode 100644 index 0000000..3a9944f --- /dev/null +++ b/src/pysqlcipher3/c/microprotocols.h @@ -0,0 +1,55 @@ +/* microprotocols.c - definitions for minimalist and non-validating protocols + * + * Copyright (C) 2003-2004 Federico Di Gregorio + * + * This file is part of psycopg and was adapted for pysqlite. Federico Di + * Gregorio gave the permission to use it within pysqlite under the following + * license: + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + */ + +#ifndef PSYCOPG_MICROPROTOCOLS_H +#define PSYCOPG_MICROPROTOCOLS_H 1 + +#include + +/** adapters registry **/ + +extern PyObject *psyco_adapters; + +/** the names of the three mandatory methods **/ + +#define MICROPROTOCOLS_GETQUOTED_NAME "getquoted" +#define MICROPROTOCOLS_GETSTRING_NAME "getstring" +#define MICROPROTOCOLS_GETBINARY_NAME "getbinary" + +/** exported functions **/ + +/* used by module.c to init the microprotocols system */ +extern int pysqlite_microprotocols_init(PyObject *dict); +extern int pysqlite_microprotocols_add( + PyTypeObject *type, PyObject *proto, PyObject *cast); +extern PyObject *pysqlite_microprotocols_adapt( + PyObject *obj, PyObject *proto, PyObject *alt); + +extern PyObject * + pysqlite_adapt(pysqlite_Cursor* self, PyObject *args); +#define pysqlite_adapt_doc \ + "adapt(obj, protocol, alternate) -> adapt obj to given protocol. Non-standard." + +#endif /* !defined(PSYCOPG_MICROPROTOCOLS_H) */ diff --git a/src/pysqlcipher3/c/module.c b/src/pysqlcipher3/c/module.c new file mode 100644 index 0000000..7a7e860 --- /dev/null +++ b/src/pysqlcipher3/c/module.c @@ -0,0 +1,483 @@ +/* module.c - the module itself + * + * Copyright (C) 2004-2010 Gerhard Häring + * + * This file is part of pysqlite. + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + */ + +#include "connection.h" +#include "statement.h" +#include "cursor.h" +#include "cache.h" +#include "prepare_protocol.h" +#include "microprotocols.h" +#include "row.h" + +#if SQLITE_VERSION_NUMBER >= 3003003 +#define HAVE_SHARED_CACHE +#endif + +/* static objects at module-level */ + +PyObject* pysqlite_Error, *pysqlite_Warning, *pysqlite_InterfaceError, *pysqlite_DatabaseError, + *pysqlite_InternalError, *pysqlite_OperationalError, *pysqlite_ProgrammingError, + *pysqlite_IntegrityError, *pysqlite_DataError, *pysqlite_NotSupportedError; + +PyObject* converters; +int _enable_callback_tracebacks; +int pysqlite_BaseTypeAdapted; + +static PyObject* module_connect(PyObject* self, PyObject* args, PyObject* + kwargs) +{ + /* Python seems to have no way of extracting a single keyword-arg at + * C-level, so this code is redundant with the one in connection_init in + * connection.c and must always be copied from there ... */ + + static char *kwlist[] = { + "database", "timeout", "detect_types", "isolation_level", + "check_same_thread", "factory", "cached_statements", "uri", + NULL + }; + char* database; + int detect_types = 0; + PyObject* isolation_level; + PyObject* factory = NULL; + int check_same_thread = 1; + int cached_statements; + int uri = 0; + double timeout = 5.0; + + PyObject* result; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s|diOiOip", kwlist, + &database, &timeout, &detect_types, + &isolation_level, &check_same_thread, + &factory, &cached_statements, &uri)) + { + return NULL; + } + + if (factory == NULL) { + factory = (PyObject*)&pysqlite_ConnectionType; + } + + result = PyObject_Call(factory, args, kwargs); + + return result; +} + +PyDoc_STRVAR(module_connect_doc, +"connect(database[, timeout, detect_types, isolation_level,\n\ + check_same_thread, factory, cached_statements, uri])\n\ +\n\ +Opens a connection to the SQLite database file *database*. You can use\n\ +\":memory:\" to open a database connection to a database that resides in\n\ +RAM instead of on disk."); + +static PyObject* module_complete(PyObject* self, PyObject* args, PyObject* + kwargs) +{ + static char *kwlist[] = {"statement", NULL, NULL}; + char* statement; + + PyObject* result; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s", kwlist, &statement)) + { + return NULL; + } + + if (sqlite3_complete(statement)) { + result = Py_True; + } else { + result = Py_False; + } + + Py_INCREF(result); + + return result; +} + +PyDoc_STRVAR(module_complete_doc, +"complete_statement(sql)\n\ +\n\ +Checks if a string contains a complete SQL statement. Non-standard."); + +#ifdef HAVE_SHARED_CACHE +static PyObject* module_enable_shared_cache(PyObject* self, PyObject* args, PyObject* + kwargs) +{ + static char *kwlist[] = {"do_enable", NULL, NULL}; + int do_enable; + int rc; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "i", kwlist, &do_enable)) + { + return NULL; + } + + rc = sqlite3_enable_shared_cache(do_enable); + + if (rc != SQLITE_OK) { + PyErr_SetString(pysqlite_OperationalError, "Changing the shared_cache flag failed"); + return NULL; + } else { + Py_INCREF(Py_None); + return Py_None; + } +} + +PyDoc_STRVAR(module_enable_shared_cache_doc, +"enable_shared_cache(do_enable)\n\ +\n\ +Enable or disable shared cache mode for the calling thread.\n\ +Experimental/Non-standard."); +#endif /* HAVE_SHARED_CACHE */ + +static PyObject* module_register_adapter(PyObject* self, PyObject* args) +{ + PyTypeObject* type; + PyObject* caster; + int rc; + + if (!PyArg_ParseTuple(args, "OO", &type, &caster)) { + return NULL; + } + + /* a basic type is adapted; there's a performance optimization if that's not the case + * (99 % of all usages) */ + if (type == &PyLong_Type || type == &PyFloat_Type + || type == &PyUnicode_Type || type == &PyByteArray_Type) { + pysqlite_BaseTypeAdapted = 1; + } + + rc = pysqlite_microprotocols_add(type, (PyObject*)&pysqlite_PrepareProtocolType, caster); + if (rc == -1) + return NULL; + + Py_INCREF(Py_None); + return Py_None; +} + +PyDoc_STRVAR(module_register_adapter_doc, +"register_adapter(type, callable)\n\ +\n\ +Registers an adapter with pysqlite's adapter registry. Non-standard."); + +static PyObject* module_register_converter(PyObject* self, PyObject* args) +{ + PyObject* orig_name; + PyObject* name = NULL; + PyObject* callable; + PyObject* retval = NULL; + _Py_IDENTIFIER(upper); + + if (!PyArg_ParseTuple(args, "UO", &orig_name, &callable)) { + return NULL; + } + + /* convert the name to upper case */ + name = _PyObject_CallMethodId(orig_name, &PyId_upper, ""); + if (!name) { + goto error; + } + + if (PyDict_SetItem(converters, name, callable) != 0) { + goto error; + } + + Py_INCREF(Py_None); + retval = Py_None; +error: + Py_XDECREF(name); + return retval; +} + +PyDoc_STRVAR(module_register_converter_doc, +"register_converter(typename, callable)\n\ +\n\ +Registers a converter with pysqlite. Non-standard."); + +static PyObject* enable_callback_tracebacks(PyObject* self, PyObject* args) +{ + if (!PyArg_ParseTuple(args, "i", &_enable_callback_tracebacks)) { + return NULL; + } + + Py_INCREF(Py_None); + return Py_None; +} + +PyDoc_STRVAR(enable_callback_tracebacks_doc, +"enable_callback_tracebacks(flag)\n\ +\n\ +Enable or disable callback functions throwing errors to stderr."); + +static void converters_init(PyObject* dict) +{ + converters = PyDict_New(); + if (!converters) { + return; + } + + PyDict_SetItemString(dict, "converters", converters); +} + +static PyMethodDef module_methods[] = { + {"connect", (PyCFunction)module_connect, + METH_VARARGS | METH_KEYWORDS, module_connect_doc}, + {"complete_statement", (PyCFunction)module_complete, + METH_VARARGS | METH_KEYWORDS, module_complete_doc}, +#ifdef HAVE_SHARED_CACHE + {"enable_shared_cache", (PyCFunction)module_enable_shared_cache, + METH_VARARGS | METH_KEYWORDS, module_enable_shared_cache_doc}, +#endif + {"register_adapter", (PyCFunction)module_register_adapter, + METH_VARARGS, module_register_adapter_doc}, + {"register_converter", (PyCFunction)module_register_converter, + METH_VARARGS, module_register_converter_doc}, + {"adapt", (PyCFunction)pysqlite_adapt, METH_VARARGS, + pysqlite_adapt_doc}, + {"enable_callback_tracebacks", (PyCFunction)enable_callback_tracebacks, + METH_VARARGS, enable_callback_tracebacks_doc}, + {NULL, NULL} +}; + +struct _IntConstantPair { + char* constant_name; + int constant_value; +}; + +typedef struct _IntConstantPair IntConstantPair; + +static IntConstantPair _int_constants[] = { + {"PARSE_DECLTYPES", PARSE_DECLTYPES}, + {"PARSE_COLNAMES", PARSE_COLNAMES}, + + {"SQLITE_OK", SQLITE_OK}, + {"SQLITE_DENY", SQLITE_DENY}, + {"SQLITE_IGNORE", SQLITE_IGNORE}, + {"SQLITE_CREATE_INDEX", SQLITE_CREATE_INDEX}, + {"SQLITE_CREATE_TABLE", SQLITE_CREATE_TABLE}, + {"SQLITE_CREATE_TEMP_INDEX", SQLITE_CREATE_TEMP_INDEX}, + {"SQLITE_CREATE_TEMP_TABLE", SQLITE_CREATE_TEMP_TABLE}, + {"SQLITE_CREATE_TEMP_TRIGGER", SQLITE_CREATE_TEMP_TRIGGER}, + {"SQLITE_CREATE_TEMP_VIEW", SQLITE_CREATE_TEMP_VIEW}, + {"SQLITE_CREATE_TRIGGER", SQLITE_CREATE_TRIGGER}, + {"SQLITE_CREATE_VIEW", SQLITE_CREATE_VIEW}, + {"SQLITE_DELETE", SQLITE_DELETE}, + {"SQLITE_DROP_INDEX", SQLITE_DROP_INDEX}, + {"SQLITE_DROP_TABLE", SQLITE_DROP_TABLE}, + {"SQLITE_DROP_TEMP_INDEX", SQLITE_DROP_TEMP_INDEX}, + {"SQLITE_DROP_TEMP_TABLE", SQLITE_DROP_TEMP_TABLE}, + {"SQLITE_DROP_TEMP_TRIGGER", SQLITE_DROP_TEMP_TRIGGER}, + {"SQLITE_DROP_TEMP_VIEW", SQLITE_DROP_TEMP_VIEW}, + {"SQLITE_DROP_TRIGGER", SQLITE_DROP_TRIGGER}, + {"SQLITE_DROP_VIEW", SQLITE_DROP_VIEW}, + {"SQLITE_INSERT", SQLITE_INSERT}, + {"SQLITE_PRAGMA", SQLITE_PRAGMA}, + {"SQLITE_READ", SQLITE_READ}, + {"SQLITE_SELECT", SQLITE_SELECT}, + {"SQLITE_TRANSACTION", SQLITE_TRANSACTION}, + {"SQLITE_UPDATE", SQLITE_UPDATE}, + {"SQLITE_ATTACH", SQLITE_ATTACH}, + {"SQLITE_DETACH", SQLITE_DETACH}, +#if SQLITE_VERSION_NUMBER >= 3002001 + {"SQLITE_ALTER_TABLE", SQLITE_ALTER_TABLE}, + {"SQLITE_REINDEX", SQLITE_REINDEX}, +#endif +#if SQLITE_VERSION_NUMBER >= 3003000 + {"SQLITE_ANALYZE", SQLITE_ANALYZE}, +#endif + {(char*)NULL, 0} +}; + + +static struct PyModuleDef _sqlite3module = { + PyModuleDef_HEAD_INIT, + "_sqlite3", + NULL, + -1, + module_methods, + NULL, + NULL, + NULL, + NULL +}; + +PyMODINIT_FUNC PyInit__sqlite3(void) +{ + PyObject *module, *dict; + PyObject *tmp_obj; + int i; + + module = PyModule_Create(&_sqlite3module); + + if (!module || + (pysqlite_row_setup_types() < 0) || + (pysqlite_cursor_setup_types() < 0) || + (pysqlite_connection_setup_types() < 0) || + (pysqlite_cache_setup_types() < 0) || + (pysqlite_statement_setup_types() < 0) || + (pysqlite_prepare_protocol_setup_types() < 0) + ) { + Py_XDECREF(module); + return NULL; + } + + Py_INCREF(&pysqlite_ConnectionType); + PyModule_AddObject(module, "Connection", (PyObject*) &pysqlite_ConnectionType); + Py_INCREF(&pysqlite_CursorType); + PyModule_AddObject(module, "Cursor", (PyObject*) &pysqlite_CursorType); + Py_INCREF(&pysqlite_CacheType); + PyModule_AddObject(module, "Statement", (PyObject*)&pysqlite_StatementType); + Py_INCREF(&pysqlite_StatementType); + PyModule_AddObject(module, "Cache", (PyObject*) &pysqlite_CacheType); + Py_INCREF(&pysqlite_PrepareProtocolType); + PyModule_AddObject(module, "PrepareProtocol", (PyObject*) &pysqlite_PrepareProtocolType); + Py_INCREF(&pysqlite_RowType); + PyModule_AddObject(module, "Row", (PyObject*) &pysqlite_RowType); + + if (!(dict = PyModule_GetDict(module))) { + goto error; + } + + /*** Create DB-API Exception hierarchy */ + + if (!(pysqlite_Error = PyErr_NewException(MODULE_NAME ".Error", PyExc_Exception, NULL))) { + goto error; + } + PyDict_SetItemString(dict, "Error", pysqlite_Error); + + if (!(pysqlite_Warning = PyErr_NewException(MODULE_NAME ".Warning", PyExc_Exception, NULL))) { + goto error; + } + PyDict_SetItemString(dict, "Warning", pysqlite_Warning); + + /* Error subclasses */ + + if (!(pysqlite_InterfaceError = PyErr_NewException(MODULE_NAME ".InterfaceError", pysqlite_Error, NULL))) { + goto error; + } + PyDict_SetItemString(dict, "InterfaceError", pysqlite_InterfaceError); + + if (!(pysqlite_DatabaseError = PyErr_NewException(MODULE_NAME ".DatabaseError", pysqlite_Error, NULL))) { + goto error; + } + PyDict_SetItemString(dict, "DatabaseError", pysqlite_DatabaseError); + + /* pysqlite_DatabaseError subclasses */ + + if (!(pysqlite_InternalError = PyErr_NewException(MODULE_NAME ".InternalError", pysqlite_DatabaseError, NULL))) { + goto error; + } + PyDict_SetItemString(dict, "InternalError", pysqlite_InternalError); + + if (!(pysqlite_OperationalError = PyErr_NewException(MODULE_NAME ".OperationalError", pysqlite_DatabaseError, NULL))) { + goto error; + } + PyDict_SetItemString(dict, "OperationalError", pysqlite_OperationalError); + + if (!(pysqlite_ProgrammingError = PyErr_NewException(MODULE_NAME ".ProgrammingError", pysqlite_DatabaseError, NULL))) { + goto error; + } + PyDict_SetItemString(dict, "ProgrammingError", pysqlite_ProgrammingError); + + if (!(pysqlite_IntegrityError = PyErr_NewException(MODULE_NAME ".IntegrityError", pysqlite_DatabaseError,NULL))) { + goto error; + } + PyDict_SetItemString(dict, "IntegrityError", pysqlite_IntegrityError); + + if (!(pysqlite_DataError = PyErr_NewException(MODULE_NAME ".DataError", pysqlite_DatabaseError, NULL))) { + goto error; + } + PyDict_SetItemString(dict, "DataError", pysqlite_DataError); + + if (!(pysqlite_NotSupportedError = PyErr_NewException(MODULE_NAME ".NotSupportedError", pysqlite_DatabaseError, NULL))) { + goto error; + } + PyDict_SetItemString(dict, "NotSupportedError", pysqlite_NotSupportedError); + + /* In Python 2.x, setting Connection.text_factory to + OptimizedUnicode caused Unicode objects to be returned for + non-ASCII data and bytestrings to be returned for ASCII data. + Now OptimizedUnicode is an alias for str, so it has no + effect. */ + Py_INCREF((PyObject*)&PyUnicode_Type); + PyDict_SetItemString(dict, "OptimizedUnicode", (PyObject*)&PyUnicode_Type); + + /* Set integer constants */ + for (i = 0; _int_constants[i].constant_name != 0; i++) { + tmp_obj = PyLong_FromLong(_int_constants[i].constant_value); + if (!tmp_obj) { + goto error; + } + PyDict_SetItemString(dict, _int_constants[i].constant_name, tmp_obj); + Py_DECREF(tmp_obj); + } + + if (!(tmp_obj = PyUnicode_FromString(PYSQLITE_VERSION))) { + goto error; + } + PyDict_SetItemString(dict, "version", tmp_obj); + Py_DECREF(tmp_obj); + + if (!(tmp_obj = PyUnicode_FromString(sqlite3_libversion()))) { + goto error; + } + PyDict_SetItemString(dict, "sqlite_version", tmp_obj); + Py_DECREF(tmp_obj); + + /* initialize microprotocols layer */ + pysqlite_microprotocols_init(dict); + + /* initialize the default converters */ + converters_init(dict); + + _enable_callback_tracebacks = 0; + + pysqlite_BaseTypeAdapted = 0; + + /* Original comment from _bsddb.c in the Python core. This is also still + * needed nowadays for Python 2.3/2.4. + * + * PyEval_InitThreads is called here due to a quirk in python 1.5 + * - 2.2.1 (at least) according to Russell Williamson : + * The global interpreter lock is not initialized until the first + * thread is created using thread.start_new_thread() or fork() is + * called. that would cause the ALLOW_THREADS here to segfault due + * to a null pointer reference if no threads or child processes + * have been created. This works around that and is a no-op if + * threads have already been initialized. + * (see pybsddb-users mailing list post on 2002-08-07) + */ +#ifdef WITH_THREAD + PyEval_InitThreads(); +#endif + +error: + if (PyErr_Occurred()) + { + PyErr_SetString(PyExc_ImportError, MODULE_NAME ": init failed"); + Py_DECREF(module); + module = NULL; + } + return module; +} diff --git a/src/pysqlcipher3/c/module.h b/src/pysqlcipher3/c/module.h new file mode 100644 index 0000000..b51724b --- /dev/null +++ b/src/pysqlcipher3/c/module.h @@ -0,0 +1,56 @@ +/* module.h - definitions for the module + * + * Copyright (C) 2004-2010 Gerhard Häring + * + * This file is part of pysqlite. + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + */ + +#ifndef PYSQLITE_MODULE_H +#define PYSQLITE_MODULE_H +#include "Python.h" + +#define PYSQLITE_VERSION "2.6.0" + +extern PyObject* pysqlite_Error; +extern PyObject* pysqlite_Warning; +extern PyObject* pysqlite_InterfaceError; +extern PyObject* pysqlite_DatabaseError; +extern PyObject* pysqlite_InternalError; +extern PyObject* pysqlite_OperationalError; +extern PyObject* pysqlite_ProgrammingError; +extern PyObject* pysqlite_IntegrityError; +extern PyObject* pysqlite_DataError; +extern PyObject* pysqlite_NotSupportedError; + +/* the functions time.time() and time.sleep() */ +extern PyObject* time_time; +extern PyObject* time_sleep; + +/* A dictionary, mapping colum types (INTEGER, VARCHAR, etc.) to converter + * functions, that convert the SQL value to the appropriate Python value. + * The key is uppercase. + */ +extern PyObject* converters; + +extern int _enable_callback_tracebacks; +extern int pysqlite_BaseTypeAdapted; + +#define PARSE_DECLTYPES 1 +#define PARSE_COLNAMES 2 +#endif diff --git a/src/pysqlcipher3/c/prepare_protocol.c b/src/pysqlcipher3/c/prepare_protocol.c new file mode 100644 index 0000000..b7ae99c --- /dev/null +++ b/src/pysqlcipher3/c/prepare_protocol.c @@ -0,0 +1,83 @@ +/* prepare_protocol.c - the protocol for preparing values for SQLite + * + * Copyright (C) 2005-2010 Gerhard Häring + * + * This file is part of pysqlite. + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + */ + +#include "prepare_protocol.h" + +int pysqlite_prepare_protocol_init(pysqlite_PrepareProtocol* self, PyObject* args, PyObject* kwargs) +{ + return 0; +} + +void pysqlite_prepare_protocol_dealloc(pysqlite_PrepareProtocol* self) +{ + Py_TYPE(self)->tp_free((PyObject*)self); +} + +PyTypeObject pysqlite_PrepareProtocolType= { + PyVarObject_HEAD_INIT(NULL, 0) + MODULE_NAME ".PrepareProtocol", /* tp_name */ + sizeof(pysqlite_PrepareProtocol), /* tp_basicsize */ + 0, /* tp_itemsize */ + (destructor)pysqlite_prepare_protocol_dealloc, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_reserved */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + 0, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT, /* tp_flags */ + 0, /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + 0, /* tp_methods */ + 0, /* tp_members */ + 0, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + (initproc)pysqlite_prepare_protocol_init, /* tp_init */ + 0, /* tp_alloc */ + 0, /* tp_new */ + 0 /* tp_free */ +}; + +extern int pysqlite_prepare_protocol_setup_types(void) +{ + pysqlite_PrepareProtocolType.tp_new = PyType_GenericNew; + Py_SET_TYPE(&pysqlite_PrepareProtocolType, &PyType_Type); + return PyType_Ready(&pysqlite_PrepareProtocolType); +} diff --git a/src/pysqlcipher3/c/prepare_protocol.h b/src/pysqlcipher3/c/prepare_protocol.h new file mode 100644 index 0000000..4e26212 --- /dev/null +++ b/src/pysqlcipher3/c/prepare_protocol.h @@ -0,0 +1,48 @@ +/* prepare_protocol.h - the protocol for preparing values for SQLite + * + * Copyright (C) 2005-2010 Gerhard Häring + * + * This file is part of pysqlite. + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + */ + +#ifndef PYSQLITE_PREPARE_PROTOCOL_H +#define PYSQLITE_PREPARE_PROTOCOL_H +#include "Python.h" + +typedef struct +{ + PyObject_HEAD +} pysqlite_PrepareProtocol; + +extern PyTypeObject pysqlite_PrepareProtocolType; + +int pysqlite_prepare_protocol_init(pysqlite_PrepareProtocol* self, PyObject* args, PyObject* kwargs); +void pysqlite_prepare_protocol_dealloc(pysqlite_PrepareProtocol* self); + +int pysqlite_prepare_protocol_setup_types(void); + +#define UNKNOWN (-1) +#endif + +// In python >=3.11 Py_TYPE has changed https://docs.python.org/3.11/whatsnew/3.11.html +#if PY_VERSION_HEX < 0x030900A4 && !defined(Py_SET_TYPE) +static inline void _Py_SET_TYPE(PyObject *ob, PyTypeObject *type) +{ ob->ob_type = type; } +#define Py_SET_TYPE(ob, type) _Py_SET_TYPE((PyObject*)(ob), type) +#endif \ No newline at end of file diff --git a/src/pysqlcipher3/c/row.c b/src/pysqlcipher3/c/row.c new file mode 100644 index 0000000..ee9a908 --- /dev/null +++ b/src/pysqlcipher3/c/row.c @@ -0,0 +1,276 @@ +/* row.c - an enhanced tuple for database rows + * + * Copyright (C) 2005-2010 Gerhard Häring + * + * This file is part of pysqlite. + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + */ + +#include "row.h" +#include "cursor.h" + +void pysqlite_row_dealloc(pysqlite_Row* self) +{ + Py_XDECREF(self->data); + Py_XDECREF(self->description); + + Py_TYPE(self)->tp_free((PyObject*)self); +} + +static PyObject * +pysqlite_row_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) +{ + pysqlite_Row *self; + PyObject* data; + pysqlite_Cursor* cursor; + + assert(type != NULL && type->tp_alloc != NULL); + + if (!_PyArg_NoKeywords("Row()", kwargs)) + return NULL; + if (!PyArg_ParseTuple(args, "OO", &cursor, &data)) + return NULL; + + if (!PyObject_IsInstance((PyObject*)cursor, (PyObject*)&pysqlite_CursorType)) { + PyErr_SetString(PyExc_TypeError, "instance of cursor required for first argument"); + return NULL; + } + + if (!PyTuple_Check(data)) { + PyErr_SetString(PyExc_TypeError, "tuple required for second argument"); + return NULL; + } + + self = (pysqlite_Row *) type->tp_alloc(type, 0); + if (self == NULL) + return NULL; + + Py_INCREF(data); + self->data = data; + + Py_INCREF(cursor->description); + self->description = cursor->description; + + return (PyObject *) self; +} + +PyObject* pysqlite_row_item(pysqlite_Row* self, Py_ssize_t idx) +{ + PyObject* item = PyTuple_GetItem(self->data, idx); + Py_XINCREF(item); + return item; +} + +PyObject* pysqlite_row_subscript(pysqlite_Row* self, PyObject* idx) +{ + Py_ssize_t _idx; + char* key; + Py_ssize_t nitems, i; + char* compare_key; + + char* p1; + char* p2; + + PyObject* item; + + if (PyLong_Check(idx)) { + _idx = PyNumber_AsSsize_t(idx, PyExc_IndexError); + if (_idx == -1 && PyErr_Occurred()) + return NULL; + if (_idx < 0) + _idx += PyTuple_GET_SIZE(self->data); + item = PyTuple_GetItem(self->data, _idx); + Py_XINCREF(item); + return item; + } else if (PyUnicode_Check(idx)) { + key = _PyUnicode_AsString(idx); + if (key == NULL) + return NULL; + + nitems = PyTuple_Size(self->description); + + for (i = 0; i < nitems; i++) { + PyObject *obj; + obj = PyTuple_GET_ITEM(self->description, i); + obj = PyTuple_GET_ITEM(obj, 0); + compare_key = _PyUnicode_AsString(obj); + if (!compare_key) { + return NULL; + } + + p1 = key; + p2 = compare_key; + + while (1) { + if ((*p1 == (char)0) || (*p2 == (char)0)) { + break; + } + + if ((*p1 | 0x20) != (*p2 | 0x20)) { + break; + } + + p1++; + p2++; + } + + if ((*p1 == (char)0) && (*p2 == (char)0)) { + /* found item */ + item = PyTuple_GetItem(self->data, i); + Py_INCREF(item); + return item; + } + + } + + PyErr_SetString(PyExc_IndexError, "No item with that key"); + return NULL; + } + else if (PySlice_Check(idx)) { + PyErr_SetString(PyExc_ValueError, "slices not implemented, yet"); + return NULL; + } + else { + PyErr_SetString(PyExc_IndexError, "Index must be int or string"); + return NULL; + } +} + +Py_ssize_t pysqlite_row_length(pysqlite_Row* self, PyObject* args, PyObject* kwargs) +{ + return PyTuple_GET_SIZE(self->data); +} + +PyObject* pysqlite_row_keys(pysqlite_Row* self, PyObject* args, PyObject* kwargs) +{ + PyObject* list; + int nitems, i; + + list = PyList_New(0); + if (!list) { + return NULL; + } + nitems = PyTuple_Size(self->description); + + for (i = 0; i < nitems; i++) { + if (PyList_Append(list, PyTuple_GET_ITEM(PyTuple_GET_ITEM(self->description, i), 0)) != 0) { + Py_DECREF(list); + return NULL; + } + } + + return list; +} + +static PyObject* pysqlite_iter(pysqlite_Row* self) +{ + return PyObject_GetIter(self->data); +} + +static Py_hash_t pysqlite_row_hash(pysqlite_Row *self) +{ + return PyObject_Hash(self->description) ^ PyObject_Hash(self->data); +} + +static PyObject* pysqlite_row_richcompare(pysqlite_Row *self, PyObject *_other, int opid) +{ + if (opid != Py_EQ && opid != Py_NE) + Py_RETURN_NOTIMPLEMENTED; + + if (PyType_IsSubtype(Py_TYPE(_other), &pysqlite_RowType)) { + pysqlite_Row *other = (pysqlite_Row *)_other; + PyObject *res = PyObject_RichCompare(self->description, other->description, opid); + if ((opid == Py_EQ && res == Py_True) + || (opid == Py_NE && res == Py_False)) { + Py_DECREF(res); + return PyObject_RichCompare(self->data, other->data, opid); + } + } + Py_RETURN_NOTIMPLEMENTED; +} + +PyMappingMethods pysqlite_row_as_mapping = { + /* mp_length */ (lenfunc)pysqlite_row_length, + /* mp_subscript */ (binaryfunc)pysqlite_row_subscript, + /* mp_ass_subscript */ (objobjargproc)0, +}; + +static PySequenceMethods pysqlite_row_as_sequence = { + /* sq_length */ (lenfunc)pysqlite_row_length, + /* sq_concat */ 0, + /* sq_repeat */ 0, + /* sq_item */ (ssizeargfunc)pysqlite_row_item, +}; + + +static PyMethodDef pysqlite_row_methods[] = { + {"keys", (PyCFunction)pysqlite_row_keys, METH_NOARGS, + PyDoc_STR("Returns the keys of the row.")}, + {NULL, NULL} +}; + + +PyTypeObject pysqlite_RowType = { + PyVarObject_HEAD_INIT(NULL, 0) + MODULE_NAME ".Row", /* tp_name */ + sizeof(pysqlite_Row), /* tp_basicsize */ + 0, /* tp_itemsize */ + (destructor)pysqlite_row_dealloc, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_reserved */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + (hashfunc)pysqlite_row_hash, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + 0, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, /* tp_flags */ + 0, /* tp_doc */ + (traverseproc)0, /* tp_traverse */ + 0, /* tp_clear */ + (richcmpfunc)pysqlite_row_richcompare, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + (getiterfunc)pysqlite_iter, /* tp_iter */ + 0, /* tp_iternext */ + pysqlite_row_methods, /* tp_methods */ + 0, /* tp_members */ + 0, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + 0, /* tp_init */ + 0, /* tp_alloc */ + 0, /* tp_new */ + 0 /* tp_free */ +}; + +extern int pysqlite_row_setup_types(void) +{ + pysqlite_RowType.tp_new = pysqlite_row_new; + pysqlite_RowType.tp_as_mapping = &pysqlite_row_as_mapping; + pysqlite_RowType.tp_as_sequence = &pysqlite_row_as_sequence; + return PyType_Ready(&pysqlite_RowType); +} diff --git a/src/pysqlcipher3/c/row.h b/src/pysqlcipher3/c/row.h new file mode 100644 index 0000000..d014109 --- /dev/null +++ b/src/pysqlcipher3/c/row.h @@ -0,0 +1,39 @@ +/* row.h - an enhanced tuple for database rows + * + * Copyright (C) 2005-2010 Gerhard Häring + * + * This file is part of pysqlite. + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + */ + +#ifndef PYSQLITE_ROW_H +#define PYSQLITE_ROW_H +#include "Python.h" + +typedef struct _Row +{ + PyObject_HEAD + PyObject* data; + PyObject* description; +} pysqlite_Row; + +extern PyTypeObject pysqlite_RowType; + +int pysqlite_row_setup_types(void); + +#endif diff --git a/sqlcipher/README.md b/src/pysqlcipher3/c/sqlcipher/README.md similarity index 91% rename from sqlcipher/README.md rename to src/pysqlcipher3/c/sqlcipher/README.md index efbbe13..a5b79e5 100644 --- a/sqlcipher/README.md +++ b/src/pysqlcipher3/c/sqlcipher/README.md @@ -1,5 +1,7 @@ # sqlcipher +Source: https://github.com/sqlcipher/sqlcipher + Copyright belongs to [SQLite](https://www.sqlite.org/) and [Zetetic](https://github.com/sqlcipher/sqlcipher?tab=License-1-ov-file). This is a vendored amalgamation build of SQLCipher, built as follows: diff --git a/sqlcipher/sqlite3.c b/src/pysqlcipher3/c/sqlcipher/sqlite3.c similarity index 100% rename from sqlcipher/sqlite3.c rename to src/pysqlcipher3/c/sqlcipher/sqlite3.c diff --git a/sqlcipher/sqlite3.h b/src/pysqlcipher3/c/sqlcipher/sqlite3.h similarity index 100% rename from sqlcipher/sqlite3.h rename to src/pysqlcipher3/c/sqlcipher/sqlite3.h diff --git a/src/pysqlcipher3/c/statement.c b/src/pysqlcipher3/c/statement.c new file mode 100644 index 0000000..c007ffe --- /dev/null +++ b/src/pysqlcipher3/c/statement.c @@ -0,0 +1,521 @@ +/* statement.c - the statement type + * + * Copyright (C) 2005-2010 Gerhard Häring + * + * This file is part of pysqlite. + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + */ + +#include "statement.h" +#include "cursor.h" +#include "connection.h" +#include "microprotocols.h" +#include "prepare_protocol.h" +#include "util.h" + +/* prototypes */ +static int pysqlite_check_remaining_sql(const char* tail); + +typedef enum { + LINECOMMENT_1, + IN_LINECOMMENT, + COMMENTSTART_1, + IN_COMMENT, + COMMENTEND_1, + NORMAL +} parse_remaining_sql_state; + +typedef enum { + TYPE_LONG, + TYPE_FLOAT, + TYPE_UNICODE, + TYPE_BUFFER, + TYPE_UNKNOWN +} parameter_type; + +int pysqlite_statement_create(pysqlite_Statement* self, pysqlite_Connection* connection, PyObject* sql) +{ + const char* tail; + int rc; + const char* sql_cstr; + Py_ssize_t sql_cstr_len; + + self->st = NULL; + self->in_use = 0; + + sql_cstr = PyUnicode_AsUTF8AndSize(sql, &sql_cstr_len); + if (sql_cstr == NULL) { + rc = PYSQLITE_SQL_WRONG_TYPE; + return rc; + } + if (strlen(sql_cstr) != (size_t)sql_cstr_len) { + PyErr_SetString(PyExc_ValueError, "the query contains a null character"); + return PYSQLITE_SQL_WRONG_TYPE; + } + + self->in_weakreflist = NULL; + Py_INCREF(sql); + self->sql = sql; + + Py_BEGIN_ALLOW_THREADS + rc = sqlite3_prepare(connection->db, + sql_cstr, + -1, + &self->st, + &tail); + Py_END_ALLOW_THREADS + + self->db = connection->db; + + if (rc == SQLITE_OK && pysqlite_check_remaining_sql(tail)) { + (void)sqlite3_finalize(self->st); + self->st = NULL; + rc = PYSQLITE_TOO_MUCH_SQL; + } + + return rc; +} + +int pysqlite_statement_bind_parameter(pysqlite_Statement* self, int pos, PyObject* parameter) +{ + int rc = SQLITE_OK; + const char* buffer; + char* string; + Py_ssize_t buflen; + parameter_type paramtype; + + if (parameter == Py_None) { + rc = sqlite3_bind_null(self->st, pos); + goto final; + } + + if (PyLong_CheckExact(parameter)) { + paramtype = TYPE_LONG; + } else if (PyFloat_CheckExact(parameter)) { + paramtype = TYPE_FLOAT; + } else if (PyUnicode_CheckExact(parameter)) { + paramtype = TYPE_UNICODE; + } else if (PyLong_Check(parameter)) { + paramtype = TYPE_LONG; + } else if (PyFloat_Check(parameter)) { + paramtype = TYPE_FLOAT; + } else if (PyUnicode_Check(parameter)) { + paramtype = TYPE_UNICODE; + } else if (PyObject_CheckBuffer(parameter)) { + paramtype = TYPE_BUFFER; + } else { + paramtype = TYPE_UNKNOWN; + } + + switch (paramtype) { + case TYPE_LONG: { + sqlite_int64 value = _pysqlite_long_as_int64(parameter); + if (value == -1 && PyErr_Occurred()) + rc = -1; + else + rc = sqlite3_bind_int64(self->st, pos, value); + break; + } + case TYPE_FLOAT: + rc = sqlite3_bind_double(self->st, pos, PyFloat_AsDouble(parameter)); + break; + case TYPE_UNICODE: + string = PyUnicode_AsUTF8AndSize(parameter, &buflen); + if (string == NULL) + return -1; + if (buflen > INT_MAX) { + PyErr_SetString(PyExc_OverflowError, + "string longer than INT_MAX bytes"); + return -1; + } + rc = sqlite3_bind_text(self->st, pos, string, (int)buflen, SQLITE_TRANSIENT); + break; + case TYPE_BUFFER: + if (PyObject_AsCharBuffer(parameter, &buffer, &buflen) != 0) { + PyErr_SetString(PyExc_ValueError, "could not convert BLOB to buffer"); + return -1; + } + if (buflen > INT_MAX) { + PyErr_SetString(PyExc_OverflowError, + "BLOB longer than INT_MAX bytes"); + return -1; + } + rc = sqlite3_bind_blob(self->st, pos, buffer, buflen, SQLITE_TRANSIENT); + break; + case TYPE_UNKNOWN: + rc = -1; + } + +final: + return rc; +} + +/* returns 0 if the object is one of Python's internal ones that don't need to be adapted */ +static int _need_adapt(PyObject* obj) +{ + if (pysqlite_BaseTypeAdapted) { + return 1; + } + + if (PyLong_CheckExact(obj) || PyFloat_CheckExact(obj) + || PyUnicode_CheckExact(obj) || PyByteArray_CheckExact(obj)) { + return 0; + } else { + return 1; + } +} + +void pysqlite_statement_bind_parameters(pysqlite_Statement* self, PyObject* parameters) +{ + PyObject* current_param; + PyObject* adapted; + const char* binding_name; + int i; + int rc; + int num_params_needed; + Py_ssize_t num_params; + + Py_BEGIN_ALLOW_THREADS + num_params_needed = sqlite3_bind_parameter_count(self->st); + Py_END_ALLOW_THREADS + + if (PyTuple_CheckExact(parameters) || PyList_CheckExact(parameters) || (!PyDict_Check(parameters) && PySequence_Check(parameters))) { + /* parameters passed as sequence */ + if (PyTuple_CheckExact(parameters)) { + num_params = PyTuple_GET_SIZE(parameters); + } else if (PyList_CheckExact(parameters)) { + num_params = PyList_GET_SIZE(parameters); + } else { + num_params = PySequence_Size(parameters); + } + if (num_params != num_params_needed) { + PyErr_Format(pysqlite_ProgrammingError, + "Incorrect number of bindings supplied. The current " + "statement uses %d, and there are %zd supplied.", + num_params_needed, num_params); + return; + } + for (i = 0; i < num_params; i++) { + if (PyTuple_CheckExact(parameters)) { + current_param = PyTuple_GET_ITEM(parameters, i); + Py_XINCREF(current_param); + } else if (PyList_CheckExact(parameters)) { + current_param = PyList_GET_ITEM(parameters, i); + Py_XINCREF(current_param); + } else { + current_param = PySequence_GetItem(parameters, i); + } + if (!current_param) { + return; + } + + if (!_need_adapt(current_param)) { + adapted = current_param; + } else { + adapted = pysqlite_microprotocols_adapt(current_param, (PyObject*)&pysqlite_PrepareProtocolType, NULL); + if (adapted) { + Py_DECREF(current_param); + } else { + PyErr_Clear(); + adapted = current_param; + } + } + + rc = pysqlite_statement_bind_parameter(self, i + 1, adapted); + Py_DECREF(adapted); + + if (rc != SQLITE_OK) { + if (!PyErr_Occurred()) { + PyErr_Format(pysqlite_InterfaceError, "Error binding parameter %d - probably unsupported type.", i); + } + return; + } + } + } else if (PyDict_Check(parameters)) { + /* parameters passed as dictionary */ + for (i = 1; i <= num_params_needed; i++) { + Py_BEGIN_ALLOW_THREADS + binding_name = sqlite3_bind_parameter_name(self->st, i); + Py_END_ALLOW_THREADS + if (!binding_name) { + PyErr_Format(pysqlite_ProgrammingError, "Binding %d has no name, but you supplied a dictionary (which has only names).", i); + return; + } + + binding_name++; /* skip first char (the colon) */ + if (PyDict_CheckExact(parameters)) { + current_param = PyDict_GetItemString(parameters, binding_name); + Py_XINCREF(current_param); + } else { + current_param = PyMapping_GetItemString(parameters, binding_name); + } + if (!current_param) { + PyErr_Format(pysqlite_ProgrammingError, "You did not supply a value for binding %d.", i); + return; + } + + if (!_need_adapt(current_param)) { + adapted = current_param; + } else { + adapted = pysqlite_microprotocols_adapt(current_param, (PyObject*)&pysqlite_PrepareProtocolType, NULL); + if (adapted) { + Py_DECREF(current_param); + } else { + PyErr_Clear(); + adapted = current_param; + } + } + + rc = pysqlite_statement_bind_parameter(self, i, adapted); + Py_DECREF(adapted); + + if (rc != SQLITE_OK) { + if (!PyErr_Occurred()) { + PyErr_Format(pysqlite_InterfaceError, "Error binding parameter :%s - probably unsupported type.", binding_name); + } + return; + } + } + } else { + PyErr_SetString(PyExc_ValueError, "parameters are of unsupported type"); + } +} + +int pysqlite_statement_recompile(pysqlite_Statement* self, PyObject* params) +{ + const char* tail; + int rc; + const char* sql_cstr; + Py_ssize_t sql_len; + sqlite3_stmt* new_st; + + sql_cstr = PyUnicode_AsUTF8AndSize(self->sql, &sql_len); + if (sql_cstr == NULL) { + rc = PYSQLITE_SQL_WRONG_TYPE; + return rc; + } + + Py_BEGIN_ALLOW_THREADS + rc = sqlite3_prepare(self->db, + sql_cstr, + -1, + &new_st, + &tail); + Py_END_ALLOW_THREADS + + if (rc == SQLITE_OK) { + /* The efficient sqlite3_transfer_bindings is only available in SQLite + * version 3.2.2 or later. For older SQLite releases, that might not + * even define SQLITE_VERSION_NUMBER, we do it the manual way. + */ + #ifdef SQLITE_VERSION_NUMBER + #if SQLITE_VERSION_NUMBER >= 3002002 + /* The check for the number of parameters is necessary to not trigger a + * bug in certain SQLite versions (experienced in 3.2.8 and 3.3.4). */ + if (sqlite3_bind_parameter_count(self->st) > 0) { + (void)sqlite3_transfer_bindings(self->st, new_st); + } + #endif + #else + statement_bind_parameters(self, params); + #endif + + (void)sqlite3_finalize(self->st); + self->st = new_st; + } + + return rc; +} + +int pysqlite_statement_finalize(pysqlite_Statement* self) +{ + int rc; + + rc = SQLITE_OK; + if (self->st) { + Py_BEGIN_ALLOW_THREADS + rc = sqlite3_finalize(self->st); + Py_END_ALLOW_THREADS + self->st = NULL; + } + + self->in_use = 0; + + return rc; +} + +int pysqlite_statement_reset(pysqlite_Statement* self) +{ + int rc; + + rc = SQLITE_OK; + + if (self->in_use && self->st) { + Py_BEGIN_ALLOW_THREADS + rc = sqlite3_reset(self->st); + Py_END_ALLOW_THREADS + + if (rc == SQLITE_OK) { + self->in_use = 0; + } + } + + return rc; +} + +void pysqlite_statement_mark_dirty(pysqlite_Statement* self) +{ + self->in_use = 1; +} + +void pysqlite_statement_dealloc(pysqlite_Statement* self) +{ + if (self->st) { + Py_BEGIN_ALLOW_THREADS + sqlite3_finalize(self->st); + Py_END_ALLOW_THREADS + } + + self->st = NULL; + + Py_XDECREF(self->sql); + + if (self->in_weakreflist != NULL) { + PyObject_ClearWeakRefs((PyObject*)self); + } + + Py_TYPE(self)->tp_free((PyObject*)self); +} + +/* + * Checks if there is anything left in an SQL string after SQLite compiled it. + * This is used to check if somebody tried to execute more than one SQL command + * with one execute()/executemany() command, which the DB-API and we don't + * allow. + * + * Returns 1 if there is more left than should be. 0 if ok. + */ +static int pysqlite_check_remaining_sql(const char* tail) +{ + const char* pos = tail; + + parse_remaining_sql_state state = NORMAL; + + for (;;) { + switch (*pos) { + case 0: + return 0; + case '-': + if (state == NORMAL) { + state = LINECOMMENT_1; + } else if (state == LINECOMMENT_1) { + state = IN_LINECOMMENT; + } + break; + case ' ': + case '\t': + break; + case '\n': + case 13: + if (state == IN_LINECOMMENT) { + state = NORMAL; + } + break; + case '/': + if (state == NORMAL) { + state = COMMENTSTART_1; + } else if (state == COMMENTEND_1) { + state = NORMAL; + } else if (state == COMMENTSTART_1) { + return 1; + } + break; + case '*': + if (state == NORMAL) { + return 1; + } else if (state == LINECOMMENT_1) { + return 1; + } else if (state == COMMENTSTART_1) { + state = IN_COMMENT; + } else if (state == IN_COMMENT) { + state = COMMENTEND_1; + } + break; + default: + if (state == COMMENTEND_1) { + state = IN_COMMENT; + } else if (state == IN_LINECOMMENT) { + } else if (state == IN_COMMENT) { + } else { + return 1; + } + } + + pos++; + } + + return 0; +} + +PyTypeObject pysqlite_StatementType = { + PyVarObject_HEAD_INIT(NULL, 0) + MODULE_NAME ".Statement", /* tp_name */ + sizeof(pysqlite_Statement), /* tp_basicsize */ + 0, /* tp_itemsize */ + (destructor)pysqlite_statement_dealloc, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_reserved */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + 0, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT, /* tp_flags */ + 0, /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + offsetof(pysqlite_Statement, in_weakreflist), /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + 0, /* tp_methods */ + 0, /* tp_members */ + 0, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + (initproc)0, /* tp_init */ + 0, /* tp_alloc */ + 0, /* tp_new */ + 0 /* tp_free */ +}; + +extern int pysqlite_statement_setup_types(void) +{ + pysqlite_StatementType.tp_new = PyType_GenericNew; + return PyType_Ready(&pysqlite_StatementType); +} diff --git a/src/pysqlcipher3/c/statement.h b/src/pysqlcipher3/c/statement.h new file mode 100644 index 0000000..52ac3a8 --- /dev/null +++ b/src/pysqlcipher3/c/statement.h @@ -0,0 +1,59 @@ +/* statement.h - definitions for the statement type + * + * Copyright (C) 2005-2010 Gerhard Häring + * + * This file is part of pysqlite. + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + */ + +#ifndef PYSQLITE_STATEMENT_H +#define PYSQLITE_STATEMENT_H +#include "Python.h" + +#include "connection.h" +#include "sqlcipher/sqlite3.h" + +#define PYSQLITE_TOO_MUCH_SQL (-100) +#define PYSQLITE_SQL_WRONG_TYPE (-101) + +typedef struct +{ + PyObject_HEAD + sqlite3* db; + sqlite3_stmt* st; + PyObject* sql; + int in_use; + PyObject* in_weakreflist; /* List of weak references */ +} pysqlite_Statement; + +extern PyTypeObject pysqlite_StatementType; + +int pysqlite_statement_create(pysqlite_Statement* self, pysqlite_Connection* connection, PyObject* sql); +void pysqlite_statement_dealloc(pysqlite_Statement* self); + +int pysqlite_statement_bind_parameter(pysqlite_Statement* self, int pos, PyObject* parameter); +void pysqlite_statement_bind_parameters(pysqlite_Statement* self, PyObject* parameters); + +int pysqlite_statement_recompile(pysqlite_Statement* self, PyObject* parameters); +int pysqlite_statement_finalize(pysqlite_Statement* self); +int pysqlite_statement_reset(pysqlite_Statement* self); +void pysqlite_statement_mark_dirty(pysqlite_Statement* self); + +int pysqlite_statement_setup_types(void); + +#endif diff --git a/src/pysqlcipher3/c/util.c b/src/pysqlcipher3/c/util.c new file mode 100644 index 0000000..312fe3b --- /dev/null +++ b/src/pysqlcipher3/c/util.c @@ -0,0 +1,172 @@ +/* util.c - various utility functions + * + * Copyright (C) 2005-2010 Gerhard Häring + * + * This file is part of pysqlite. + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + */ + +#include "module.h" +#include "connection.h" + +int pysqlite_step(sqlite3_stmt* statement, pysqlite_Connection* connection) +{ + int rc; + + if (statement == NULL) { + /* this is a workaround for SQLite 3.5 and later. it now apparently + * returns NULL for "no-operation" statements */ + rc = SQLITE_OK; + } else { + Py_BEGIN_ALLOW_THREADS + rc = sqlite3_step(statement); + Py_END_ALLOW_THREADS + } + + return rc; +} + +/** + * Checks the SQLite error code and sets the appropriate DB-API exception. + * Returns the error code (0 means no error occurred). + */ +int _pysqlite_seterror(sqlite3* db, sqlite3_stmt* st) +{ + int errorcode; + + /* SQLite often doesn't report anything useful, unless you reset the statement first */ + if (st != NULL) { + (void)sqlite3_reset(st); + } + + errorcode = sqlite3_errcode(db); + + switch (errorcode) + { + case SQLITE_OK: + PyErr_Clear(); + break; + case SQLITE_INTERNAL: + case SQLITE_NOTFOUND: + PyErr_SetString(pysqlite_InternalError, sqlite3_errmsg(db)); + break; + case SQLITE_NOMEM: + (void)PyErr_NoMemory(); + break; + case SQLITE_ERROR: + case SQLITE_PERM: + case SQLITE_ABORT: + case SQLITE_BUSY: + case SQLITE_LOCKED: + case SQLITE_READONLY: + case SQLITE_INTERRUPT: + case SQLITE_IOERR: + case SQLITE_FULL: + case SQLITE_CANTOPEN: + case SQLITE_PROTOCOL: + case SQLITE_EMPTY: + case SQLITE_SCHEMA: + PyErr_SetString(pysqlite_OperationalError, sqlite3_errmsg(db)); + break; + case SQLITE_CORRUPT: + PyErr_SetString(pysqlite_DatabaseError, sqlite3_errmsg(db)); + break; + case SQLITE_TOOBIG: + PyErr_SetString(pysqlite_DataError, sqlite3_errmsg(db)); + break; + case SQLITE_CONSTRAINT: + case SQLITE_MISMATCH: + PyErr_SetString(pysqlite_IntegrityError, sqlite3_errmsg(db)); + break; + case SQLITE_MISUSE: + PyErr_SetString(pysqlite_ProgrammingError, sqlite3_errmsg(db)); + break; + default: + PyErr_SetString(pysqlite_DatabaseError, sqlite3_errmsg(db)); + break; + } + + return errorcode; +} + +#ifdef WORDS_BIGENDIAN +# define IS_LITTLE_ENDIAN 0 +#else +# define IS_LITTLE_ENDIAN 1 +#endif + +PyObject * +_pysqlite_long_from_int64(sqlite_int64 value) +{ +#ifdef HAVE_LONG_LONG +# if SIZEOF_LONG_LONG < 8 + if (value > PY_LLONG_MAX || value < PY_LLONG_MIN) { + return _PyLong_FromByteArray(&value, sizeof(value), + IS_LITTLE_ENDIAN, 1 /* signed */); + } +# endif +# if SIZEOF_LONG < SIZEOF_LONG_LONG + if (value > LONG_MAX || value < LONG_MIN) + return PyLong_FromLongLong(value); +# endif +#else +# if SIZEOF_LONG < 8 + if (value > LONG_MAX || value < LONG_MIN) { + return _PyLong_FromByteArray(&value, sizeof(value), + IS_LITTLE_ENDIAN, 1 /* signed */); + } +# endif +#endif + return PyLong_FromLong(Py_SAFE_DOWNCAST(value, sqlite_int64, long)); +} + +sqlite_int64 +_pysqlite_long_as_int64(PyObject * py_val) +{ + int overflow; +#ifdef HAVE_LONG_LONG + PY_LONG_LONG value = PyLong_AsLongLongAndOverflow(py_val, &overflow); +#else + long value = PyLong_AsLongAndOverflow(py_val, &overflow); +#endif + if (value == -1 && PyErr_Occurred()) + return -1; + if (!overflow) { +#ifdef HAVE_LONG_LONG +# if SIZEOF_LONG_LONG > 8 + if (-0x8000000000000000LL <= value && value <= 0x7FFFFFFFFFFFFFFFLL) +# endif +#else +# if SIZEOF_LONG > 8 + if (-0x8000000000000000L <= value && value <= 0x7FFFFFFFFFFFFFFFL) +# endif +#endif + return value; + } + else if (sizeof(value) < sizeof(sqlite_int64)) { + sqlite_int64 int64val; + if (_PyLong_AsByteArray((PyLongObject *)py_val, + (unsigned char *)&int64val, sizeof(int64val), + IS_LITTLE_ENDIAN, 1 /* signed */) >= 0) { + return int64val; + } + } + PyErr_SetString(PyExc_OverflowError, + "Python int too large to convert to SQLite INTEGER"); + return -1; +} diff --git a/src/pysqlcipher3/c/util.h b/src/pysqlcipher3/c/util.h new file mode 100644 index 0000000..748fad1 --- /dev/null +++ b/src/pysqlcipher3/c/util.h @@ -0,0 +1,42 @@ +/* util.h - various utility functions + * + * Copyright (C) 2005-2010 Gerhard Häring + * + * This file is part of pysqlite. + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + */ + +#ifndef PYSQLITE_UTIL_H +#define PYSQLITE_UTIL_H +#include "Python.h" +#include "pythread.h" +#include "sqlcipher/sqlite3.h" +#include "connection.h" + +int pysqlite_step(sqlite3_stmt* statement, pysqlite_Connection* connection); + +/** + * Checks the SQLite error code and sets the appropriate DB-API exception. + * Returns the error code (0 means no error occurred). + */ +int _pysqlite_seterror(sqlite3* db, sqlite3_stmt* st); + +PyObject * _pysqlite_long_from_int64(sqlite_int64 value); +sqlite_int64 _pysqlite_long_as_int64(PyObject * value); + +#endif diff --git a/src/pysqlcipher3/dbapi2.py b/src/pysqlcipher3/dbapi2.py new file mode 100644 index 0000000..ab394e3 --- /dev/null +++ b/src/pysqlcipher3/dbapi2.py @@ -0,0 +1,96 @@ +# -*- coding: ISO-8859-1 -*- +# pysqlcipher3/dbapi2.py: the DB-API 2.0 interface with SQLCipher support +# +# Copyright (C) David Riggleman +# Copyright (C) Kali Kaneko +# Copyright (C) 2010 Gerhard Häring +# +# This file is part of pysqlite. +# +# This software is provided 'as-is', without any express or implied +# warranty. In no event will the authors be held liable for any damages +# arising from the use of this software. +# +# Permission is granted to anyone to use this software for any purpose, +# including commercial applications, and to alter it and redistribute it +# freely, subject to the following restrictions: +# +# 1. The origin of this software must not be misrepresented; you must not +# claim that you wrote the original software. If you use this software +# in a product, an acknowledgment in the product documentation would be +# appreciated but is not required. +# 2. Altered source versions must be plainly marked as such, and must not be +# misrepresented as being the original software. +# 3. This notice may not be removed or altered from any source distribution. + +import collections.abc +import datetime +import time + +from pysqlcipher3._sqlite3 import * + + +paramstyle = "qmark" +threadsafety = 1 +apilevel = "2.0" + +Date = datetime.date + +Time = datetime.time + +Timestamp = datetime.datetime + + +def DateFromTicks(ticks): + return Date(*time.localtime(ticks)[:3]) + + +def TimeFromTicks(ticks): + return Time(*time.localtime(ticks)[3:6]) + + +def TimestampFromTicks(ticks): + return Timestamp(*time.localtime(ticks)[:6]) + + +version_info = tuple([int(x) for x in version.split(".")]) +sqlite_version_info = tuple([int(x) for x in sqlite_version.split(".")]) + + +Binary = memoryview +collections.abc.Sequence.register(Row) + + +def register_adapters_and_converters(): + def adapt_date(val): + return val.isoformat() + + def adapt_datetime(val): + return val.isoformat(" ") + + def convert_date(val): + return datetime.date(*map(int, val.split(b"-"))) + + def convert_timestamp(val): + datepart, timepart = val.split(b" ") + year, month, day = map(int, datepart.split(b"-")) + timepart_full = timepart.split(b".") + hours, minutes, seconds = map(int, timepart_full[0].split(b":")) + if len(timepart_full) == 2: + microseconds = int('{:0<6.6}'.format(timepart_full[1].decode())) + else: + microseconds = 0 + + val = datetime.datetime(year, month, day, hours, minutes, + seconds, microseconds) + return val + + + register_adapter(datetime.date, adapt_date) + register_adapter(datetime.datetime, adapt_datetime) + register_converter("date", convert_date) + register_converter("timestamp", convert_timestamp) + +register_adapters_and_converters() + +del register_adapters_and_converters