!1 first commit for python driver
Merge pull request !1 from lishifu/master
This commit is contained in:
15
AUTHORS
Normal file
15
AUTHORS
Normal file
@ -0,0 +1,15 @@
|
||||
Main authors:
|
||||
Federico Di Gregorio <fog@debian.org>
|
||||
Daniele Varrazzo <daniele.varrazzo@gmail.com>
|
||||
|
||||
For the win32 port:
|
||||
Jason Erickson <jerickso@indian.com>
|
||||
|
||||
Additional Help:
|
||||
|
||||
Peter Fein contributed a logging connection/cursor class that even if it
|
||||
was not used directly heavily influenced the implementation currently in
|
||||
psycopg2.extras.
|
||||
|
||||
Jan Urbański (re)started the work on asynchronous queries and contributed
|
||||
both on that and on other parts of psycopg2.
|
||||
4
INSTALL
Normal file
4
INSTALL
Normal file
@ -0,0 +1,4 @@
|
||||
Installation instructions are included in the docs.
|
||||
|
||||
Please check the 'doc/src/install.rst' file or online at
|
||||
<https://www.psycopg.org/docs/install.html>.
|
||||
49
LICENSE
Normal file
49
LICENSE
Normal file
@ -0,0 +1,49 @@
|
||||
psycopg2 and the LGPL
|
||||
---------------------
|
||||
|
||||
psycopg2 is free software: you can redistribute it and/or modify it
|
||||
under the terms of the GNU Lesser General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
psycopg2 is distributed in the hope that it will be useful, but WITHOUT
|
||||
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
|
||||
License for more details.
|
||||
|
||||
In addition, as a special exception, the copyright holders give
|
||||
permission to link this program with the OpenSSL library (or with
|
||||
modified versions of OpenSSL that use the same license as OpenSSL),
|
||||
and distribute linked combinations including the two.
|
||||
|
||||
You must obey the GNU Lesser General Public License in all respects for
|
||||
all of the code used other than OpenSSL. If you modify file(s) with this
|
||||
exception, you may extend this exception to your version of the file(s),
|
||||
but you are not obligated to do so. If you do not wish to do so, delete
|
||||
this exception statement from your version. If you delete this exception
|
||||
statement from all source files in the program, then also delete it here.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with psycopg2 (see the doc/ directory.)
|
||||
If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
Alternative licenses
|
||||
--------------------
|
||||
|
||||
The following BSD-like license applies (at your option) to the files following
|
||||
the pattern ``psycopg/adapter*.{h,c}`` and ``psycopg/microprotocol*.{h,c}``:
|
||||
|
||||
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.
|
||||
9
MANIFEST.in
Normal file
9
MANIFEST.in
Normal file
@ -0,0 +1,9 @@
|
||||
recursive-include psycopg *.c *.h *.manifest
|
||||
recursive-include lib *.py
|
||||
recursive-include tests *.py
|
||||
include doc/README.rst doc/SUCCESS doc/COPYING.LESSER doc/pep-0249.txt
|
||||
include doc/Makefile doc/requirements.txt
|
||||
recursive-include doc/src *.rst *.py *.css Makefile
|
||||
recursive-include scripts *.py *.sh
|
||||
include AUTHORS README.rst INSTALL LICENSE NEWS
|
||||
include MANIFEST.in setup.py setup.cfg Makefile
|
||||
104
Makefile
Normal file
104
Makefile
Normal file
@ -0,0 +1,104 @@
|
||||
# Makefile for psycopg2. Do you want to...
|
||||
#
|
||||
# Build the library::
|
||||
#
|
||||
# make
|
||||
#
|
||||
# Build the documentation::
|
||||
#
|
||||
# make env (once)
|
||||
# make docs
|
||||
#
|
||||
# Create a source package::
|
||||
#
|
||||
# make sdist
|
||||
#
|
||||
# Run the test::
|
||||
#
|
||||
# make check # this requires setting up a test database with the correct user
|
||||
|
||||
PYTHON := python$(PYTHON_VERSION)
|
||||
PYTHON_VERSION ?= $(shell $(PYTHON) -c 'import sys; print ("%d.%d" % sys.version_info[:2])')
|
||||
BUILD_DIR = $(shell pwd)/build/lib.$(PYTHON_VERSION)
|
||||
|
||||
SOURCE_C := $(wildcard psycopg/*.c psycopg/*.h)
|
||||
SOURCE_PY := $(wildcard lib/*.py)
|
||||
SOURCE_TESTS := $(wildcard tests/*.py)
|
||||
SOURCE_DOC := $(wildcard doc/src/*.rst)
|
||||
SOURCE := $(SOURCE_C) $(SOURCE_PY) $(SOURCE_TESTS) $(SOURCE_DOC)
|
||||
|
||||
PACKAGE := $(BUILD_DIR)/psycopg2
|
||||
PLATLIB := $(PACKAGE)/_psycopg.so
|
||||
PURELIB := $(patsubst lib/%,$(PACKAGE)/%,$(SOURCE_PY))
|
||||
|
||||
BUILD_OPT := --build-lib=$(BUILD_DIR)
|
||||
BUILD_EXT_OPT := --build-lib=$(BUILD_DIR)
|
||||
SDIST_OPT := --formats=gztar
|
||||
|
||||
ifdef PG_CONFIG
|
||||
BUILD_EXT_OPT += --pg-config=$(PG_CONFIG)
|
||||
endif
|
||||
|
||||
VERSION := $(shell grep PSYCOPG_VERSION setup.py | head -1 | sed -e "s/.*'\(.*\)'/\1/")
|
||||
SDIST := dist/psycopg2-$(VERSION).tar.gz
|
||||
|
||||
.PHONY: check clean
|
||||
|
||||
default: package
|
||||
|
||||
all: package sdist
|
||||
|
||||
package: $(PLATLIB) $(PURELIB)
|
||||
|
||||
docs: docs-html
|
||||
|
||||
docs-html: doc/html/genindex.html
|
||||
|
||||
# for PyPI documentation
|
||||
docs-zip: doc/docs.zip
|
||||
|
||||
sdist: $(SDIST)
|
||||
|
||||
env:
|
||||
$(MAKE) -C doc $@
|
||||
|
||||
check:
|
||||
PYTHONPATH=$(BUILD_DIR) $(PYTHON) -c "import tests; tests.unittest.main(defaultTest='tests.test_suite')" --verbose
|
||||
|
||||
testdb:
|
||||
@echo "* Creating $(TESTDB)"
|
||||
@if psql -l | grep -q " $(TESTDB) "; then \
|
||||
dropdb $(TESTDB) >/dev/null; \
|
||||
fi
|
||||
createdb $(TESTDB)
|
||||
# Note to packagers: this requires the postgres user running the test
|
||||
# to be a superuser. You may change this line to use the superuser only
|
||||
# to install the contrib. Feel free to suggest a better way to set up the
|
||||
# testing environment (as the current is enough for development).
|
||||
psql -f `pg_config --sharedir`/contrib/hstore.sql $(TESTDB)
|
||||
|
||||
|
||||
$(PLATLIB): $(SOURCE_C)
|
||||
$(PYTHON) setup.py build_ext $(BUILD_EXT_OPT)
|
||||
|
||||
$(PACKAGE)/%.py: lib/%.py
|
||||
$(PYTHON) setup.py build_py $(BUILD_OPT)
|
||||
touch $@
|
||||
|
||||
$(PACKAGE)/tests/%.py: tests/%.py
|
||||
$(PYTHON) setup.py build_py $(BUILD_OPT)
|
||||
touch $@
|
||||
|
||||
$(SDIST): $(SOURCE)
|
||||
$(PYTHON) setup.py sdist $(SDIST_OPT)
|
||||
|
||||
# docs depend on the build as it partly use introspection.
|
||||
doc/html/genindex.html: $(PLATLIB) $(PURELIB) $(SOURCE_DOC)
|
||||
$(MAKE) -C doc html
|
||||
|
||||
doc/docs.zip: doc/html/genindex.html
|
||||
(cd doc/html && zip -r ../docs.zip *)
|
||||
|
||||
clean:
|
||||
rm -rf build
|
||||
$(MAKE) -C doc clean
|
||||
73
README.rst
Normal file
73
README.rst
Normal file
@ -0,0 +1,73 @@
|
||||
psycopg2 - Python-PostgreSQL Database Adapter
|
||||
=============================================
|
||||
|
||||
Psycopg is the most popular PostgreSQL database adapter for the Python
|
||||
programming language. Its main features are the complete implementation of
|
||||
the Python DB API 2.0 specification and the thread safety (several threads can
|
||||
share the same connection). It was designed for heavily multi-threaded
|
||||
applications that create and destroy lots of cursors and make a large number
|
||||
of concurrent "INSERT"s or "UPDATE"s.
|
||||
|
||||
Psycopg 2 is mostly implemented in C as a libpq wrapper, resulting in being
|
||||
both efficient and secure. It features client-side and server-side cursors,
|
||||
asynchronous communication and notifications, "COPY TO/COPY FROM" support.
|
||||
Many Python types are supported out-of-the-box and adapted to matching
|
||||
PostgreSQL data types; adaptation can be extended and customized thanks to a
|
||||
flexible objects adaptation system.
|
||||
|
||||
Psycopg 2 is both Unicode and Python 3 friendly.
|
||||
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
|
||||
Documentation is included in the ``doc`` directory and is `available online`__.
|
||||
|
||||
.. __: https://www.psycopg.org/docs/
|
||||
|
||||
For any other resource (source code repository, bug tracker, mailing list)
|
||||
please check the `project homepage`__.
|
||||
|
||||
.. __: https://psycopg.org/
|
||||
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
Building Psycopg requires a few prerequisites (a C compiler, some development
|
||||
packages): please check the install_ and the faq_ documents in the ``doc`` dir
|
||||
or online for the details.
|
||||
|
||||
If prerequisites are met, you can install psycopg like any other Python
|
||||
package, using ``pip`` to download it from PyPI_::
|
||||
|
||||
$ pip install psycopg2
|
||||
|
||||
or using ``setup.py`` if you have downloaded the source package locally::
|
||||
|
||||
$ python setup.py build
|
||||
$ sudo python setup.py install
|
||||
|
||||
You can also obtain a stand-alone package, not requiring a compiler or
|
||||
external libraries, by installing the `psycopg2-binary`_ package from PyPI::
|
||||
|
||||
$ pip install psycopg2-binary
|
||||
|
||||
The binary package is a practical choice for development and testing but in
|
||||
production it is advised to use the package built from sources.
|
||||
|
||||
.. _PyPI: https://pypi.org/project/psycopg2/
|
||||
.. _psycopg2-binary: https://pypi.org/project/psycopg2-binary/
|
||||
.. _install: https://www.psycopg.org/docs/install.html#install-from-source
|
||||
.. _faq: https://www.psycopg.org/docs/faq.html#faq-compile
|
||||
|
||||
:Linux/OSX: |gh-actions|
|
||||
:Windows: |appveyor|
|
||||
|
||||
.. |gh-actions| image:: https://github.com/psycopg/psycopg2/actions/workflows/tests.yml/badge.svg
|
||||
:target: https://github.com/psycopg/psycopg2/actions/workflows/tests.yml
|
||||
:alt: Linux and OSX build status
|
||||
|
||||
.. |appveyor| image:: https://ci.appveyor.com/api/projects/status/github/psycopg/psycopg2?branch=master&svg=true
|
||||
:target: https://ci.appveyor.com/project/psycopg/psycopg2/branch/master
|
||||
:alt: Windows build status
|
||||
295
build.sh
Normal file
295
build.sh
Normal file
@ -0,0 +1,295 @@
|
||||
#!/bin/bash
|
||||
#######################################################################
|
||||
# Copyright (c): 2020-2025, Huawei Tech. Co., Ltd.
|
||||
# descript: Compile and pack python driver for openGauss
|
||||
# Return 0 means OK.
|
||||
# Return 1 means failed.
|
||||
# version: 2.0
|
||||
# date: 2020-08-09
|
||||
#######################################################################
|
||||
declare install_package_format='tar'
|
||||
declare serverlib_dir='None'
|
||||
|
||||
#detect platform information.
|
||||
PLATFORM=32
|
||||
bit=$(getconf LONG_BIT)
|
||||
if [ "$bit" -eq 64 ]; then
|
||||
PLATFORM=64
|
||||
fi
|
||||
|
||||
#get OS distributed version.
|
||||
if [ -f "/etc/euleros-release" ]; then
|
||||
kernel=$(cat /etc/euleros-release | awk -F ' ' '{print $1}' | tr A-Z a-z)
|
||||
version=$(cat /etc/euleros-release | awk -F '(' '{print $2}'| awk -F ')' '{print $1}' | tr A-Z a-z)
|
||||
elif [ -f "/etc/openEuler-release" ]; then
|
||||
kernel=$(cat /etc/openEuler-release | awk -F ' ' '{print $1}' | tr A-Z a-z)
|
||||
version=$(cat /etc/openEuler-release | awk -F '(' '{print $2}'| awk -F ')' '{print $1}' | tr A-Z a-z)
|
||||
elif [ -f "/etc/centos-release" ]; then
|
||||
kernel=$(cat /etc/centos-release | awk -F ' ' '{print $1}' | tr A-Z a-z)
|
||||
version=$(cat /etc/centos-release | awk -F '(' '{print $2}'| awk -F ')' '{print $1}' | tr A-Z a-z)
|
||||
elif [ -f "/etc/kylin-release" ]; then
|
||||
kernel=$(cat /etc/kylin-release | awk -F ' ' '{print $1}' | tr A-Z a-z)
|
||||
version=$(cat /etc/kylin-release | awk '{print $6}' | tr A-Z a-z)
|
||||
else
|
||||
kernel=$(lsb_release -d | awk -F ' ' '{print $2}'| tr A-Z a-z)
|
||||
version=$(lsb_release -r | awk -F ' ' '{print $2}')
|
||||
fi
|
||||
|
||||
if [ X"$kernel" == X"euleros" ]; then
|
||||
dist_version="EULER"
|
||||
elif [ X"$kernel" == X"centos" ]; then
|
||||
dist_version="CENTOS"
|
||||
elif [ X"$kernel" == X"openeuler" ]; then
|
||||
dist_version="OPENEULER"
|
||||
elif [ X"$kernel" == X"kylin" ]; then
|
||||
dist_version="KYLIN"
|
||||
else
|
||||
echo "We only support EulerOS, OPENEULER(aarch64) and CentOS platform."
|
||||
echo "Kernel is $kernel"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
##default install version storage path
|
||||
declare db_name_for_package='openGauss'
|
||||
declare version_number='2.0.0'
|
||||
|
||||
if [ $# = 0 ] ; then
|
||||
echo "missing option"
|
||||
print_help
|
||||
exit 1
|
||||
fi
|
||||
|
||||
LOCAL_PATH=${0}
|
||||
FIRST_CHAR=$(expr substr "$LOCAL_PATH" 1 1)
|
||||
if [ "$FIRST_CHAR" = "/" ]; then
|
||||
LOCAL_PATH=${0}
|
||||
else
|
||||
LOCAL_PATH="$(pwd)/${LOCAL_PATH}"
|
||||
fi
|
||||
|
||||
LOCAL_DIR=$(dirname "${LOCAL_PATH}")
|
||||
#########################################################################
|
||||
##read command line paramenters
|
||||
#######################################################################
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
-h|--help)
|
||||
print_help
|
||||
exit 1
|
||||
;;
|
||||
-bd|--serverlib_dir)
|
||||
if [ "$2"X = X ]; then
|
||||
echo "no given binarylib directory values"
|
||||
exit 1
|
||||
fi
|
||||
serverlib_dir=$2
|
||||
shift 2
|
||||
;;
|
||||
*)
|
||||
echo "Internal Error: option processing error: $1" 1>&2
|
||||
echo "please input right paramtenter, the following command may help you"
|
||||
echo "./build.sh --help or ./build.sh -h"
|
||||
exit 1
|
||||
esac
|
||||
done
|
||||
|
||||
#######################################################################
|
||||
## declare all package name
|
||||
#######################################################################
|
||||
declare version_string="${db_name_for_package}-${version_number}"
|
||||
declare package_pre_name="${version_string}-${dist_version}-${PLATFORM}bit"
|
||||
declare python_package_name="${package_pre_name}-Python.${install_package_format}.gz"
|
||||
|
||||
declare BUILD_DIR="${LOCAL_DIR}/build"
|
||||
declare MKGS_OK=0
|
||||
SERVERLIBS_PATH="${serverlib_dir}"
|
||||
PSYCOPG_VERSION=psycopg2-2.9
|
||||
declare LOG_FILE="${LOCAL_DIR}/build_psycopg2.log"
|
||||
declare ERR_MKGS_FAILED=1
|
||||
echo "[makepython] $(date +%y-%m-%d' '%T): script dir : ${LOCAL_DIR}"
|
||||
|
||||
#######################################################################
|
||||
## print help information
|
||||
#######################################################################
|
||||
function print_help()
|
||||
{
|
||||
echo "Usage: $0 [OPTION]
|
||||
-h|--help show help information.
|
||||
-bd|--serverlib_dir the directory of sever binarylibs.
|
||||
"
|
||||
}
|
||||
|
||||
#######################################################################
|
||||
# Print log.
|
||||
#######################################################################
|
||||
log()
|
||||
{
|
||||
echo "[Build psycopg2] $(date +%y-%m-%d' '%T): $@"
|
||||
echo "[Build psycopg2] $(date +%y-%m-%d' '%T): $@" >> "$LOG_FILE" 2>&1
|
||||
}
|
||||
|
||||
#######################################################################
|
||||
# print log and exit.
|
||||
#######################################################################
|
||||
die()
|
||||
{
|
||||
log "$@"
|
||||
echo "$@"
|
||||
exit $ERR_MKGS_FAILED
|
||||
}
|
||||
|
||||
# clean build python log
|
||||
function clean_environment()
|
||||
{
|
||||
|
||||
if [ -f "$LOG_FILE" ]; then
|
||||
rm -rf "$LOG_FILE"
|
||||
fi
|
||||
|
||||
if [ -d "${LOCAL_DIR}/build" ]; then
|
||||
rm -rf ${LOCAL_DIR}/build
|
||||
fi
|
||||
|
||||
echo "clean completely"
|
||||
}
|
||||
|
||||
function check_python()
|
||||
{
|
||||
array=(python3 python2 python)
|
||||
for py in ${array[@]}; do
|
||||
${py} -c 'import setuptools' >> /dev/null 2>&1
|
||||
if [ $? -eq 0 ]; then
|
||||
log "choose python: $(which ${py})"
|
||||
PYTHON=${py}
|
||||
version=$(${py} --version 2>&1)
|
||||
PYTHON_VERSION=python${version:7:1}
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
die "the python in your current enviroment is invalid, please check again and install setuptools."
|
||||
}
|
||||
|
||||
function change_gaussdb_version()
|
||||
{
|
||||
if [ ! -f setup.py ]; then
|
||||
die "not found psycopg2 code in current working directory."
|
||||
fi
|
||||
|
||||
src_text='(pgmajor, pgminor, pgpatch)'
|
||||
dst_text='(9, 2, 4)'
|
||||
sed -i "s/${src_text}/${dst_text}/g" setup.py
|
||||
search_result=$(cat setup.py | grep "$src_text")
|
||||
if [ "${search_result}"X != "X" ]; then
|
||||
die "failed to replace PG_VERSION_NUM at setup.py."
|
||||
fi
|
||||
}
|
||||
|
||||
###############################################################
|
||||
## copy the target to set path
|
||||
###############################################################
|
||||
function target_file_copy()
|
||||
{
|
||||
cd ${LOCAL_DIR}/build/lib*
|
||||
mv psycopg2/_psycopg*.so psycopg2/_psycopg.so
|
||||
|
||||
mkdir -p lib
|
||||
#copy libraries into lib
|
||||
cd ./psycopg2
|
||||
libs=$(ldd _psycopg.so | awk '{print $3}' | grep $GAUSSHOME/lib)
|
||||
for lib in ${libs[@]}; do
|
||||
cp $lib ../lib
|
||||
done
|
||||
cd ..
|
||||
|
||||
}
|
||||
|
||||
#######################################################################
|
||||
# build and install component
|
||||
#######################################################################
|
||||
function build_python()
|
||||
{
|
||||
cd ${LOCAL_DIR}
|
||||
# set GAUSSHOME enviroment variable with BUILD_OPTION
|
||||
export GAUSSHOME=$SERVERLIBS_PATH
|
||||
export LD_LIBRARY_PATH=$GAUSSHOME/lib:$LD_LIBRARY_PATH
|
||||
export PATH=${GAUSSHOME}/bin:${PATH}
|
||||
|
||||
echo "GAUSSHOME: ${GAUSSHOME}"
|
||||
|
||||
change_gaussdb_version
|
||||
check_python
|
||||
${PYTHON} ./setup.py build
|
||||
if [ $? -ne 0 ]; then
|
||||
die "failed to compile psycopg2."
|
||||
fi
|
||||
|
||||
echo "End make python" >> "$LOG_FILE" 2>&1
|
||||
}
|
||||
|
||||
declare package_command
|
||||
#######################################################################
|
||||
##select package command accroding to install_package_format
|
||||
#######################################################################
|
||||
function select_package_command()
|
||||
{
|
||||
case "$install_package_format" in
|
||||
tar)
|
||||
tar='tar'
|
||||
option=' -zcvf'
|
||||
package_command="$tar$option"
|
||||
;;
|
||||
rpm)
|
||||
rpm='rpm'
|
||||
option=' -i'
|
||||
package_command="$rpm$option"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
#######################################################################
|
||||
##function make_package have tree actions
|
||||
##1.copy target file into a newly created temporary directory temp
|
||||
##2.package all file in the temp directory and renome to destination package_path
|
||||
#######################################################################
|
||||
function make_package()
|
||||
{
|
||||
target_file_copy
|
||||
cd ${BUILD_DIR}/lib*
|
||||
select_package_command
|
||||
|
||||
echo "packaging python..."
|
||||
$package_command "${python_package_name}" ./lib ./psycopg2 >>"$LOG_FILE" 2>&1
|
||||
if [ $? -ne 0 ]; then
|
||||
die "$package_command ${python_package_name} failed"
|
||||
fi
|
||||
|
||||
mv ${python_package_name} ${BUILD_DIR}/
|
||||
|
||||
echo "install python tools is ${python_package_name} of ${BUILD_DIR} directory " >> "$LOG_FILE" 2>&1
|
||||
echo "success!"
|
||||
}
|
||||
|
||||
#############################################################
|
||||
# main function
|
||||
#############################################################
|
||||
|
||||
# 1. clean environment
|
||||
echo "clean enviroment"
|
||||
echo "[makedb] $(date +%y-%m-%d' '%T): remove ${BUILD_DIR}" >>"$LOG_FILE" 2>&1
|
||||
clean_environment
|
||||
|
||||
|
||||
# 2. build python
|
||||
build_python
|
||||
|
||||
# 3. make python package
|
||||
make_package
|
||||
|
||||
# 4. cp python package to output
|
||||
mkdir ${LOCAL_DIR}/output
|
||||
mv ${BUILD_DIR}/*.tar.gz ${LOCAL_DIR}/output/
|
||||
|
||||
echo "now, python driver package has finished!"
|
||||
|
||||
exit 0
|
||||
|
||||
8
doc/.gitignore
vendored
Normal file
8
doc/.gitignore
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
env
|
||||
src/_build/*
|
||||
html/*
|
||||
psycopg2.txt
|
||||
src/sqlstate_errors.rst
|
||||
|
||||
# Added by psycopg-website to customize published docs
|
||||
src/_templates/layout.html
|
||||
165
doc/COPYING.LESSER
Normal file
165
doc/COPYING.LESSER
Normal file
@ -0,0 +1,165 @@
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
|
||||
This version of the GNU Lesser General Public License incorporates
|
||||
the terms and conditions of version 3 of the GNU General Public
|
||||
License, supplemented by the additional permissions listed below.
|
||||
|
||||
0. Additional Definitions.
|
||||
|
||||
As used herein, "this License" refers to version 3 of the GNU Lesser
|
||||
General Public License, and the "GNU GPL" refers to version 3 of the GNU
|
||||
General Public License.
|
||||
|
||||
"The Library" refers to a covered work governed by this License,
|
||||
other than an Application or a Combined Work as defined below.
|
||||
|
||||
An "Application" is any work that makes use of an interface provided
|
||||
by the Library, but which is not otherwise based on the Library.
|
||||
Defining a subclass of a class defined by the Library is deemed a mode
|
||||
of using an interface provided by the Library.
|
||||
|
||||
A "Combined Work" is a work produced by combining or linking an
|
||||
Application with the Library. The particular version of the Library
|
||||
with which the Combined Work was made is also called the "Linked
|
||||
Version".
|
||||
|
||||
The "Minimal Corresponding Source" for a Combined Work means the
|
||||
Corresponding Source for the Combined Work, excluding any source code
|
||||
for portions of the Combined Work that, considered in isolation, are
|
||||
based on the Application, and not on the Linked Version.
|
||||
|
||||
The "Corresponding Application Code" for a Combined Work means the
|
||||
object code and/or source code for the Application, including any data
|
||||
and utility programs needed for reproducing the Combined Work from the
|
||||
Application, but excluding the System Libraries of the Combined Work.
|
||||
|
||||
1. Exception to Section 3 of the GNU GPL.
|
||||
|
||||
You may convey a covered work under sections 3 and 4 of this License
|
||||
without being bound by section 3 of the GNU GPL.
|
||||
|
||||
2. Conveying Modified Versions.
|
||||
|
||||
If you modify a copy of the Library, and, in your modifications, a
|
||||
facility refers to a function or data to be supplied by an Application
|
||||
that uses the facility (other than as an argument passed when the
|
||||
facility is invoked), then you may convey a copy of the modified
|
||||
version:
|
||||
|
||||
a) under this License, provided that you make a good faith effort to
|
||||
ensure that, in the event an Application does not supply the
|
||||
function or data, the facility still operates, and performs
|
||||
whatever part of its purpose remains meaningful, or
|
||||
|
||||
b) under the GNU GPL, with none of the additional permissions of
|
||||
this License applicable to that copy.
|
||||
|
||||
3. Object Code Incorporating Material from Library Header Files.
|
||||
|
||||
The object code form of an Application may incorporate material from
|
||||
a header file that is part of the Library. You may convey such object
|
||||
code under terms of your choice, provided that, if the incorporated
|
||||
material is not limited to numerical parameters, data structure
|
||||
layouts and accessors, or small macros, inline functions and templates
|
||||
(ten or fewer lines in length), you do both of the following:
|
||||
|
||||
a) Give prominent notice with each copy of the object code that the
|
||||
Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
|
||||
b) Accompany the object code with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
4. Combined Works.
|
||||
|
||||
You may convey a Combined Work under terms of your choice that,
|
||||
taken together, effectively do not restrict modification of the
|
||||
portions of the Library contained in the Combined Work and reverse
|
||||
engineering for debugging such modifications, if you also do each of
|
||||
the following:
|
||||
|
||||
a) Give prominent notice with each copy of the Combined Work that
|
||||
the Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
|
||||
b) Accompany the Combined Work with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
c) For a Combined Work that displays copyright notices during
|
||||
execution, include the copyright notice for the Library among
|
||||
these notices, as well as a reference directing the user to the
|
||||
copies of the GNU GPL and this license document.
|
||||
|
||||
d) Do one of the following:
|
||||
|
||||
0) Convey the Minimal Corresponding Source under the terms of this
|
||||
License, and the Corresponding Application Code in a form
|
||||
suitable for, and under terms that permit, the user to
|
||||
recombine or relink the Application with a modified version of
|
||||
the Linked Version to produce a modified Combined Work, in the
|
||||
manner specified by section 6 of the GNU GPL for conveying
|
||||
Corresponding Source.
|
||||
|
||||
1) Use a suitable shared library mechanism for linking with the
|
||||
Library. A suitable mechanism is one that (a) uses at run time
|
||||
a copy of the Library already present on the user's computer
|
||||
system, and (b) will operate properly with a modified version
|
||||
of the Library that is interface-compatible with the Linked
|
||||
Version.
|
||||
|
||||
e) Provide Installation Information, but only if you would otherwise
|
||||
be required to provide such information under section 6 of the
|
||||
GNU GPL, and only to the extent that such information is
|
||||
necessary to install and execute a modified version of the
|
||||
Combined Work produced by recombining or relinking the
|
||||
Application with a modified version of the Linked Version. (If
|
||||
you use option 4d0, the Installation Information must accompany
|
||||
the Minimal Corresponding Source and Corresponding Application
|
||||
Code. If you use option 4d1, you must provide the Installation
|
||||
Information in the manner specified by section 6 of the GNU GPL
|
||||
for conveying Corresponding Source.)
|
||||
|
||||
5. Combined Libraries.
|
||||
|
||||
You may place library facilities that are a work based on the
|
||||
Library side by side in a single library together with other library
|
||||
facilities that are not Applications and are not covered by this
|
||||
License, and convey such a combined library under terms of your
|
||||
choice, if you do both of the following:
|
||||
|
||||
a) Accompany the combined library with a copy of the same work based
|
||||
on the Library, uncombined with any other library facilities,
|
||||
conveyed under the terms of this License.
|
||||
|
||||
b) Give prominent notice with the combined library that part of it
|
||||
is a work based on the Library, and explaining where to find the
|
||||
accompanying uncombined form of the same work.
|
||||
|
||||
6. Revised Versions of the GNU Lesser General Public License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions
|
||||
of the GNU Lesser General Public License from time to time. Such new
|
||||
versions will be similar in spirit to the present version, but may
|
||||
differ in detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Library as you received it specifies that a certain numbered version
|
||||
of the GNU Lesser General Public License "or any later version"
|
||||
applies to it, you have the option of following the terms and
|
||||
conditions either of that published version or of any later version
|
||||
published by the Free Software Foundation. If the Library as you
|
||||
received it does not specify a version number of the GNU Lesser
|
||||
General Public License, you may choose any version of the GNU Lesser
|
||||
General Public License ever published by the Free Software Foundation.
|
||||
|
||||
If the Library as you received it specifies that a proxy can decide
|
||||
whether future versions of the GNU Lesser General Public License shall
|
||||
apply, that proxy's public statement of acceptance of any version is
|
||||
permanent authorization for you to choose that version for the
|
||||
Library.
|
||||
39
doc/Makefile
Normal file
39
doc/Makefile
Normal file
@ -0,0 +1,39 @@
|
||||
.PHONY: env help clean html package doctest
|
||||
|
||||
docs: html
|
||||
|
||||
check: doctest
|
||||
|
||||
# The environment is currently required to build the documentation.
|
||||
# It is not clean by 'make clean'
|
||||
|
||||
PYTHON := python$(PYTHON_VERSION)
|
||||
PYTHON_VERSION ?= $(shell $(PYTHON) -c 'import sys; print ("%d.%d" % sys.version_info[:2])')
|
||||
BUILD_DIR = $(shell pwd)/../build/lib.$(PYTHON_VERSION)
|
||||
|
||||
SPHINXBUILD ?= $$(pwd)/env/bin/sphinx-build
|
||||
SPHOPTS = SPHINXBUILD=$(SPHINXBUILD)
|
||||
|
||||
html: package src/sqlstate_errors.rst
|
||||
$(MAKE) $(SPHOPTS) -C src $@
|
||||
cp -r src/_build/html .
|
||||
|
||||
src/sqlstate_errors.rst: ../psycopg/sqlstate_errors.h $(BUILD_DIR)
|
||||
env/bin/python src/tools/make_sqlstate_docs.py $< > $@
|
||||
|
||||
$(BUILD_DIR):
|
||||
$(MAKE) PYTHON=$(PYTHON) -C .. package
|
||||
|
||||
doctest:
|
||||
$(MAKE) PYTHON=$(PYTHON) -C .. package
|
||||
$(MAKE) $(SPHOPTS) -C src $@
|
||||
|
||||
clean:
|
||||
$(MAKE) $(SPHOPTS) -C src $@
|
||||
rm -rf html src/sqlstate_errors.rst
|
||||
|
||||
env: requirements.txt
|
||||
virtualenv -p $(PYTHON) env
|
||||
./env/bin/pip install -r requirements.txt
|
||||
echo "$$(pwd)/../build/lib.$(PYTHON_VERSION)" \
|
||||
> env/lib/python$(PYTHON_VERSION)/site-packages/psycopg.pth
|
||||
20
doc/README.rst
Normal file
20
doc/README.rst
Normal file
@ -0,0 +1,20 @@
|
||||
How to build psycopg documentation
|
||||
----------------------------------
|
||||
|
||||
Building the documentation usually requires building the library too for
|
||||
introspection, so you will need the same prerequisites_. The only extra
|
||||
prerequisite is virtualenv_: the packages needed to build the docs will be
|
||||
installed when building the env.
|
||||
|
||||
.. _prerequisites: https://www.psycopg.org/docs/install.html#install-from-source
|
||||
.. _virtualenv: https://virtualenv.pypa.io/en/latest/
|
||||
|
||||
Build the env once with::
|
||||
|
||||
make env
|
||||
|
||||
Then you can build the documentation with::
|
||||
|
||||
make
|
||||
|
||||
You should find the rendered documentation in the ``html`` directory.
|
||||
114
doc/SUCCESS
Normal file
114
doc/SUCCESS
Normal file
@ -0,0 +1,114 @@
|
||||
From: Jack Moffitt <jack@xiph.org>
|
||||
To: Psycopg Mailing List <psycopg@lists.initd.org>
|
||||
Subject: Re: [Psycopg] preparing for 1.0
|
||||
Date: 22 Oct 2001 11:16:21 -0600
|
||||
|
||||
www.vorbis.com is serving from 5-10k pages per day with psycopg serving
|
||||
data for most of that.
|
||||
|
||||
I plan to use it for several of our other sites, so that number will
|
||||
increase.
|
||||
|
||||
I've never had a single problem (that wasn't my fault) besides those
|
||||
segfaults, and those are now gone as well, and I've been using psycopg
|
||||
since June (around 0.99.2?).
|
||||
|
||||
jack.
|
||||
|
||||
|
||||
From: Yury Don <gercon@vpcit.ru>
|
||||
To: Psycopg Mailing List <psycopg@lists.initd.org>
|
||||
Subject: Re: [Psycopg] preparing for 1.0
|
||||
Date: 23 Oct 2001 09:53:11 +0600
|
||||
|
||||
We use psycopg and psycopg zope adapter since fisrt public
|
||||
release (it seems version 0.4). Now it works on 3 our sites and in intranet
|
||||
applications. We had few problems, but all problems were quickly
|
||||
solved. The strong side of psycopg is that it's code is well organized
|
||||
and easy to understand. When I found a problem with non-ISO datestyle in first
|
||||
version of psycopg, it took for me 15 or 20 minutes to learn code and
|
||||
to solve the problem, even thouth my knowledge of c were poor.
|
||||
|
||||
BTW, segfault with dictfetchall on particular data set (see [Psycopg]
|
||||
dictfetchXXX() problems) disappeared in 0.99.8pre2.
|
||||
|
||||
--
|
||||
Best regards,
|
||||
Yury Don
|
||||
|
||||
|
||||
From: Tom Jenkins <tjenkins@devis.com>
|
||||
To: Federico Di Gregorio <fog@debian.org>
|
||||
Cc: Psycopg Mailing List <psycopg@lists.initd.org>
|
||||
Subject: Re: [Psycopg] preparing for 1.0
|
||||
Date: 23 Oct 2001 08:25:52 -0400
|
||||
|
||||
The US Govt Department of Labor's Office of Disability Employment
|
||||
Policy's DisabilityDirect website is run on zope and zpsycopg.
|
||||
|
||||
|
||||
From: Scott Leerssen <sleerssen@racemi.com>
|
||||
To: Federico Di Gregorio <fog@debian.org>
|
||||
Subject: Re: [Psycopg] preparing for 1.0
|
||||
Date: 23 Oct 2001 09:56:10 -0400
|
||||
|
||||
Racemi's load management software infrastructure uses psycopg to handle
|
||||
complex server allocation decisions, plus storage and access of
|
||||
environmental conditions and accounting records for potentially
|
||||
thousands of servers. Psycopg has, to this point, been the only
|
||||
Python/PostGreSQL interface that could handle the scaling required for
|
||||
our multithreaded applications.
|
||||
|
||||
Scott
|
||||
|
||||
|
||||
From: Andre Schubert <andre.schubert@geyer.kabeljournal.de>
|
||||
To: Federico Di Gregorio <fog@debian.org>
|
||||
Cc: Psycopg Mailing List <psycopg@lists.initd.org>
|
||||
Subject: Re: [Psycopg] preparing for 1.0
|
||||
Date: 23 Oct 2001 11:46:07 +0200
|
||||
|
||||
i have changed the psycopg version to 0.99.8pre2 on all devel-machines
|
||||
and all segfaults are gone. after my holiday i wil change to 0.99.8pre2
|
||||
or 1.0 on our production-server.
|
||||
this server contains several web-sites which are all connected to
|
||||
postgres over ZPsycopgDA.
|
||||
|
||||
thanks as
|
||||
|
||||
|
||||
From: Fred Wilson Horch <fhorch@ecoaccess.org>
|
||||
To: <psycopg@lists.initd.org>
|
||||
Subject: [Psycopg] Success story for psycopg
|
||||
Date: 23 Oct 2001 10:59:17 -0400
|
||||
|
||||
Due to various quirks of PyGreSQL and PoPy, EcoAccess has been looking for
|
||||
a reliable, fast and relatively bug-free Python-PostgreSQL interface for
|
||||
our project.
|
||||
|
||||
Binary support in psycopg, along with the umlimited tuple size in
|
||||
PostgreSQL 7.1, allowed us to quickly prototype a database-backed file
|
||||
storage web application, which we're using for file sharing among our
|
||||
staff and volunteers. Using a database backend instead of a file system
|
||||
allows us to easily enrich the meta-information associated with each file
|
||||
and simplifies our data handling routines.
|
||||
|
||||
We've been impressed by the responsiveness of the psycopg team to bug
|
||||
reports and feature requests, and we're looking forward to using psycopg
|
||||
as the Python interface for additional database-backed web applications.
|
||||
|
||||
Keep up the good work!
|
||||
--
|
||||
Fred Wilson Horch mailto:fhorch@ecoaccess.org
|
||||
Executive Director, EcoAccess http://ecoaccess.org/
|
||||
|
||||
|
||||
From: Damon Fasching <fasching@design.lbl.gov>
|
||||
To: Michele Comitini <mcm@glisco.it>
|
||||
Cc: fog@debian.org
|
||||
Subject: Re: How does one create a database within Python using psycopg?
|
||||
Date: 25 Feb 2002 17:39:41 -0800
|
||||
|
||||
[snip]
|
||||
btw I checked out 4 different Python-PostgreSQL packages. psycopg is the
|
||||
only one which built and imported w/o any trouble! (At least for me.)
|
||||
1005
doc/pep-0249.txt
Normal file
1005
doc/pep-0249.txt
Normal file
File diff suppressed because it is too large
Load Diff
82
doc/release.rst
Normal file
82
doc/release.rst
Normal file
@ -0,0 +1,82 @@
|
||||
How to make a psycopg2 release
|
||||
==============================
|
||||
|
||||
- Edit ``setup.py`` and set a stable version release. Use PEP 440 to choose
|
||||
version numbers, e.g.
|
||||
|
||||
- ``2.7``: a new major release, new features
|
||||
- ``2.7.1``: a bugfix release
|
||||
- ``2.7.1.1``: a release to fix packaging problems
|
||||
- ``2.7.2.dev0``: version held during development, non-public test packages...
|
||||
- ``2.8b1``: a beta for public tests
|
||||
|
||||
In the rest of this document we assume you have exported the version number
|
||||
into an environment variable, e.g.::
|
||||
|
||||
$ export VERSION=2.8.4
|
||||
|
||||
- Push psycopg2 to master or to the maint branch. Make sure tests on `GitHub
|
||||
Actions`__ and AppVeyor__ pass.
|
||||
|
||||
.. __: https://github.com/psycopg/psycopg2/actions/workflows/tests.yml
|
||||
.. __: https://ci.appveyor.com/project/psycopg/psycopg2
|
||||
|
||||
- Create a signed tag with the content of the relevant NEWS bit and push it.
|
||||
E.g.::
|
||||
|
||||
# Tag name will be 2_8_4
|
||||
$ git tag -a -s ${VERSION//\./_}
|
||||
|
||||
Psycopg 2.8.4 released
|
||||
|
||||
What's new in psycopg 2.8.4
|
||||
---------------------------
|
||||
|
||||
New features:
|
||||
|
||||
- Fixed bug blah (:ticket:`#42`).
|
||||
...
|
||||
|
||||
- Create the packages:
|
||||
|
||||
- On GitHub Actions run manually a `package build workflow`__.
|
||||
|
||||
- On Appveyor change the `build settings`__ and replace the custom
|
||||
configuration file name from ``.appveyor/tests.yml`` to
|
||||
``.appveyor/packages.yml`` (yeah, that sucks a bit. Remember to put it
|
||||
back to testing).
|
||||
|
||||
.. __: https://github.com/psycopg/psycopg2/actions/workflows/packages.yml
|
||||
.. __: https://ci.appveyor.com/project/psycopg/psycopg2/settings
|
||||
|
||||
- When the workflows have finished download the packages using the
|
||||
``download_packages_{github|appveyor}.py`` scripts from the
|
||||
``scripts/build`` directory. They will be saved in a
|
||||
``packages/psycopg2-${VERSION}`` directory.
|
||||
|
||||
- Remove the ``.exe`` from the dir, because we don't want to upload them on
|
||||
PyPI::
|
||||
|
||||
$ rm -v psycopg2-${VERSION}/*.exe
|
||||
|
||||
- Only for stable packages: upload the signed packages on PyPI::
|
||||
|
||||
$ twine upload -s psycopg2-${VERSION}/*
|
||||
|
||||
- Create a release and release notes in the psycopg website, announce to
|
||||
psycopg and pgsql-announce mailing lists.
|
||||
|
||||
- Edit ``setup.py`` changing the version again (e.g. go to ``2.8.5.dev0``).
|
||||
|
||||
|
||||
Releasing test packages
|
||||
-----------------------
|
||||
|
||||
Test packages may be uploaded on the `PyPI testing site`__ using::
|
||||
|
||||
$ twine upload -s -r testpypi psycopg2-${VERSION}/*
|
||||
|
||||
assuming `proper configuration`__ of ``~/.pypirc``.
|
||||
|
||||
.. __: https://test.pypi.org/project/psycopg2/
|
||||
.. __: https://wiki.python.org/moin/TestPyPI
|
||||
8
doc/requirements.txt
Normal file
8
doc/requirements.txt
Normal file
@ -0,0 +1,8 @@
|
||||
# Packages only needed to build the docs
|
||||
Pygments>=2.2,<2.3
|
||||
Sphinx>=1.6,<=1.7
|
||||
sphinx-better-theme>=0.1.5,<0.2
|
||||
|
||||
# 0.15.2 affected by https://sourceforge.net/p/docutils/bugs/353/
|
||||
# Can update to 0.16 after release (currently in rc) but must update Sphinx too
|
||||
docutils<0.15
|
||||
99
doc/src/Makefile
Normal file
99
doc/src/Makefile
Normal file
@ -0,0 +1,99 @@
|
||||
# Makefile for Sphinx documentation
|
||||
#
|
||||
|
||||
# You can set these variables from the command line.
|
||||
SPHINXOPTS =
|
||||
SPHINXBUILD = sphinx-build
|
||||
PAPER =
|
||||
BUILDDIR = _build
|
||||
|
||||
# DSN for the doctest database
|
||||
PSYCOPG2_DSN="user=postgres dbname=test"
|
||||
|
||||
# Internal variables.
|
||||
PAPEROPT_a4 = -D latex_paper_size=a4
|
||||
PAPEROPT_letter = -D latex_paper_size=letter
|
||||
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
|
||||
|
||||
.PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest
|
||||
|
||||
help:
|
||||
@echo "Please use \`make <target>' where <target> is one of"
|
||||
@echo " html to make standalone HTML files"
|
||||
@echo " dirhtml to make HTML files named index.html in directories"
|
||||
@echo " pickle to make pickle files"
|
||||
@echo " json to make JSON files"
|
||||
@echo " htmlhelp to make HTML files and a HTML help project"
|
||||
@echo " qthelp to make HTML files and a qthelp project"
|
||||
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
|
||||
@echo " changes to make an overview of all changed/added/deprecated items"
|
||||
@echo " linkcheck to check all external links for integrity"
|
||||
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
|
||||
|
||||
clean:
|
||||
-rm -rf $(BUILDDIR)/*
|
||||
-rm -rf ./html/*
|
||||
|
||||
html:
|
||||
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
|
||||
|
||||
dirhtml:
|
||||
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
|
||||
|
||||
text:
|
||||
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
|
||||
@echo
|
||||
@echo "Build finished. The text pages are in $(BUILDDIR)/text."
|
||||
|
||||
pickle:
|
||||
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
|
||||
@echo
|
||||
@echo "Build finished; now you can process the pickle files."
|
||||
|
||||
json:
|
||||
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
|
||||
@echo
|
||||
@echo "Build finished; now you can process the JSON files."
|
||||
|
||||
htmlhelp:
|
||||
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
|
||||
@echo
|
||||
@echo "Build finished; now you can run HTML Help Workshop with the" \
|
||||
".hhp project file in $(BUILDDIR)/htmlhelp."
|
||||
|
||||
qthelp:
|
||||
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
|
||||
@echo
|
||||
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
|
||||
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
|
||||
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/psycopg.qhcp"
|
||||
@echo "To view the help file:"
|
||||
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/psycopg.qhc"
|
||||
|
||||
latex:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo
|
||||
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
|
||||
@echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \
|
||||
"run these through (pdf)latex."
|
||||
|
||||
changes:
|
||||
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
|
||||
@echo
|
||||
@echo "The overview file is in $(BUILDDIR)/changes."
|
||||
|
||||
linkcheck:
|
||||
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
|
||||
@echo
|
||||
@echo "Link check complete; look for any errors in the above output " \
|
||||
"or in $(BUILDDIR)/linkcheck/output.txt."
|
||||
|
||||
doctest:
|
||||
PSYCOPG2_DSN=$(PSYCOPG2_DSN) \
|
||||
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
|
||||
@echo "Testing of doctests in the sources finished, look at the " \
|
||||
"results in $(BUILDDIR)/doctest/output.txt."
|
||||
136
doc/src/_static/psycopg.css
Normal file
136
doc/src/_static/psycopg.css
Normal file
@ -0,0 +1,136 @@
|
||||
blockquote {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
div.admonition-todo {
|
||||
background-color: #ffa;
|
||||
border: 1px solid #ee2;
|
||||
}
|
||||
|
||||
div.dbapi-extension {
|
||||
background-color: #eef;
|
||||
border: 1px solid #aaf;
|
||||
}
|
||||
|
||||
code.sql,
|
||||
tt.sql {
|
||||
font-size: 1em;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
a > code.sql,
|
||||
a > tt.sql {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
a > code.sql:hover,
|
||||
a > tt.sql:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
dl.faq dt {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
table.data-types div.line-block {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
|
||||
/* better theme customisation */
|
||||
|
||||
body {
|
||||
background-color: #216464;
|
||||
}
|
||||
|
||||
header, .related, .document, footer {
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
header h1 {
|
||||
font-size: 150%;
|
||||
margin-bottom: 0;
|
||||
padding: 0.5rem 10px 0.5rem 10px;
|
||||
}
|
||||
|
||||
h1, h2, h3 {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.body h1, .body h2, .body h3 {
|
||||
color: #074848;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 200%;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 160%;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 140%;
|
||||
}
|
||||
|
||||
footer#pagefooter {
|
||||
margin-bottom: 1rem;
|
||||
font-size: 85%;
|
||||
color: #444;
|
||||
}
|
||||
|
||||
#rellinks, #breadcrumbs {
|
||||
padding-right: 10px;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.sphinxsidebar {
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.bodywrapper {
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
div.body h1, div.body h2, div.body h3 {
|
||||
background-color: #f2f2f2;
|
||||
border-bottom: 1px solid #d0d0d0;
|
||||
}
|
||||
|
||||
div.body p.rubric {
|
||||
border-bottom: 1px solid #d0d0d0;
|
||||
}
|
||||
|
||||
body .sphinxsidebar .search {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
html pre {
|
||||
background-color: #efc;
|
||||
border: 1px solid #ac9;
|
||||
border-left: none;
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
a, a:visited {
|
||||
color: #0b6868;
|
||||
}
|
||||
|
||||
th {
|
||||
background-color: #ede;
|
||||
}
|
||||
|
||||
code.xref, a code {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
code.descname {
|
||||
font-weight: bold;
|
||||
font-size: 120%;
|
||||
}
|
||||
|
||||
@media (max-width: 820px) {
|
||||
body {
|
||||
background-color: white;
|
||||
}
|
||||
}
|
||||
6
doc/src/_templates/searchbox.html
Normal file
6
doc/src/_templates/searchbox.html
Normal file
@ -0,0 +1,6 @@
|
||||
{# Add a title over the search box #}
|
||||
|
||||
{%- if pagename != "search" %}
|
||||
<h3>Quick search</h3>
|
||||
{%- include "!searchbox.html" %}
|
||||
{%- endif %}
|
||||
599
doc/src/advanced.rst
Normal file
599
doc/src/advanced.rst
Normal file
@ -0,0 +1,599 @@
|
||||
More advanced topics
|
||||
====================
|
||||
|
||||
.. sectionauthor:: Daniele Varrazzo <daniele.varrazzo@gmail.com>
|
||||
|
||||
.. testsetup:: *
|
||||
|
||||
import re
|
||||
import select
|
||||
|
||||
cur.execute("CREATE TABLE atable (apoint point)")
|
||||
conn.commit()
|
||||
|
||||
def wait(conn):
|
||||
while True:
|
||||
state = conn.poll()
|
||||
if state == psycopg2.extensions.POLL_OK:
|
||||
break
|
||||
elif state == psycopg2.extensions.POLL_WRITE:
|
||||
select.select([], [conn.fileno()], [])
|
||||
elif state == psycopg2.extensions.POLL_READ:
|
||||
select.select([conn.fileno()], [], [])
|
||||
else:
|
||||
raise psycopg2.OperationalError("poll() returned %s" % state)
|
||||
|
||||
aconn = psycopg2.connect(database='test', async=1)
|
||||
wait(aconn)
|
||||
acurs = aconn.cursor()
|
||||
|
||||
|
||||
.. index::
|
||||
double: Subclassing; Cursor
|
||||
double: Subclassing; Connection
|
||||
|
||||
.. _subclassing-connection:
|
||||
.. _subclassing-cursor:
|
||||
|
||||
Connection and cursor factories
|
||||
-------------------------------
|
||||
|
||||
Psycopg exposes two new-style classes that can be sub-classed and expanded to
|
||||
adapt them to the needs of the programmer: `psycopg2.extensions.cursor`
|
||||
and `psycopg2.extensions.connection`. The `connection` class is
|
||||
usually sub-classed only to provide an easy way to create customized cursors
|
||||
but other uses are possible. `cursor` is much more interesting, because
|
||||
it is the class where query building, execution and result type-casting into
|
||||
Python variables happens.
|
||||
|
||||
The `~psycopg2.extras` module contains several examples of :ref:`connection
|
||||
and cursor subclasses <cursor-subclasses>`.
|
||||
|
||||
.. note::
|
||||
|
||||
If you only need a customized cursor class, since Psycopg 2.5 you can use
|
||||
the `~connection.cursor_factory` parameter of a regular connection instead
|
||||
of creating a new `!connection` subclass.
|
||||
|
||||
|
||||
.. index::
|
||||
single: Example; Cursor subclass
|
||||
|
||||
An example of cursor subclass performing logging is::
|
||||
|
||||
import psycopg2
|
||||
import psycopg2.extensions
|
||||
import logging
|
||||
|
||||
class LoggingCursor(psycopg2.extensions.cursor):
|
||||
def execute(self, sql, args=None):
|
||||
logger = logging.getLogger('sql_debug')
|
||||
logger.info(self.mogrify(sql, args))
|
||||
|
||||
try:
|
||||
psycopg2.extensions.cursor.execute(self, sql, args)
|
||||
except Exception, exc:
|
||||
logger.error("%s: %s" % (exc.__class__.__name__, exc))
|
||||
raise
|
||||
|
||||
conn = psycopg2.connect(DSN)
|
||||
cur = conn.cursor(cursor_factory=LoggingCursor)
|
||||
cur.execute("INSERT INTO mytable VALUES (%s, %s, %s);",
|
||||
(10, 20, 30))
|
||||
|
||||
|
||||
|
||||
.. index::
|
||||
single: Objects; Creating new adapters
|
||||
single: Adaptation; Creating new adapters
|
||||
single: Data types; Creating new adapters
|
||||
|
||||
.. _adapting-new-types:
|
||||
|
||||
Adapting new Python types to SQL syntax
|
||||
---------------------------------------
|
||||
|
||||
Any Python class or type can be adapted to an SQL string. Adaptation mechanism
|
||||
is similar to the Object Adaptation proposed in the :pep:`246` and is exposed
|
||||
by the `psycopg2.extensions.adapt()` function.
|
||||
|
||||
The `~cursor.execute()` method adapts its arguments to the
|
||||
`~psycopg2.extensions.ISQLQuote` protocol. Objects that conform to this
|
||||
protocol expose a `!getquoted()` method returning the SQL representation
|
||||
of the object as a string (the method must return `!bytes` in Python 3).
|
||||
Optionally the conform object may expose a
|
||||
`~psycopg2.extensions.ISQLQuote.prepare()` method.
|
||||
|
||||
There are two basic ways to have a Python object adapted to SQL:
|
||||
|
||||
- the object itself is conform, or knows how to make itself conform. Such
|
||||
object must expose a `__conform__()` method that will be called with the
|
||||
protocol object as argument. The object can check that the protocol is
|
||||
`!ISQLQuote`, in which case it can return `!self` (if the object also
|
||||
implements `!getquoted()`) or a suitable wrapper object. This option is
|
||||
viable if you are the author of the object and if the object is specifically
|
||||
designed for the database (i.e. having Psycopg as a dependency and polluting
|
||||
its interface with the required methods doesn't bother you). For a simple
|
||||
example you can take a look at the source code for the
|
||||
`psycopg2.extras.Inet` object.
|
||||
|
||||
- If implementing the `!ISQLQuote` interface directly in the object is not an
|
||||
option (maybe because the object to adapt comes from a third party library),
|
||||
you can use an *adaptation function*, taking the object to be adapted as
|
||||
argument and returning a conforming object. The adapter must be
|
||||
registered via the `~psycopg2.extensions.register_adapter()` function. A
|
||||
simple example wrapper is `!psycopg2.extras.UUID_adapter` used by the
|
||||
`~psycopg2.extras.register_uuid()` function.
|
||||
|
||||
A convenient object to write adapters is the `~psycopg2.extensions.AsIs`
|
||||
wrapper, whose `!getquoted()` result is simply the `!str()`\ ing conversion of
|
||||
the wrapped object.
|
||||
|
||||
.. index::
|
||||
single: Example; Types adaptation
|
||||
|
||||
Example: mapping of a `!Point` class into the |point|_ PostgreSQL
|
||||
geometric type:
|
||||
|
||||
.. doctest::
|
||||
|
||||
>>> from psycopg2.extensions import adapt, register_adapter, AsIs
|
||||
|
||||
>>> class Point(object):
|
||||
... def __init__(self, x, y):
|
||||
... self.x = x
|
||||
... self.y = y
|
||||
|
||||
>>> def adapt_point(point):
|
||||
... x = adapt(point.x).getquoted()
|
||||
... y = adapt(point.y).getquoted()
|
||||
... return AsIs("'(%s, %s)'" % (x, y))
|
||||
|
||||
>>> register_adapter(Point, adapt_point)
|
||||
|
||||
>>> cur.execute("INSERT INTO atable (apoint) VALUES (%s)",
|
||||
... (Point(1.23, 4.56),))
|
||||
|
||||
|
||||
.. |point| replace:: :sql:`point`
|
||||
.. _point: https://www.postgresql.org/docs/current/static/datatype-geometric.html#DATATYPE-GEOMETRIC
|
||||
|
||||
The above function call results in the SQL command::
|
||||
|
||||
INSERT INTO atable (apoint) VALUES ('(1.23, 4.56)');
|
||||
|
||||
|
||||
|
||||
.. index:: Type casting
|
||||
|
||||
.. _type-casting-from-sql-to-python:
|
||||
|
||||
Type casting of SQL types into Python objects
|
||||
---------------------------------------------
|
||||
|
||||
PostgreSQL objects read from the database can be adapted to Python objects
|
||||
through an user-defined adapting function. An adapter function takes two
|
||||
arguments: the object string representation as returned by PostgreSQL and the
|
||||
cursor currently being read, and should return a new Python object. For
|
||||
example, the following function parses the PostgreSQL :sql:`point`
|
||||
representation into the previously defined `!Point` class:
|
||||
|
||||
>>> def cast_point(value, cur):
|
||||
... if value is None:
|
||||
... return None
|
||||
...
|
||||
... # Convert from (f1, f2) syntax using a regular expression.
|
||||
... m = re.match(r"\(([^)]+),([^)]+)\)", value)
|
||||
... if m:
|
||||
... return Point(float(m.group(1)), float(m.group(2)))
|
||||
... else:
|
||||
... raise InterfaceError("bad point representation: %r" % value)
|
||||
|
||||
|
||||
In order to create a mapping from a PostgreSQL type (either standard or
|
||||
user-defined), its OID must be known. It can be retrieved either by the second
|
||||
column of the `cursor.description`:
|
||||
|
||||
>>> cur.execute("SELECT NULL::point")
|
||||
>>> point_oid = cur.description[0][1]
|
||||
>>> point_oid
|
||||
600
|
||||
|
||||
or by querying the system catalog for the type name and namespace (the
|
||||
namespace for system objects is :sql:`pg_catalog`):
|
||||
|
||||
>>> cur.execute("""
|
||||
... SELECT pg_type.oid
|
||||
... FROM pg_type JOIN pg_namespace
|
||||
... ON typnamespace = pg_namespace.oid
|
||||
... WHERE typname = %(typename)s
|
||||
... AND nspname = %(namespace)s""",
|
||||
... {'typename': 'point', 'namespace': 'pg_catalog'})
|
||||
>>> point_oid = cur.fetchone()[0]
|
||||
>>> point_oid
|
||||
600
|
||||
|
||||
After you know the object OID, you can create and register the new type:
|
||||
|
||||
>>> POINT = psycopg2.extensions.new_type((point_oid,), "POINT", cast_point)
|
||||
>>> psycopg2.extensions.register_type(POINT)
|
||||
|
||||
The `~psycopg2.extensions.new_type()` function binds the object OIDs
|
||||
(more than one can be specified) to the adapter function.
|
||||
`~psycopg2.extensions.register_type()` completes the spell. Conversion
|
||||
is automatically performed when a column whose type is a registered OID is
|
||||
read:
|
||||
|
||||
>>> cur.execute("SELECT '(10.2,20.3)'::point")
|
||||
>>> point = cur.fetchone()[0]
|
||||
>>> print type(point), point.x, point.y
|
||||
<class 'Point'> 10.2 20.3
|
||||
|
||||
A typecaster created by `!new_type()` can be also used with
|
||||
`~psycopg2.extensions.new_array_type()` to create a typecaster converting a
|
||||
PostgreSQL array into a Python list.
|
||||
|
||||
|
||||
.. index::
|
||||
pair: Asynchronous; Notifications
|
||||
pair: LISTEN; SQL command
|
||||
pair: NOTIFY; SQL command
|
||||
|
||||
.. _async-notify:
|
||||
|
||||
Asynchronous notifications
|
||||
--------------------------
|
||||
|
||||
Psycopg allows asynchronous interaction with other database sessions using the
|
||||
facilities offered by PostgreSQL commands |LISTEN|_ and |NOTIFY|_. Please
|
||||
refer to the PostgreSQL documentation for examples about how to use this form of
|
||||
communication.
|
||||
|
||||
Notifications are instances of the `~psycopg2.extensions.Notify` object made
|
||||
available upon reception in the `connection.notifies` list. Notifications can
|
||||
be sent from Python code simply executing a :sql:`NOTIFY` command in an
|
||||
`~cursor.execute()` call.
|
||||
|
||||
Because of the way sessions interact with notifications (see |NOTIFY|_
|
||||
documentation), you should keep the connection in `~connection.autocommit`
|
||||
mode if you wish to receive or send notifications in a timely manner.
|
||||
|
||||
.. |LISTEN| replace:: :sql:`LISTEN`
|
||||
.. _LISTEN: https://www.postgresql.org/docs/current/static/sql-listen.html
|
||||
.. |NOTIFY| replace:: :sql:`NOTIFY`
|
||||
.. _NOTIFY: https://www.postgresql.org/docs/current/static/sql-notify.html
|
||||
|
||||
Notifications are received after every query execution. If the user is
|
||||
interested in receiving notifications but not in performing any query, the
|
||||
`~connection.poll()` method can be used to check for new messages without
|
||||
wasting resources.
|
||||
|
||||
A simple application could poll the connection from time to time to check if
|
||||
something new has arrived. A better strategy is to use some I/O completion
|
||||
function such as :py:func:`~select.select` to sleep until awakened by the kernel when there is
|
||||
some data to read on the connection, thereby using no CPU unless there is
|
||||
something to read::
|
||||
|
||||
import select
|
||||
import psycopg2
|
||||
import psycopg2.extensions
|
||||
|
||||
conn = psycopg2.connect(DSN)
|
||||
conn.set_isolation_level(psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT)
|
||||
|
||||
curs = conn.cursor()
|
||||
curs.execute("LISTEN test;")
|
||||
|
||||
print "Waiting for notifications on channel 'test'"
|
||||
while True:
|
||||
if select.select([conn],[],[],5) == ([],[],[]):
|
||||
print "Timeout"
|
||||
else:
|
||||
conn.poll()
|
||||
while conn.notifies:
|
||||
notify = conn.notifies.pop(0)
|
||||
print "Got NOTIFY:", notify.pid, notify.channel, notify.payload
|
||||
|
||||
Running the script and executing a command such as :sql:`NOTIFY test, 'hello'`
|
||||
in a separate :program:`psql` shell, the output may look similar to:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
Waiting for notifications on channel 'test'
|
||||
Timeout
|
||||
Timeout
|
||||
Got NOTIFY: 6535 test hello
|
||||
Timeout
|
||||
...
|
||||
|
||||
Note that the payload is only available from PostgreSQL 9.0: notifications
|
||||
received from a previous version server will have the
|
||||
`~psycopg2.extensions.Notify.payload` attribute set to the empty string.
|
||||
|
||||
.. versionchanged:: 2.3
|
||||
Added `~psycopg2.extensions.Notify` object and handling notification
|
||||
payload.
|
||||
|
||||
.. versionchanged:: 2.7
|
||||
The `~connection.notifies` attribute is writable: it is possible to
|
||||
replace it with any object exposing an `!append()` method. An useful
|
||||
example would be to use a `~collections.deque` object.
|
||||
|
||||
|
||||
.. index::
|
||||
double: Asynchronous; Connection
|
||||
|
||||
.. _async-support:
|
||||
|
||||
Asynchronous support
|
||||
--------------------
|
||||
|
||||
.. versionadded:: 2.2
|
||||
|
||||
Psycopg can issue asynchronous queries to a PostgreSQL database. An asynchronous
|
||||
communication style is established passing the parameter *async*\=1 to the
|
||||
`~psycopg2.connect()` function: the returned connection will work in
|
||||
*asynchronous mode*.
|
||||
|
||||
In asynchronous mode, a Psycopg connection will rely on the caller to poll the
|
||||
socket file descriptor, checking if it is ready to accept data or if a query
|
||||
result has been transferred and is ready to be read on the client. The caller
|
||||
can use the method `~connection.fileno()` to get the connection file
|
||||
descriptor and `~connection.poll()` to make communication proceed according to
|
||||
the current connection state.
|
||||
|
||||
The following is an example loop using methods `!fileno()` and `!poll()`
|
||||
together with the Python :py:func:`~select.select` function in order to carry on
|
||||
asynchronous operations with Psycopg::
|
||||
|
||||
def wait(conn):
|
||||
while True:
|
||||
state = conn.poll()
|
||||
if state == psycopg2.extensions.POLL_OK:
|
||||
break
|
||||
elif state == psycopg2.extensions.POLL_WRITE:
|
||||
select.select([], [conn.fileno()], [])
|
||||
elif state == psycopg2.extensions.POLL_READ:
|
||||
select.select([conn.fileno()], [], [])
|
||||
else:
|
||||
raise psycopg2.OperationalError("poll() returned %s" % state)
|
||||
|
||||
The above loop of course would block an entire application: in a real
|
||||
asynchronous framework, `!select()` would be called on many file descriptors
|
||||
waiting for any of them to be ready. Nonetheless the function can be used to
|
||||
connect to a PostgreSQL server only using nonblocking commands and the
|
||||
connection obtained can be used to perform further nonblocking queries. After
|
||||
`!poll()` has returned `~psycopg2.extensions.POLL_OK`, and thus `!wait()` has
|
||||
returned, the connection can be safely used:
|
||||
|
||||
>>> aconn = psycopg2.connect(database='test', async=1)
|
||||
>>> wait(aconn)
|
||||
>>> acurs = aconn.cursor()
|
||||
|
||||
Note that there are a few other requirements to be met in order to have a
|
||||
completely non-blocking connection attempt: see the libpq documentation for
|
||||
|PQconnectStart|_.
|
||||
|
||||
.. |PQconnectStart| replace:: `!PQconnectStart()`
|
||||
.. _PQconnectStart: https://www.postgresql.org/docs/current/static/libpq-connect.html#LIBPQ-PQCONNECTSTARTPARAMS
|
||||
|
||||
The same loop should be also used to perform nonblocking queries: after
|
||||
sending a query via `~cursor.execute()` or `~cursor.callproc()`, call
|
||||
`!poll()` on the connection available from `cursor.connection` until it
|
||||
returns `!POLL_OK`, at which point the query has been completely sent to the
|
||||
server and, if it produced data, the results have been transferred to the
|
||||
client and available using the regular cursor methods:
|
||||
|
||||
>>> acurs.execute("SELECT pg_sleep(5); SELECT 42;")
|
||||
>>> wait(acurs.connection)
|
||||
>>> acurs.fetchone()[0]
|
||||
42
|
||||
|
||||
When an asynchronous query is being executed, `connection.isexecuting()` returns
|
||||
`!True`. Two cursors can't execute concurrent queries on the same asynchronous
|
||||
connection.
|
||||
|
||||
There are several limitations in using asynchronous connections: the
|
||||
connection is always in `~connection.autocommit` mode and it is not
|
||||
possible to change it. So a
|
||||
transaction is not implicitly started at the first query and is not possible
|
||||
to use methods `~connection.commit()` and `~connection.rollback()`: you can
|
||||
manually control transactions using `~cursor.execute()` to send database
|
||||
commands such as :sql:`BEGIN`, :sql:`COMMIT` and :sql:`ROLLBACK`. Similarly
|
||||
`~connection.set_session()` can't be used but it is still possible to invoke the
|
||||
:sql:`SET` command with the proper :sql:`default_transaction_...` parameter.
|
||||
|
||||
With asynchronous connections it is also not possible to use
|
||||
`~connection.set_client_encoding()`, `~cursor.executemany()`, :ref:`large
|
||||
objects <large-objects>`, :ref:`named cursors <server-side-cursors>`.
|
||||
|
||||
:ref:`COPY commands <copy>` are not supported either in asynchronous mode, but
|
||||
this will be probably implemented in a future release.
|
||||
|
||||
|
||||
|
||||
|
||||
.. index::
|
||||
single: Greenlet
|
||||
single: Coroutine
|
||||
single: Eventlet
|
||||
single: gevent
|
||||
single: Wait callback
|
||||
|
||||
.. _green-support:
|
||||
|
||||
Support for coroutine libraries
|
||||
-------------------------------
|
||||
|
||||
.. versionadded:: 2.2
|
||||
|
||||
Psycopg can be used together with coroutine_\-based libraries and participate
|
||||
in cooperative multithreading.
|
||||
|
||||
Coroutine-based libraries (such as Eventlet_ or gevent_) can usually patch the
|
||||
Python standard library in order to enable a coroutine switch in the presence of
|
||||
blocking I/O: the process is usually referred as making the system *green*, in
|
||||
reference to the `green threads`_.
|
||||
|
||||
Because Psycopg is a C extension module, it is not possible for coroutine
|
||||
libraries to patch it: Psycopg instead enables cooperative multithreading by
|
||||
allowing the registration of a *wait callback* using the
|
||||
`psycopg2.extensions.set_wait_callback()` function. When a wait callback is
|
||||
registered, Psycopg will use `libpq non-blocking calls`__ instead of the regular
|
||||
blocking ones, and will delegate to the callback the responsibility to wait
|
||||
for the socket to become readable or writable.
|
||||
|
||||
Working this way, the caller does not have the complete freedom to schedule the
|
||||
socket check whenever they want as with an :ref:`asynchronous connection
|
||||
<async-support>`, but has the advantage of maintaining a complete |DBAPI|
|
||||
semantics: from the point of view of the end user, all Psycopg functions and
|
||||
objects will work transparently in the coroutine environment (blocking the
|
||||
calling green thread and giving other green threads the possibility to be
|
||||
scheduled), allowing non modified code and third party libraries (such as
|
||||
SQLAlchemy_) to be used in coroutine-based programs.
|
||||
|
||||
.. warning::
|
||||
Psycopg connections are not *green thread safe* and can't be used
|
||||
concurrently by different green threads. Trying to execute more than one
|
||||
command at time using one cursor per thread will result in an error (or a
|
||||
deadlock on versions before 2.4.2).
|
||||
|
||||
Therefore, programmers are advised to either avoid sharing connections
|
||||
between coroutines or to use a library-friendly lock to synchronize shared
|
||||
connections, e.g. for pooling.
|
||||
|
||||
Coroutine libraries authors should provide a callback implementation (and
|
||||
possibly a method to register it) to make Psycopg as green as they want. An
|
||||
example callback (using `!select()` to block) is provided as
|
||||
`psycopg2.extras.wait_select()`: it boils down to something similar to::
|
||||
|
||||
def wait_select(conn):
|
||||
while True:
|
||||
state = conn.poll()
|
||||
if state == extensions.POLL_OK:
|
||||
break
|
||||
elif state == extensions.POLL_READ:
|
||||
select.select([conn.fileno()], [], [])
|
||||
elif state == extensions.POLL_WRITE:
|
||||
select.select([], [conn.fileno()], [])
|
||||
else:
|
||||
raise OperationalError("bad state from poll: %s" % state)
|
||||
|
||||
Providing callback functions for the single coroutine libraries is out of
|
||||
psycopg2 scope, as the callback can be tied to the libraries' implementation
|
||||
details. You can check the `psycogreen`_ project for further informations and
|
||||
resources about the topic.
|
||||
|
||||
.. _coroutine: https://en.wikipedia.org/wiki/Coroutine
|
||||
.. _greenlet: https://pypi.org/project/greenlet/
|
||||
.. _green threads: https://en.wikipedia.org/wiki/Green_threads
|
||||
.. _Eventlet: https://eventlet.net/
|
||||
.. _gevent: http://www.gevent.org/
|
||||
.. _SQLAlchemy: https://www.sqlalchemy.org/
|
||||
.. _psycogreen: https://github.com/psycopg/psycogreen/
|
||||
.. __: https://www.postgresql.org/docs/current/static/libpq-async.html
|
||||
|
||||
.. warning::
|
||||
|
||||
:ref:`COPY commands <copy>` are currently not supported when a wait callback
|
||||
is registered, but they will be probably implemented in a future release.
|
||||
|
||||
:ref:`Large objects <large-objects>` are not supported either: they are
|
||||
not compatible with asynchronous connections.
|
||||
|
||||
|
||||
.. testcode::
|
||||
:hide:
|
||||
|
||||
aconn.close()
|
||||
conn.rollback()
|
||||
cur.execute("DROP TABLE atable")
|
||||
conn.commit()
|
||||
cur.close()
|
||||
conn.close()
|
||||
|
||||
|
||||
|
||||
.. index::
|
||||
single: Replication
|
||||
|
||||
.. _replication-support:
|
||||
|
||||
Replication protocol support
|
||||
----------------------------
|
||||
|
||||
.. versionadded:: 2.7
|
||||
|
||||
Modern PostgreSQL servers (version 9.0 and above) support replication. The
|
||||
replication protocol is built on top of the client-server protocol and can be
|
||||
operated using ``libpq``, as such it can be also operated by ``psycopg2``.
|
||||
The replication protocol can be operated on both synchronous and
|
||||
:ref:`asynchronous <async-support>` connections.
|
||||
|
||||
Server version 9.4 adds a new feature called *Logical Replication*.
|
||||
|
||||
.. seealso::
|
||||
|
||||
- PostgreSQL `Streaming Replication Protocol`__
|
||||
|
||||
.. __: https://www.postgresql.org/docs/current/static/protocol-replication.html
|
||||
|
||||
|
||||
Logical replication Quick-Start
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
You must be using PostgreSQL server version 9.4 or above to run this quick
|
||||
start.
|
||||
|
||||
Make sure that replication connections are permitted for user ``postgres`` in
|
||||
``pg_hba.conf`` and reload the server configuration. You also need to set
|
||||
``wal_level=logical`` and ``max_wal_senders``, ``max_replication_slots`` to
|
||||
value greater than zero in ``postgresql.conf`` (these changes require a server
|
||||
restart). Create a database ``psycopg2_test``.
|
||||
|
||||
Then run the following code to quickly try the replication support out. This
|
||||
is not production code -- it's only intended as a simple demo of logical
|
||||
replication::
|
||||
|
||||
from __future__ import print_function
|
||||
import sys
|
||||
import psycopg2
|
||||
import psycopg2.extras
|
||||
|
||||
conn = psycopg2.connect('dbname=psycopg2_test user=postgres',
|
||||
connection_factory=psycopg2.extras.LogicalReplicationConnection)
|
||||
cur = conn.cursor()
|
||||
try:
|
||||
# test_decoding produces textual output
|
||||
cur.start_replication(slot_name='pytest', decode=True)
|
||||
except psycopg2.ProgrammingError:
|
||||
cur.create_replication_slot('pytest', output_plugin='test_decoding')
|
||||
cur.start_replication(slot_name='pytest', decode=True)
|
||||
|
||||
class DemoConsumer(object):
|
||||
def __call__(self, msg):
|
||||
print(msg.payload)
|
||||
msg.cursor.send_feedback(flush_lsn=msg.data_start)
|
||||
|
||||
democonsumer = DemoConsumer()
|
||||
|
||||
print("Starting streaming, press Control-C to end...", file=sys.stderr)
|
||||
try:
|
||||
cur.consume_stream(democonsumer)
|
||||
except KeyboardInterrupt:
|
||||
cur.close()
|
||||
conn.close()
|
||||
print("The slot 'pytest' still exists. Drop it with "
|
||||
"SELECT pg_drop_replication_slot('pytest'); if no longer needed.",
|
||||
file=sys.stderr)
|
||||
print("WARNING: Transaction logs will accumulate in pg_xlog "
|
||||
"until the slot is dropped.", file=sys.stderr)
|
||||
|
||||
|
||||
You can now make changes to the ``psycopg2_test`` database using a normal
|
||||
psycopg2 session, ``psql``, etc. and see the logical decoding stream printed
|
||||
by this demo client.
|
||||
|
||||
This will continue running until terminated with ``Control-C``.
|
||||
|
||||
For the details see :ref:`replication-objects`.
|
||||
288
doc/src/conf.py
Normal file
288
doc/src/conf.py
Normal file
@ -0,0 +1,288 @@
|
||||
#
|
||||
# Psycopg documentation build configuration file, created by
|
||||
# sphinx-quickstart on Sun Feb 7 13:48:41 2010.
|
||||
#
|
||||
# This file is execfile()d with the current directory set to its containing dir.
|
||||
#
|
||||
# Note that not all possible configuration values are present in this
|
||||
# autogenerated file.
|
||||
#
|
||||
# All configuration values have a default; values that are commented out
|
||||
# serve to show the default.
|
||||
|
||||
import os
|
||||
import sys
|
||||
from better import better_theme_path
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
sys.path.append(os.path.abspath('tools/lib'))
|
||||
|
||||
# -- General configuration -----------------------------------------------------
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be extensions
|
||||
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
||||
extensions = [
|
||||
'sphinx.ext.autodoc',
|
||||
'sphinx.ext.todo',
|
||||
'sphinx.ext.ifconfig',
|
||||
'sphinx.ext.doctest',
|
||||
'sphinx.ext.intersphinx',
|
||||
]
|
||||
|
||||
# Specific extensions for Psycopg documentation.
|
||||
extensions += ['dbapi_extension', 'sql_role', 'ticket_role']
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
|
||||
# The suffix of source filenames.
|
||||
source_suffix = '.rst'
|
||||
|
||||
# The encoding of source files.
|
||||
# source_encoding = 'utf-8'
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = 'Psycopg'
|
||||
copyright = (
|
||||
'2001-2021, Federico Di Gregorio, Daniele Varrazzo, The Psycopg Team'
|
||||
)
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = '2.0'
|
||||
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
try:
|
||||
import psycopg2
|
||||
except ImportError:
|
||||
print("WARNING: couldn't import psycopg to read version.")
|
||||
release = version
|
||||
else:
|
||||
release = psycopg2.__version__.split()[0]
|
||||
version = '.'.join(release.split('.')[:2])
|
||||
|
||||
intersphinx_mapping = {'py': ('https://docs.python.org/3', None)}
|
||||
|
||||
# Pattern to generate links to the bug tracker
|
||||
ticket_url = 'https://github.com/psycopg/psycopg2/issues/%s'
|
||||
ticket_remap_until = 25
|
||||
ticket_remap_offset = 230
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
# language = None
|
||||
|
||||
# There are two options for replacing |today|: either, you set today to some
|
||||
# non-false value, then it is used:
|
||||
# today = ''
|
||||
# Else, today_fmt is used as the format for a strftime call.
|
||||
# today_fmt = '%B %d, %Y'
|
||||
|
||||
# List of documents that shouldn't be included in the build.
|
||||
# unused_docs = []
|
||||
|
||||
# List of directories, relative to source directory, that shouldn't be searched
|
||||
# for source files.
|
||||
exclude_trees = ['_build', 'html']
|
||||
|
||||
# The reST default role (used for this markup: `text`) to use for all documents.
|
||||
default_role = 'obj'
|
||||
|
||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||
# add_function_parentheses = True
|
||||
|
||||
# If true, the current module name will be prepended to all description
|
||||
# unit titles (such as .. function::).
|
||||
# add_module_names = True
|
||||
|
||||
# If true, sectionauthor and moduleauthor directives will be shown in the
|
||||
# output. They are ignored by default.
|
||||
# show_authors = False
|
||||
|
||||
# Using 'python' instead of the default gives warnings if parsing an example
|
||||
# fails, instead of defaulting to none
|
||||
highlight_language = 'python'
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = 'sphinx'
|
||||
|
||||
# A list of ignored prefixes for module index sorting.
|
||||
# modindex_common_prefix = []
|
||||
|
||||
# Include TODO items in the documentation
|
||||
todo_include_todos = False
|
||||
|
||||
rst_epilog = """
|
||||
.. |DBAPI| replace:: DB API 2.0
|
||||
|
||||
.. _DBAPI: https://www.python.org/dev/peps/pep-0249/
|
||||
|
||||
.. _transaction isolation level:
|
||||
https://www.postgresql.org/docs/current/static/transaction-iso.html
|
||||
|
||||
.. |MVCC| replace:: :abbr:`MVCC (Multiversion concurrency control)`
|
||||
"""
|
||||
|
||||
# -- Options for HTML output ---------------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. Major themes that come with
|
||||
# Sphinx are currently 'default' and 'sphinxdoc'.
|
||||
html_theme = 'better'
|
||||
|
||||
# The stylesheet to use with HTML output: this will include the original one
|
||||
# adding a few classes.
|
||||
# html_style = 'psycopg.css'
|
||||
|
||||
# Hide the sphinx footer
|
||||
html_show_sphinx = False
|
||||
|
||||
# Theme options are theme-specific and customize the look and feel of a theme
|
||||
# further. For a list of options available for each theme, see the
|
||||
# documentation.
|
||||
html_theme_options = {
|
||||
'linktotheme': False,
|
||||
'cssfiles': ['_static/psycopg.css'],
|
||||
}
|
||||
|
||||
# Add any paths that contain custom themes here, relative to this directory.
|
||||
html_theme_path = [better_theme_path]
|
||||
|
||||
# The name for this set of Sphinx documents. If None, it defaults to
|
||||
# "<project> v<release> documentation".
|
||||
# html_title = None
|
||||
|
||||
# A shorter title for the navigation bar. Default is the same as html_title.
|
||||
html_short_title = 'Home'
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top
|
||||
# of the sidebar.
|
||||
# html_logo = None
|
||||
|
||||
# The name of an image file (within the static path) to use as favicon of the
|
||||
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
||||
# pixels large.
|
||||
# html_favicon = None
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ['_static']
|
||||
|
||||
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
||||
# using the given strftime format.
|
||||
# html_last_updated_fmt = '%b %d, %Y'
|
||||
|
||||
# If true, SmartyPants will be used to convert quotes and dashes to
|
||||
# typographically correct entities.
|
||||
# html_use_smartypants = True
|
||||
|
||||
# Custom sidebar templates, maps document names to template names.
|
||||
# no need for the prev/next topic link using better theme: they are on top
|
||||
html_sidebars = {
|
||||
'**': ['localtoc.html', 'searchbox.html'],
|
||||
}
|
||||
|
||||
# Additional templates that should be rendered to pages, maps page names to
|
||||
# template names.
|
||||
# html_additional_pages = {}
|
||||
|
||||
# If false, no module index is generated.
|
||||
# html_use_modindex = True
|
||||
|
||||
# If false, no index is generated.
|
||||
# html_use_index = True
|
||||
|
||||
# If true, the index is split into individual pages for each letter.
|
||||
# html_split_index = False
|
||||
|
||||
# If true, links to the reST sources are added to the pages.
|
||||
# html_show_sourcelink = True
|
||||
|
||||
# If true, an OpenSearch description file will be output, and all pages will
|
||||
# contain a <link> tag referring to it. The value of this option must be the
|
||||
# base URL from which the finished HTML is served.
|
||||
# html_use_opensearch = ''
|
||||
|
||||
# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml").
|
||||
# html_file_suffix = ''
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = 'psycopgdoc'
|
||||
|
||||
|
||||
# -- Options for LaTeX output --------------------------------------------------
|
||||
|
||||
# The paper size ('letter' or 'a4').
|
||||
# latex_paper_size = 'letter'
|
||||
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
# latex_font_size = '10pt'
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title, author, documentclass [howto/manual]).
|
||||
latex_documents = [
|
||||
(
|
||||
'index',
|
||||
'psycopg.tex',
|
||||
'Psycopg Documentation',
|
||||
'Federico Di Gregorio',
|
||||
'manual',
|
||||
)
|
||||
]
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top of
|
||||
# the title page.
|
||||
# latex_logo = None
|
||||
|
||||
# For "manual" documents, if this is true, then toplevel headings are parts,
|
||||
# not chapters.
|
||||
# latex_use_parts = False
|
||||
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
# latex_preamble = ''
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
# latex_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
# latex_use_modindex = True
|
||||
|
||||
|
||||
doctest_global_setup = """
|
||||
|
||||
import os
|
||||
import psycopg2
|
||||
|
||||
def test_connect():
|
||||
try:
|
||||
dsn = os.environ['PSYCOPG2_DSN']
|
||||
except KeyError:
|
||||
assert False, "You need to set the environment variable PSYCOPG2_DSN" \
|
||||
" in order to test the documentation!"
|
||||
return psycopg2.connect(dsn)
|
||||
|
||||
conn = test_connect()
|
||||
cur = conn.cursor()
|
||||
|
||||
def drop_test_table(name):
|
||||
cur.execute("SAVEPOINT drop_test_table;")
|
||||
try:
|
||||
cur.execute("DROP TABLE %s;" % name)
|
||||
except:
|
||||
cur.execute("ROLLBACK TO SAVEPOINT drop_test_table;")
|
||||
conn.commit()
|
||||
|
||||
def create_test_table():
|
||||
drop_test_table('test')
|
||||
cur.execute("CREATE TABLE test (id SERIAL PRIMARY KEY, num INT, data TEXT)")
|
||||
conn.commit()
|
||||
|
||||
"""
|
||||
916
doc/src/connection.rst
Normal file
916
doc/src/connection.rst
Normal file
@ -0,0 +1,916 @@
|
||||
The ``connection`` class
|
||||
========================
|
||||
|
||||
.. sectionauthor:: Daniele Varrazzo <daniele.varrazzo@gmail.com>
|
||||
|
||||
.. testsetup::
|
||||
|
||||
from pprint import pprint
|
||||
import psycopg2.extensions
|
||||
|
||||
drop_test_table('foo')
|
||||
|
||||
.. class:: connection
|
||||
|
||||
Handles the connection to a PostgreSQL database instance. It encapsulates
|
||||
a database session.
|
||||
|
||||
Connections are created using the factory function
|
||||
`~psycopg2.connect()`.
|
||||
|
||||
Connections are thread safe and can be shared among many threads. See
|
||||
:ref:`thread-safety` for details.
|
||||
|
||||
Connections can be used as context managers. Note that a context wraps a
|
||||
transaction: if the context exits with success the transaction is
|
||||
committed, if it exits with an exception the transaction is rolled back.
|
||||
Note that the connection is not closed by the context and it can be used
|
||||
for several contexts.
|
||||
|
||||
.. code:: python
|
||||
|
||||
conn = psycopg2.connect(DSN)
|
||||
|
||||
with conn:
|
||||
with conn.cursor() as curs:
|
||||
curs.execute(SQL1)
|
||||
|
||||
with conn:
|
||||
with conn.cursor() as curs:
|
||||
curs.execute(SQL2)
|
||||
|
||||
# leaving contexts doesn't close the connection
|
||||
conn.close()
|
||||
|
||||
|
||||
.. method:: cursor(name=None, cursor_factory=None, scrollable=None, withhold=False)
|
||||
|
||||
Return a new `cursor` object using the connection.
|
||||
|
||||
If *name* is specified, the returned cursor will be a :ref:`server
|
||||
side cursor <server-side-cursors>` (also known as *named cursor*).
|
||||
Otherwise it will be a regular *client side* cursor. By default a
|
||||
named cursor is declared without :sql:`SCROLL` option and
|
||||
:sql:`WITHOUT HOLD`: set the argument or property `~cursor.scrollable`
|
||||
to `!True`/`!False` and or `~cursor.withhold` to `!True` to change the
|
||||
declaration.
|
||||
|
||||
The name can be a string not valid as a PostgreSQL identifier: for
|
||||
example it may start with a digit and contain non-alphanumeric
|
||||
characters and quotes.
|
||||
|
||||
.. versionchanged:: 2.4
|
||||
previously only valid PostgreSQL identifiers were accepted as
|
||||
cursor name.
|
||||
|
||||
The *cursor_factory* argument can be used to create non-standard
|
||||
cursors. The class returned must be a subclass of
|
||||
`psycopg2.extensions.cursor`. See :ref:`subclassing-cursor` for
|
||||
details. A default factory for the connection can also be specified
|
||||
using the `~connection.cursor_factory` attribute.
|
||||
|
||||
.. versionchanged:: 2.4.3 added the *withhold* argument.
|
||||
.. versionchanged:: 2.5 added the *scrollable* argument.
|
||||
|
||||
.. extension::
|
||||
|
||||
All the function arguments are Psycopg extensions to the |DBAPI|.
|
||||
|
||||
|
||||
.. index::
|
||||
pair: Transaction; Commit
|
||||
|
||||
.. method:: commit()
|
||||
|
||||
Commit any pending transaction to the database.
|
||||
|
||||
By default, Psycopg opens a transaction before executing the first
|
||||
command: if `!commit()` is not called, the effect of any data
|
||||
manipulation will be lost.
|
||||
|
||||
The connection can be also set in "autocommit" mode: no transaction is
|
||||
automatically open, commands have immediate effect. See
|
||||
:ref:`transactions-control` for details.
|
||||
|
||||
.. versionchanged:: 2.5 if the connection is used in a ``with``
|
||||
statement, the method is automatically called if no exception is
|
||||
raised in the ``with`` block.
|
||||
|
||||
|
||||
.. index::
|
||||
pair: Transaction; Rollback
|
||||
|
||||
.. method:: rollback()
|
||||
|
||||
Roll back to the start of any pending transaction. Closing a
|
||||
connection without committing the changes first will cause an implicit
|
||||
rollback to be performed.
|
||||
|
||||
.. versionchanged:: 2.5 if the connection is used in a ``with``
|
||||
statement, the method is automatically called if an exception is
|
||||
raised in the ``with`` block.
|
||||
|
||||
|
||||
.. method:: close()
|
||||
|
||||
Close the connection now (rather than whenever `del` is executed).
|
||||
The connection will be unusable from this point forward; an
|
||||
`~psycopg2.InterfaceError` will be raised if any operation is
|
||||
attempted with the connection. The same applies to all cursor objects
|
||||
trying to use the connection. Note that closing a connection without
|
||||
committing the changes first will cause any pending change to be
|
||||
discarded as if a :sql:`ROLLBACK` was performed (unless a different
|
||||
isolation level has been selected: see
|
||||
`~connection.set_isolation_level()`).
|
||||
|
||||
.. index::
|
||||
single: PgBouncer; unclean server
|
||||
|
||||
.. versionchanged:: 2.2
|
||||
previously an explicit :sql:`ROLLBACK` was issued by Psycopg on
|
||||
`!close()`. The command could have been sent to the backend at an
|
||||
inappropriate time, so Psycopg currently relies on the backend to
|
||||
implicitly discard uncommitted changes. Some middleware are known
|
||||
to behave incorrectly though when the connection is closed during
|
||||
a transaction (when `~connection.status` is
|
||||
`~psycopg2.extensions.STATUS_IN_TRANSACTION`), e.g. PgBouncer_
|
||||
reports an ``unclean server`` and discards the connection. To
|
||||
avoid this problem you can ensure to terminate the transaction
|
||||
with a `~connection.commit()`/`~connection.rollback()` before
|
||||
closing.
|
||||
|
||||
.. _PgBouncer: http://www.pgbouncer.org/
|
||||
|
||||
|
||||
.. index::
|
||||
single: Exceptions; In the connection class
|
||||
|
||||
.. rubric:: Exceptions as connection class attributes
|
||||
|
||||
The `!connection` also exposes as attributes the same exceptions
|
||||
available in the `psycopg2` module. See :ref:`dbapi-exceptions`.
|
||||
|
||||
|
||||
|
||||
.. index::
|
||||
single: Two-phase commit; methods
|
||||
|
||||
.. rubric:: Two-phase commit support methods
|
||||
|
||||
.. versionadded:: 2.3
|
||||
|
||||
.. seealso:: :ref:`tpc` for an introductory explanation of these methods.
|
||||
|
||||
Note that PostgreSQL supports two-phase commit since release 8.1: these
|
||||
methods raise `~psycopg2.NotSupportedError` if used with an older version
|
||||
server.
|
||||
|
||||
|
||||
.. _tpc_methods:
|
||||
|
||||
.. method:: xid(format_id, gtrid, bqual)
|
||||
|
||||
Returns a `~psycopg2.extensions.Xid` instance to be passed to the
|
||||
`!tpc_*()` methods of this connection. The argument types and
|
||||
constraints are explained in :ref:`tpc`.
|
||||
|
||||
The values passed to the method will be available on the returned
|
||||
object as the members `~psycopg2.extensions.Xid.format_id`,
|
||||
`~psycopg2.extensions.Xid.gtrid`, `~psycopg2.extensions.Xid.bqual`.
|
||||
The object also allows accessing to these members and unpacking as a
|
||||
3-items tuple.
|
||||
|
||||
|
||||
.. method:: tpc_begin(xid)
|
||||
|
||||
Begins a TPC transaction with the given transaction ID *xid*.
|
||||
|
||||
This method should be called outside of a transaction (i.e. nothing
|
||||
may have executed since the last `~connection.commit()` or
|
||||
`~connection.rollback()` and `connection.status` is
|
||||
`~psycopg2.extensions.STATUS_READY`).
|
||||
|
||||
Furthermore, it is an error to call `!commit()` or `!rollback()`
|
||||
within the TPC transaction: in this case a `~psycopg2.ProgrammingError`
|
||||
is raised.
|
||||
|
||||
The *xid* may be either an object returned by the `~connection.xid()`
|
||||
method or a plain string: the latter allows to create a transaction
|
||||
using the provided string as PostgreSQL transaction id. See also
|
||||
`~connection.tpc_recover()`.
|
||||
|
||||
|
||||
.. index::
|
||||
pair: Transaction; Prepare
|
||||
|
||||
.. method:: tpc_prepare()
|
||||
|
||||
Performs the first phase of a transaction started with
|
||||
`~connection.tpc_begin()`. A `~psycopg2.ProgrammingError` is raised if
|
||||
this method is used outside of a TPC transaction.
|
||||
|
||||
After calling `!tpc_prepare()`, no statements can be executed until
|
||||
`~connection.tpc_commit()` or `~connection.tpc_rollback()` will be
|
||||
called. The `~connection.reset()` method can be used to restore the
|
||||
status of the connection to `~psycopg2.extensions.STATUS_READY`: the
|
||||
transaction will remain prepared in the database and will be
|
||||
possible to finish it with `!tpc_commit(xid)` and
|
||||
`!tpc_rollback(xid)`.
|
||||
|
||||
.. seealso:: the |PREPARE TRANSACTION|_ PostgreSQL command.
|
||||
|
||||
.. |PREPARE TRANSACTION| replace:: :sql:`PREPARE TRANSACTION`
|
||||
.. _PREPARE TRANSACTION: https://www.postgresql.org/docs/current/static/sql-prepare-transaction.html
|
||||
|
||||
|
||||
.. index::
|
||||
pair: Commit; Prepared
|
||||
|
||||
.. method:: tpc_commit([xid])
|
||||
|
||||
When called with no arguments, `!tpc_commit()` commits a TPC
|
||||
transaction previously prepared with `~connection.tpc_prepare()`.
|
||||
|
||||
If `!tpc_commit()` is called prior to `!tpc_prepare()`, a single phase
|
||||
commit is performed. A transaction manager may choose to do this if
|
||||
only a single resource is participating in the global transaction.
|
||||
|
||||
When called with a transaction ID *xid*, the database commits
|
||||
the given transaction. If an invalid transaction ID is
|
||||
provided, a `~psycopg2.ProgrammingError` will be raised. This form
|
||||
should be called outside of a transaction, and is intended for use in
|
||||
recovery.
|
||||
|
||||
On return, the TPC transaction is ended.
|
||||
|
||||
.. seealso:: the |COMMIT PREPARED|_ PostgreSQL command.
|
||||
|
||||
.. |COMMIT PREPARED| replace:: :sql:`COMMIT PREPARED`
|
||||
.. _COMMIT PREPARED: https://www.postgresql.org/docs/current/static/sql-commit-prepared.html
|
||||
|
||||
|
||||
.. index::
|
||||
pair: Rollback; Prepared
|
||||
|
||||
.. method:: tpc_rollback([xid])
|
||||
|
||||
When called with no arguments, `!tpc_rollback()` rolls back a TPC
|
||||
transaction. It may be called before or after
|
||||
`~connection.tpc_prepare()`.
|
||||
|
||||
When called with a transaction ID *xid*, it rolls back the given
|
||||
transaction. If an invalid transaction ID is provided, a
|
||||
`~psycopg2.ProgrammingError` is raised. This form should be called
|
||||
outside of a transaction, and is intended for use in recovery.
|
||||
|
||||
On return, the TPC transaction is ended.
|
||||
|
||||
.. seealso:: the |ROLLBACK PREPARED|_ PostgreSQL command.
|
||||
|
||||
.. |ROLLBACK PREPARED| replace:: :sql:`ROLLBACK PREPARED`
|
||||
.. _ROLLBACK PREPARED: https://www.postgresql.org/docs/current/static/sql-rollback-prepared.html
|
||||
|
||||
|
||||
.. index::
|
||||
pair: Transaction; Recover
|
||||
|
||||
.. method:: tpc_recover()
|
||||
|
||||
Returns a list of `~psycopg2.extensions.Xid` representing pending
|
||||
transactions, suitable for use with `tpc_commit()` or
|
||||
`tpc_rollback()`.
|
||||
|
||||
If a transaction was not initiated by Psycopg, the returned Xids will
|
||||
have attributes `~psycopg2.extensions.Xid.format_id` and
|
||||
`~psycopg2.extensions.Xid.bqual` set to `!None` and the
|
||||
`~psycopg2.extensions.Xid.gtrid` set to the PostgreSQL transaction ID: such Xids are still
|
||||
usable for recovery. Psycopg uses the same algorithm of the
|
||||
`PostgreSQL JDBC driver`__ to encode a XA triple in a string, so
|
||||
transactions initiated by a program using such driver should be
|
||||
unpacked correctly.
|
||||
|
||||
.. __: https://jdbc.postgresql.org/
|
||||
|
||||
Xids returned by `!tpc_recover()` also have extra attributes
|
||||
`~psycopg2.extensions.Xid.prepared`, `~psycopg2.extensions.Xid.owner`,
|
||||
`~psycopg2.extensions.Xid.database` populated with the values read
|
||||
from the server.
|
||||
|
||||
.. seealso:: the |pg_prepared_xacts|_ system view.
|
||||
|
||||
.. |pg_prepared_xacts| replace:: `pg_prepared_xacts`
|
||||
.. _pg_prepared_xacts: https://www.postgresql.org/docs/current/static/view-pg-prepared-xacts.html
|
||||
|
||||
|
||||
|
||||
.. extension::
|
||||
|
||||
The above methods are the only ones defined by the |DBAPI| protocol.
|
||||
The Psycopg connection objects exports the following additional
|
||||
methods and attributes.
|
||||
|
||||
|
||||
.. attribute:: closed
|
||||
|
||||
Read-only integer attribute: 0 if the connection is open, nonzero if
|
||||
it is closed or broken.
|
||||
|
||||
|
||||
.. method:: cancel
|
||||
|
||||
Cancel the current database operation.
|
||||
|
||||
The method interrupts the processing of the current operation. If no
|
||||
query is being executed, it does nothing. You can call this function
|
||||
from a different thread than the one currently executing a database
|
||||
operation, for instance if you want to cancel a long running query if a
|
||||
button is pushed in the UI. Interrupting query execution will cause the
|
||||
cancelled method to raise a
|
||||
`~psycopg2.extensions.QueryCanceledError`. Note that the termination
|
||||
of the query is not guaranteed to succeed: see the documentation for
|
||||
|PQcancel|_.
|
||||
|
||||
.. |PQcancel| replace:: `!PQcancel()`
|
||||
.. _PQcancel: https://www.postgresql.org/docs/current/static/libpq-cancel.html#LIBPQ-PQCANCEL
|
||||
|
||||
.. versionadded:: 2.3
|
||||
|
||||
|
||||
.. method:: reset
|
||||
|
||||
Reset the connection to the default.
|
||||
|
||||
The method rolls back an eventual pending transaction and executes the
|
||||
PostgreSQL |RESET|_ and |SET SESSION AUTHORIZATION|__ to revert the
|
||||
session to the default values. A two-phase commit transaction prepared
|
||||
using `~connection.tpc_prepare()` will remain in the database
|
||||
available for recover.
|
||||
|
||||
.. |RESET| replace:: :sql:`RESET`
|
||||
.. _RESET: https://www.postgresql.org/docs/current/static/sql-reset.html
|
||||
|
||||
.. |SET SESSION AUTHORIZATION| replace:: :sql:`SET SESSION AUTHORIZATION`
|
||||
.. __: https://www.postgresql.org/docs/current/static/sql-set-session-authorization.html
|
||||
|
||||
.. versionadded:: 2.0.12
|
||||
|
||||
|
||||
.. attribute:: dsn
|
||||
|
||||
Read-only string containing the connection string used by the
|
||||
connection.
|
||||
|
||||
If a password was specified in the connection string it will be
|
||||
obscured.
|
||||
|
||||
|
||||
|
||||
.. rubric:: Transaction control methods and attributes.
|
||||
|
||||
.. index::
|
||||
pair: Transaction; Autocommit
|
||||
pair: Transaction; Isolation level
|
||||
|
||||
.. method:: set_session(isolation_level=None, readonly=None, deferrable=None, autocommit=None)
|
||||
|
||||
Set one or more parameters for the next transactions or statements in
|
||||
the current session.
|
||||
|
||||
:param isolation_level: set the `isolation level`_ for the next
|
||||
transactions/statements. The value can be one of the literal
|
||||
values ``READ UNCOMMITTED``, ``READ COMMITTED``, ``REPEATABLE
|
||||
READ``, ``SERIALIZABLE`` or the equivalent :ref:`constant
|
||||
<isolation-level-constants>` defined in the `~psycopg2.extensions`
|
||||
module.
|
||||
:param readonly: if `!True`, set the connection to read only;
|
||||
read/write if `!False`.
|
||||
:param deferrable: if `!True`, set the connection to deferrable;
|
||||
non deferrable if `!False`. Only available from PostgreSQL 9.1.
|
||||
:param autocommit: switch the connection to autocommit mode: not a
|
||||
PostgreSQL session setting but an alias for setting the
|
||||
`autocommit` attribute.
|
||||
|
||||
.. _isolation level:
|
||||
https://www.postgresql.org/docs/current/static/transaction-iso.html
|
||||
|
||||
Arguments set to `!None` (the default for all) will not be changed.
|
||||
The parameters *isolation_level*, *readonly* and *deferrable* also
|
||||
accept the string ``DEFAULT`` as a value: the effect is to reset the
|
||||
parameter to the server default. Defaults are defined by the server
|
||||
configuration: see values for |default_transaction_isolation|__,
|
||||
|default_transaction_read_only|__, |default_transaction_deferrable|__.
|
||||
|
||||
.. |default_transaction_isolation| replace:: :sql:`default_transaction_isolation`
|
||||
.. __: https://www.postgresql.org/docs/current/static/runtime-config-client.html#GUC-DEFAULT-TRANSACTION-ISOLATION
|
||||
.. |default_transaction_read_only| replace:: :sql:`default_transaction_read_only`
|
||||
.. __: https://www.postgresql.org/docs/current/static/runtime-config-client.html#GUC-DEFAULT-TRANSACTION-READ-ONLY
|
||||
.. |default_transaction_deferrable| replace:: :sql:`default_transaction_deferrable`
|
||||
.. __: https://www.postgresql.org/docs/current/static/runtime-config-client.html#GUC-DEFAULT-TRANSACTION-DEFERRABLE
|
||||
|
||||
The function must be invoked with no transaction in progress.
|
||||
|
||||
.. seealso:: |SET TRANSACTION|_ for further details about the behaviour
|
||||
of the transaction parameters in the server.
|
||||
|
||||
.. |SET TRANSACTION| replace:: :sql:`SET TRANSACTION`
|
||||
.. _SET TRANSACTION: https://www.postgresql.org/docs/current/static/sql-set-transaction.html
|
||||
|
||||
.. versionadded:: 2.4.2
|
||||
|
||||
.. versionchanged:: 2.7
|
||||
Before this version, the function would have set
|
||||
:sql:`default_transaction_*` attribute in the current session;
|
||||
this implementation has the problem of not playing well with
|
||||
external connection pooling working at transaction level and not
|
||||
resetting the state of the session: changing the default
|
||||
transaction would pollute the connections in the pool and create
|
||||
problems to other applications using the same pool.
|
||||
|
||||
Starting from 2.7, if the connection is not autocommit, the
|
||||
transaction characteristics are issued together with :sql:`BEGIN`
|
||||
and will leave the :sql:`default_transaction_*` settings untouched.
|
||||
For example::
|
||||
|
||||
conn.set_session(readonly=True)
|
||||
|
||||
will not change :sql:`default_transaction_read_only`, but
|
||||
following transaction will start with a :sql:`BEGIN READ ONLY`.
|
||||
Conversely, using::
|
||||
|
||||
conn.set_session(readonly=True, autocommit=True)
|
||||
|
||||
will set :sql:`default_transaction_read_only` to :sql:`on` and
|
||||
rely on the server to apply the read only state to whatever
|
||||
transaction, implicit or explicit, is executed in the connection.
|
||||
|
||||
|
||||
.. attribute:: autocommit
|
||||
|
||||
Read/write attribute: if `!True`, no transaction is handled by the
|
||||
driver and every statement sent to the backend has immediate effect;
|
||||
if `!False` a new transaction is started at the first command
|
||||
execution: the methods `commit()` or `rollback()` must be manually
|
||||
invoked to terminate the transaction.
|
||||
|
||||
The autocommit mode is useful to execute commands requiring to be run
|
||||
outside a transaction, such as :sql:`CREATE DATABASE` or
|
||||
:sql:`VACUUM`.
|
||||
|
||||
The default is `!False` (manual commit) as per DBAPI specification.
|
||||
|
||||
.. warning::
|
||||
|
||||
By default, any query execution, including a simple :sql:`SELECT`
|
||||
will start a transaction: for long-running programs, if no further
|
||||
action is taken, the session will remain "idle in transaction", an
|
||||
undesirable condition for several reasons (locks are held by
|
||||
the session, tables bloat...). For long lived scripts, either
|
||||
ensure to terminate a transaction as soon as possible or use an
|
||||
autocommit connection.
|
||||
|
||||
.. versionadded:: 2.4.2
|
||||
|
||||
|
||||
.. attribute:: isolation_level
|
||||
|
||||
Return or set the `transaction isolation level`_ for the current
|
||||
session. The value is one of the :ref:`isolation-level-constants`
|
||||
defined in the `psycopg2.extensions` module. On set it is also
|
||||
possible to use one of the literal values ``READ UNCOMMITTED``, ``READ
|
||||
COMMITTED``, ``REPEATABLE READ``, ``SERIALIZABLE``, ``DEFAULT``.
|
||||
|
||||
.. versionchanged:: 2.7
|
||||
|
||||
the property is writable.
|
||||
|
||||
.. versionchanged:: 2.7
|
||||
|
||||
the default value for `!isolation_level` is
|
||||
`~psycopg2.extensions.ISOLATION_LEVEL_DEFAULT`; previously the
|
||||
property would have queried the server and returned the real value
|
||||
applied. To know this value you can run a query such as :sql:`show
|
||||
transaction_isolation`. Usually the default value is `READ
|
||||
COMMITTED`, but this may be changed in the server configuration.
|
||||
|
||||
This value is now entirely separate from the `autocommit`
|
||||
property: in previous version, if `!autocommit` was set to `!True`
|
||||
this property would have returned
|
||||
`~psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT`; it will now
|
||||
return the server isolation level.
|
||||
|
||||
|
||||
.. attribute:: readonly
|
||||
|
||||
Return or set the read-only status for the current session. Available
|
||||
values are `!True` (new transactions will be in read-only mode),
|
||||
`!False` (new transactions will be writable), `!None` (use the default
|
||||
configured for the server by :sql:`default_transaction_read_only`).
|
||||
|
||||
.. versionadded:: 2.7
|
||||
|
||||
|
||||
.. attribute:: deferrable
|
||||
|
||||
Return or set the `deferrable status`__ for the current session.
|
||||
Available values are `!True` (new transactions will be in deferrable
|
||||
mode), `!False` (new transactions will be in non deferrable mode),
|
||||
`!None` (use the default configured for the server by
|
||||
:sql:`default_transaction_deferrable`).
|
||||
|
||||
.. __: `SET TRANSACTION`_
|
||||
|
||||
.. versionadded:: 2.7
|
||||
|
||||
|
||||
.. method:: set_isolation_level(level)
|
||||
|
||||
.. note::
|
||||
|
||||
This is a legacy method mixing `~conn.isolation_level` and
|
||||
`~conn.autocommit`. Using the respective properties is a better
|
||||
option.
|
||||
|
||||
Set the `transaction isolation level`_ for the current session.
|
||||
The level defines the different phenomena that can happen in the
|
||||
database between concurrent transactions.
|
||||
|
||||
The value set is an integer: symbolic constants are defined in
|
||||
the module `psycopg2.extensions`: see
|
||||
:ref:`isolation-level-constants` for the available values.
|
||||
|
||||
The default level is `~psycopg2.extensions.ISOLATION_LEVEL_DEFAULT`:
|
||||
at this level a transaction is automatically started the first time a
|
||||
database command is executed. If you want an *autocommit* mode,
|
||||
switch to `~psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT` before
|
||||
executing any command::
|
||||
|
||||
>>> conn.set_isolation_level(psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT)
|
||||
|
||||
See also :ref:`transactions-control`.
|
||||
|
||||
|
||||
.. index::
|
||||
pair: Client; Encoding
|
||||
|
||||
.. attribute:: encoding
|
||||
.. method:: set_client_encoding(enc)
|
||||
|
||||
Read or set the client encoding for the current session. The default
|
||||
is the encoding defined by the database. It should be one of the
|
||||
`characters set supported by PostgreSQL`__
|
||||
|
||||
.. __: https://www.postgresql.org/docs/current/static/multibyte.html
|
||||
|
||||
|
||||
.. index::
|
||||
pair: Client; Logging
|
||||
|
||||
.. attribute:: notices
|
||||
|
||||
A list containing all the database messages sent to the client during
|
||||
the session.
|
||||
|
||||
.. doctest::
|
||||
:options: +NORMALIZE_WHITESPACE
|
||||
|
||||
>>> cur.execute("CREATE TABLE foo (id serial PRIMARY KEY);")
|
||||
>>> pprint(conn.notices)
|
||||
['NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "foo_pkey" for table "foo"\n',
|
||||
'NOTICE: CREATE TABLE will create implicit sequence "foo_id_seq" for serial column "foo.id"\n']
|
||||
|
||||
.. versionchanged:: 2.7
|
||||
The `!notices` attribute is writable: the user may replace it
|
||||
with any Python object exposing an `!append()` method. If
|
||||
appending raises an exception the notice is silently
|
||||
dropped.
|
||||
|
||||
To avoid a leak in case excessive notices are generated, only the last
|
||||
50 messages are kept. This check is only in place if the `!notices`
|
||||
attribute is a list: if any other object is used it will be up to the
|
||||
user to guard from leakage.
|
||||
|
||||
You can configure what messages to receive using `PostgreSQL logging
|
||||
configuration parameters`__ such as ``log_statement``,
|
||||
``client_min_messages``, ``log_min_duration_statement`` etc.
|
||||
|
||||
.. __: https://www.postgresql.org/docs/current/static/runtime-config-logging.html
|
||||
|
||||
|
||||
.. attribute:: notifies
|
||||
|
||||
List of `~psycopg2.extensions.Notify` objects containing asynchronous
|
||||
notifications received by the session.
|
||||
|
||||
For other details see :ref:`async-notify`.
|
||||
|
||||
.. versionchanged:: 2.3
|
||||
Notifications are instances of the `!Notify` object. Previously the
|
||||
list was composed by 2 items tuples :samp:`({pid},{channel})` and
|
||||
the payload was not accessible. To keep backward compatibility,
|
||||
`!Notify` objects can still be accessed as 2 items tuples.
|
||||
|
||||
.. versionchanged:: 2.7
|
||||
The `!notifies` attribute is writable: the user may replace it
|
||||
with any Python object exposing an `!append()` method. If
|
||||
appending raises an exception the notification is silently
|
||||
dropped.
|
||||
|
||||
|
||||
.. attribute:: cursor_factory
|
||||
|
||||
The default cursor factory used by `~connection.cursor()` if the
|
||||
parameter is not specified.
|
||||
|
||||
.. versionadded:: 2.5
|
||||
|
||||
|
||||
.. index::
|
||||
pair: Connection; Info
|
||||
|
||||
.. attribute:: info
|
||||
|
||||
A `~psycopg2.extensions.ConnectionInfo` object exposing information
|
||||
about the native libpq connection.
|
||||
|
||||
.. versionadded:: 2.8
|
||||
|
||||
|
||||
.. index::
|
||||
pair: Connection; Status
|
||||
|
||||
.. attribute:: status
|
||||
|
||||
A read-only integer representing the status of the connection.
|
||||
Symbolic constants for the values are defined in the module
|
||||
`psycopg2.extensions`: see :ref:`connection-status-constants`
|
||||
for the available values.
|
||||
|
||||
The status is undefined for `closed` connections.
|
||||
|
||||
|
||||
.. method:: lobject([oid [, mode [, new_oid [, new_file [, lobject_factory]]]]])
|
||||
|
||||
Return a new database large object as a `~psycopg2.extensions.lobject`
|
||||
instance.
|
||||
|
||||
See :ref:`large-objects` for an overview.
|
||||
|
||||
:param oid: The OID of the object to read or write. 0 to create
|
||||
a new large object and and have its OID assigned automatically.
|
||||
:param mode: Access mode to the object, see below.
|
||||
:param new_oid: Create a new object using the specified OID. The
|
||||
function raises `~psycopg2.OperationalError` if the OID is already
|
||||
in use. Default is 0, meaning assign a new one automatically.
|
||||
:param new_file: The name of a file to be imported in the database
|
||||
(using the |lo_import|_ function)
|
||||
:param lobject_factory: Subclass of
|
||||
`~psycopg2.extensions.lobject` to be instantiated.
|
||||
|
||||
.. |lo_import| replace:: `!lo_import()`
|
||||
.. _lo_import: https://www.postgresql.org/docs/current/static/lo-interfaces.html#LO-IMPORT
|
||||
|
||||
Available values for *mode* are:
|
||||
|
||||
======= =========
|
||||
*mode* meaning
|
||||
======= =========
|
||||
``r`` Open for read only
|
||||
``w`` Open for write only
|
||||
``rw`` Open for read/write
|
||||
``n`` Don't open the file
|
||||
``b`` Don't decode read data (return data as `!str` in Python 2 or `!bytes` in Python 3)
|
||||
``t`` Decode read data according to `connection.encoding` (return data as `!unicode` in Python 2 or `!str` in Python 3)
|
||||
======= =========
|
||||
|
||||
``b`` and ``t`` can be specified together with a read/write mode. If
|
||||
neither ``b`` nor ``t`` is specified, the default is ``b`` in Python 2
|
||||
and ``t`` in Python 3.
|
||||
|
||||
.. versionadded:: 2.0.8
|
||||
|
||||
.. versionchanged:: 2.4 added ``b`` and ``t`` mode and unicode
|
||||
support.
|
||||
|
||||
|
||||
.. rubric:: Methods related to asynchronous support
|
||||
|
||||
.. versionadded:: 2.2
|
||||
|
||||
.. seealso:: :ref:`async-support` and :ref:`green-support`.
|
||||
|
||||
|
||||
.. attribute:: async
|
||||
async_
|
||||
|
||||
Read only attribute: 1 if the connection is asynchronous, 0 otherwise.
|
||||
|
||||
.. versionchanged:: 2.7 added the `!async_` alias for Python versions
|
||||
where `!async` is a keyword.
|
||||
|
||||
|
||||
.. method:: poll()
|
||||
|
||||
Used during an asynchronous connection attempt, or when a cursor is
|
||||
executing a query on an asynchronous connection, make communication
|
||||
proceed if it wouldn't block.
|
||||
|
||||
Return one of the constants defined in :ref:`poll-constants`. If it
|
||||
returns `~psycopg2.extensions.POLL_OK` then the connection has been
|
||||
established or the query results are available on the client.
|
||||
Otherwise wait until the file descriptor returned by `fileno()` is
|
||||
ready to read or to write, as explained in :ref:`async-support`.
|
||||
`poll()` should be also used by the function installed by
|
||||
`~psycopg2.extensions.set_wait_callback()` as explained in
|
||||
:ref:`green-support`.
|
||||
|
||||
`poll()` is also used to receive asynchronous notifications from the
|
||||
database: see :ref:`async-notify` from further details.
|
||||
|
||||
|
||||
.. method:: fileno()
|
||||
|
||||
Return the file descriptor underlying the connection: useful to read
|
||||
its status during asynchronous communication.
|
||||
|
||||
|
||||
.. method:: isexecuting()
|
||||
|
||||
Return `!True` if the connection is executing an asynchronous operation.
|
||||
|
||||
|
||||
.. rubric:: Interoperation with other C API modules
|
||||
|
||||
.. attribute:: pgconn_ptr
|
||||
|
||||
Return the internal `!PGconn*` as integer. Useful to pass the libpq
|
||||
raw connection structure to C functions, e.g. via `ctypes`::
|
||||
|
||||
>>> import ctypes
|
||||
>>> import ctypes.util
|
||||
>>> libpq = ctypes.pydll.LoadLibrary(ctypes.util.find_library('pq'))
|
||||
>>> libpq.PQserverVersion.argtypes = [ctypes.c_void_p]
|
||||
>>> libpq.PQserverVersion.restype = ctypes.c_int
|
||||
>>> libpq.PQserverVersion(conn.pgconn_ptr)
|
||||
90611
|
||||
|
||||
.. versionadded:: 2.8
|
||||
|
||||
|
||||
.. method:: get_native_connection()
|
||||
|
||||
Return the internal `!PGconn*` wrapped in a PyCapsule object. This is
|
||||
only useful for passing the `libpq` raw connection associated to this
|
||||
connection object to other C-level modules that may have a use for it.
|
||||
|
||||
.. seealso:: Python C API `Capsules`__ docs.
|
||||
|
||||
.. __: https://docs.python.org/3.1/c-api/capsule.html
|
||||
|
||||
.. versionadded:: 2.8
|
||||
|
||||
|
||||
|
||||
.. rubric:: informative methods of the native connection
|
||||
|
||||
.. note::
|
||||
|
||||
These methods are better accessed using the `~connection.info`
|
||||
attributes and may be dropped in future versions.
|
||||
|
||||
|
||||
.. index::
|
||||
pair: Transaction; Status
|
||||
|
||||
.. method:: get_transaction_status()
|
||||
|
||||
Also available as `~connection.info`\ `!.`\
|
||||
`~psycopg2.extensions.ConnectionInfo.transaction_status`.
|
||||
|
||||
Return the current session transaction status as an integer. Symbolic
|
||||
constants for the values are defined in the module
|
||||
`psycopg2.extensions`: see :ref:`transaction-status-constants`
|
||||
for the available values.
|
||||
|
||||
.. seealso:: libpq docs for `PQtransactionStatus()`__ for details.
|
||||
|
||||
.. __: https://www.postgresql.org/docs/current/static/libpq-status.html#LIBPQ-PQTRANSACTIONSTATUS
|
||||
|
||||
|
||||
.. index::
|
||||
pair: Protocol; Version
|
||||
|
||||
.. attribute:: protocol_version
|
||||
|
||||
Also available as `~connection.info`\ `!.`\
|
||||
`~psycopg2.extensions.ConnectionInfo.protocol_version`.
|
||||
|
||||
A read-only integer representing frontend/backend protocol being used.
|
||||
Currently Psycopg supports only protocol 3, which allows connection
|
||||
to PostgreSQL server from version 7.4. Psycopg versions previous than
|
||||
2.3 support both protocols 2 and 3.
|
||||
|
||||
.. seealso:: libpq docs for `PQprotocolVersion()`__ for details.
|
||||
|
||||
.. __: https://www.postgresql.org/docs/current/static/libpq-status.html#LIBPQ-PQPROTOCOLVERSION
|
||||
|
||||
.. versionadded:: 2.0.12
|
||||
|
||||
|
||||
.. index::
|
||||
pair: Server; Version
|
||||
|
||||
.. attribute:: server_version
|
||||
|
||||
Also available as `~connection.info`\ `!.`\
|
||||
`~psycopg2.extensions.ConnectionInfo.server_version`.
|
||||
|
||||
A read-only integer representing the backend version.
|
||||
|
||||
The number is formed by converting the major, minor, and revision
|
||||
numbers into two-decimal-digit numbers and appending them together.
|
||||
For example, version 8.1.5 will be returned as ``80105``.
|
||||
|
||||
.. seealso:: libpq docs for `PQserverVersion()`__ for details.
|
||||
|
||||
.. __: https://www.postgresql.org/docs/current/static/libpq-status.html#LIBPQ-PQSERVERVERSION
|
||||
|
||||
.. versionadded:: 2.0.12
|
||||
|
||||
|
||||
.. index::
|
||||
pair: Backend; PID
|
||||
|
||||
.. method:: get_backend_pid()
|
||||
|
||||
Also available as `~connection.info`\ `!.`\
|
||||
`~psycopg2.extensions.ConnectionInfo.backend_pid`.
|
||||
|
||||
Returns the process ID (PID) of the backend server process *you
|
||||
connected to*. Note that if you use a connection pool service such as
|
||||
PgBouncer_ this value will not be updated if your connection is
|
||||
switched to a different backend.
|
||||
|
||||
Note that the PID belongs to a process executing on the database
|
||||
server host, not the local host!
|
||||
|
||||
.. seealso:: libpq docs for `PQbackendPID()`__ for details.
|
||||
|
||||
.. __: https://www.postgresql.org/docs/current/static/libpq-status.html#LIBPQ-PQBACKENDPID
|
||||
|
||||
.. versionadded:: 2.0.8
|
||||
|
||||
|
||||
.. index::
|
||||
pair: Server; Parameters
|
||||
|
||||
.. method:: get_parameter_status(parameter)
|
||||
|
||||
Also available as `~connection.info`\ `!.`\
|
||||
`~psycopg2.extensions.ConnectionInfo.parameter_status()`.
|
||||
|
||||
Look up a current parameter setting of the server.
|
||||
|
||||
Potential values for ``parameter`` are: ``server_version``,
|
||||
``server_encoding``, ``client_encoding``, ``is_superuser``,
|
||||
``session_authorization``, ``DateStyle``, ``TimeZone``,
|
||||
``integer_datetimes``, and ``standard_conforming_strings``.
|
||||
|
||||
If server did not report requested parameter, return `!None`.
|
||||
|
||||
.. seealso:: libpq docs for `PQparameterStatus()`__ for details.
|
||||
|
||||
.. __: https://www.postgresql.org/docs/current/static/libpq-status.html#LIBPQ-PQPARAMETERSTATUS
|
||||
|
||||
.. versionadded:: 2.0.12
|
||||
|
||||
|
||||
.. index::
|
||||
pair: Connection; Parameters
|
||||
|
||||
.. method:: get_dsn_parameters()
|
||||
|
||||
Also available as `~connection.info`\ `!.`\
|
||||
`~psycopg2.extensions.ConnectionInfo.dsn_parameters`.
|
||||
|
||||
Get the effective dsn parameters for the connection as a dictionary.
|
||||
|
||||
The *password* parameter is removed from the result.
|
||||
|
||||
Example::
|
||||
|
||||
>>> conn.get_dsn_parameters()
|
||||
{'dbname': 'test', 'user': 'postgres', 'port': '5432', 'sslmode': 'prefer'}
|
||||
|
||||
Requires libpq >= 9.3.
|
||||
|
||||
.. seealso:: libpq docs for `PQconninfo()`__ for details.
|
||||
|
||||
.. __: https://www.postgresql.org/docs/current/static/libpq-connect.html#LIBPQ-PQCONNINFO
|
||||
|
||||
.. versionadded:: 2.7
|
||||
|
||||
|
||||
.. testcode::
|
||||
:hide:
|
||||
|
||||
conn.rollback()
|
||||
668
doc/src/cursor.rst
Normal file
668
doc/src/cursor.rst
Normal file
@ -0,0 +1,668 @@
|
||||
The ``cursor`` class
|
||||
====================
|
||||
|
||||
.. sectionauthor:: Daniele Varrazzo <daniele.varrazzo@gmail.com>
|
||||
|
||||
.. testsetup:: *
|
||||
|
||||
from StringIO import StringIO
|
||||
import sys
|
||||
|
||||
create_test_table()
|
||||
|
||||
# initial data
|
||||
cur.executemany("INSERT INTO test (num, data) VALUES (%s, %s)",
|
||||
[(100, "abc'def"), (None, 'dada'), (42, 'bar')])
|
||||
conn.commit()
|
||||
|
||||
|
||||
.. class:: cursor
|
||||
|
||||
Allows Python code to execute PostgreSQL command in a database session.
|
||||
Cursors are created by the `connection.cursor()` method: they are
|
||||
bound to the connection for the entire lifetime and all the commands are
|
||||
executed in the context of the database session wrapped by the connection.
|
||||
|
||||
Cursors created from the same connection are not isolated, i.e., any
|
||||
changes done to the database by a cursor are immediately visible by the
|
||||
other cursors. Cursors created from different connections can or can not
|
||||
be isolated, depending on the connections' :ref:`isolation level
|
||||
<transactions-control>`. See also `~connection.rollback()` and
|
||||
`~connection.commit()` methods.
|
||||
|
||||
Cursors are *not* thread safe: a multithread application can create
|
||||
many cursors from the same connection and should use each cursor from
|
||||
a single thread. See :ref:`thread-safety` for details.
|
||||
|
||||
Cursors can be used as context managers: leaving the context will close
|
||||
the cursor.
|
||||
|
||||
.. code:: python
|
||||
|
||||
with conn.cursor() as curs:
|
||||
curs.execute(SQL)
|
||||
|
||||
# the cursor is now closed
|
||||
|
||||
|
||||
.. attribute:: description
|
||||
|
||||
Read-only attribute describing the result of a query. It is a
|
||||
sequence of `~psycopg2.extensions.Column` instances, each one
|
||||
describing one result column in order. The attribute is `!None` for
|
||||
operations that do not return rows or if the cursor has not had an
|
||||
operation invoked via the |execute*|_ methods yet.
|
||||
|
||||
For compatibility with the DB-API, every object can be unpacked as a
|
||||
7-items sequence: the attributes retuned this way are the following.
|
||||
For further details and other attributes available check the
|
||||
`~psycopg2.extensions.Column` documentation.
|
||||
|
||||
0. `~psycopg2.extensions.Column.name`: the name of the column returned.
|
||||
|
||||
1. `~psycopg2.extensions.Column.type_code`: the PostgreSQL OID of the
|
||||
column.
|
||||
|
||||
2. `~psycopg2.extensions.Column.display_size`: the actual length of
|
||||
the column in bytes.
|
||||
|
||||
3. `~psycopg2.extensions.Column.internal_size`: the size in bytes of
|
||||
the column associated to this column on the server.
|
||||
|
||||
4. `~psycopg2.extensions.Column.precision`: total number of
|
||||
significant digits in columns of type |NUMERIC|. `!None`
|
||||
for other types.
|
||||
|
||||
5. `~psycopg2.extensions.Column.scale`: count of decimal digits in
|
||||
the fractional part in columns of type |NUMERIC|. `!None`
|
||||
for other types.
|
||||
|
||||
6. `~psycopg2.extensions.Column.null_ok`: always `!None` as not easy
|
||||
to retrieve from the libpq.
|
||||
|
||||
.. versionchanged:: 2.4
|
||||
if possible, columns descriptions are named tuple instead of
|
||||
regular tuples.
|
||||
|
||||
.. versionchanged:: 2.8
|
||||
columns descriptions are instances of `!Column`, exposing extra
|
||||
attributes.
|
||||
|
||||
.. |NUMERIC| replace:: :sql:`NUMERIC`
|
||||
|
||||
.. method:: close()
|
||||
|
||||
Close the cursor now (rather than whenever `del` is executed).
|
||||
The cursor will be unusable from this point forward; an
|
||||
`~psycopg2.InterfaceError` will be raised if any operation is
|
||||
attempted with the cursor.
|
||||
|
||||
.. versionchanged:: 2.5 if the cursor is used in a ``with`` statement,
|
||||
the method is automatically called at the end of the ``with``
|
||||
block.
|
||||
|
||||
|
||||
.. attribute:: closed
|
||||
|
||||
Read-only boolean attribute: specifies if the cursor is closed
|
||||
(`!True`) or not (`!False`).
|
||||
|
||||
.. extension::
|
||||
|
||||
The `closed` attribute is a Psycopg extension to the
|
||||
|DBAPI|.
|
||||
|
||||
.. versionadded:: 2.0.7
|
||||
|
||||
|
||||
.. attribute:: connection
|
||||
|
||||
Read-only attribute returning a reference to the `connection`
|
||||
object on which the cursor was created.
|
||||
|
||||
|
||||
.. attribute:: name
|
||||
|
||||
Read-only attribute containing the name of the cursor if it was
|
||||
created as named cursor by `connection.cursor()`, or `!None` if
|
||||
it is a client side cursor. See :ref:`server-side-cursors`.
|
||||
|
||||
.. extension::
|
||||
|
||||
The `name` attribute is a Psycopg extension to the |DBAPI|.
|
||||
|
||||
|
||||
.. attribute:: scrollable
|
||||
|
||||
Read/write attribute: specifies if a named cursor is declared
|
||||
:sql:`SCROLL`, hence is capable to scroll backwards (using
|
||||
`~cursor.scroll()`). If `!True`, the cursor can be scrolled backwards,
|
||||
if `!False` it is never scrollable. If `!None` (default) the cursor
|
||||
scroll option is not specified, usually but not always meaning no
|
||||
backward scroll (see the |declare-notes|__).
|
||||
|
||||
.. |declare-notes| replace:: :sql:`DECLARE` notes
|
||||
.. __: https://www.postgresql.org/docs/current/static/sql-declare.html#SQL-DECLARE-NOTES
|
||||
|
||||
.. note::
|
||||
|
||||
set the value before calling `~cursor.execute()` or use the
|
||||
`connection.cursor()` *scrollable* parameter, otherwise the value
|
||||
will have no effect.
|
||||
|
||||
.. versionadded:: 2.5
|
||||
|
||||
.. extension::
|
||||
|
||||
The `scrollable` attribute is a Psycopg extension to the |DBAPI|.
|
||||
|
||||
|
||||
.. attribute:: withhold
|
||||
|
||||
Read/write attribute: specifies if a named cursor lifetime should
|
||||
extend outside of the current transaction, i.e., it is possible to
|
||||
fetch from the cursor even after a `connection.commit()` (but not after
|
||||
a `connection.rollback()`). See :ref:`server-side-cursors`
|
||||
|
||||
.. note::
|
||||
|
||||
set the value before calling `~cursor.execute()` or use the
|
||||
`connection.cursor()` *withhold* parameter, otherwise the value
|
||||
will have no effect.
|
||||
|
||||
.. versionadded:: 2.4.3
|
||||
|
||||
.. extension::
|
||||
|
||||
The `withhold` attribute is a Psycopg extension to the |DBAPI|.
|
||||
|
||||
|
||||
.. |execute*| replace:: `execute*()`
|
||||
|
||||
.. _execute*:
|
||||
|
||||
.. rubric:: Commands execution methods
|
||||
|
||||
|
||||
.. method:: execute(query, vars=None)
|
||||
|
||||
Execute a database operation (query or command).
|
||||
|
||||
Parameters may be provided as sequence or mapping and will be bound to
|
||||
variables in the operation. Variables are specified either with
|
||||
positional (``%s``) or named (:samp:`%({name})s`) placeholders. See
|
||||
:ref:`query-parameters`.
|
||||
|
||||
The method returns `!None`. If a query was executed, the returned
|
||||
values can be retrieved using |fetch*|_ methods.
|
||||
|
||||
|
||||
.. method:: executemany(query, vars_list)
|
||||
|
||||
Execute a database operation (query or command) against all parameter
|
||||
tuples or mappings found in the sequence *vars_list*.
|
||||
|
||||
The function is mostly useful for commands that update the database:
|
||||
any result set returned by the query is discarded.
|
||||
|
||||
Parameters are bounded to the query using the same rules described in
|
||||
the `~cursor.execute()` method.
|
||||
|
||||
.. warning::
|
||||
In its current implementation this method is not faster than
|
||||
executing `~cursor.execute()` in a loop. For better performance
|
||||
you can use the functions described in :ref:`fast-exec`.
|
||||
|
||||
|
||||
.. method:: callproc(procname [, parameters])
|
||||
|
||||
Call a stored database procedure with the given name. The sequence of
|
||||
parameters must contain one entry for each argument that the procedure
|
||||
expects. Overloaded procedures are supported. Named parameters can be
|
||||
used by supplying the parameters as a dictionary.
|
||||
|
||||
This function is, at present, not DBAPI-compliant. The return value is
|
||||
supposed to consist of the sequence of parameters with modified output
|
||||
and input/output parameters. In future versions, the DBAPI-compliant
|
||||
return value may be implemented, but for now the function returns None.
|
||||
|
||||
The procedure may provide a result set as output. This is then made
|
||||
available through the standard |fetch*|_ methods.
|
||||
|
||||
.. versionchanged:: 2.7
|
||||
added support for named arguments.
|
||||
|
||||
.. method:: mogrify(operation [, parameters])
|
||||
|
||||
Return a query string after arguments binding. The string returned is
|
||||
exactly the one that would be sent to the database running the
|
||||
`~cursor.execute()` method or similar.
|
||||
|
||||
The returned string is always a bytes string.
|
||||
|
||||
>>> cur.mogrify("INSERT INTO test (num, data) VALUES (%s, %s)", (42, 'bar'))
|
||||
"INSERT INTO test (num, data) VALUES (42, E'bar')"
|
||||
|
||||
.. extension::
|
||||
|
||||
The `mogrify()` method is a Psycopg extension to the |DBAPI|.
|
||||
|
||||
.. method:: setinputsizes(sizes)
|
||||
|
||||
This method is exposed in compliance with the |DBAPI|. It currently
|
||||
does nothing but it is safe to call it.
|
||||
|
||||
|
||||
|
||||
.. |fetch*| replace:: `!fetch*()`
|
||||
|
||||
.. _fetch*:
|
||||
|
||||
.. rubric:: Results retrieval methods
|
||||
|
||||
|
||||
The following methods are used to read data from the database after an
|
||||
`~cursor.execute()` call.
|
||||
|
||||
.. _cursor-iterable:
|
||||
|
||||
.. note::
|
||||
|
||||
`cursor` objects are iterable, so, instead of calling
|
||||
explicitly `~cursor.fetchone()` in a loop, the object itself can
|
||||
be used:
|
||||
|
||||
>>> cur.execute("SELECT * FROM test;")
|
||||
>>> for record in cur:
|
||||
... print record
|
||||
...
|
||||
(1, 100, "abc'def")
|
||||
(2, None, 'dada')
|
||||
(3, 42, 'bar')
|
||||
|
||||
.. versionchanged:: 2.4
|
||||
iterating over a :ref:`named cursor <server-side-cursors>`
|
||||
fetches `~cursor.itersize` records at time from the backend.
|
||||
Previously only one record was fetched per roundtrip, resulting
|
||||
in a large overhead.
|
||||
|
||||
.. method:: fetchone()
|
||||
|
||||
Fetch the next row of a query result set, returning a single tuple,
|
||||
or `!None` when no more data is available:
|
||||
|
||||
>>> cur.execute("SELECT * FROM test WHERE id = %s", (3,))
|
||||
>>> cur.fetchone()
|
||||
(3, 42, 'bar')
|
||||
|
||||
A `~psycopg2.ProgrammingError` is raised if the previous call
|
||||
to |execute*|_ did not produce any result set or no call was issued
|
||||
yet.
|
||||
|
||||
|
||||
.. method:: fetchmany([size=cursor.arraysize])
|
||||
|
||||
Fetch the next set of rows of a query result, returning a list of
|
||||
tuples. An empty list is returned when no more rows are available.
|
||||
|
||||
The number of rows to fetch per call is specified by the parameter.
|
||||
If it is not given, the cursor's `~cursor.arraysize` determines
|
||||
the number of rows to be fetched. The method should try to fetch as
|
||||
many rows as indicated by the size parameter. If this is not possible
|
||||
due to the specified number of rows not being available, fewer rows
|
||||
may be returned:
|
||||
|
||||
>>> cur.execute("SELECT * FROM test;")
|
||||
>>> cur.fetchmany(2)
|
||||
[(1, 100, "abc'def"), (2, None, 'dada')]
|
||||
>>> cur.fetchmany(2)
|
||||
[(3, 42, 'bar')]
|
||||
>>> cur.fetchmany(2)
|
||||
[]
|
||||
|
||||
A `~psycopg2.ProgrammingError` is raised if the previous call to
|
||||
|execute*|_ did not produce any result set or no call was issued yet.
|
||||
|
||||
Note there are performance considerations involved with the size
|
||||
parameter. For optimal performance, it is usually best to use the
|
||||
`~cursor.arraysize` attribute. If the size parameter is used,
|
||||
then it is best for it to retain the same value from one
|
||||
`fetchmany()` call to the next.
|
||||
|
||||
|
||||
.. method:: fetchall()
|
||||
|
||||
Fetch all (remaining) rows of a query result, returning them as a list
|
||||
of tuples. An empty list is returned if there is no more record to
|
||||
fetch.
|
||||
|
||||
>>> cur.execute("SELECT * FROM test;")
|
||||
>>> cur.fetchall()
|
||||
[(1, 100, "abc'def"), (2, None, 'dada'), (3, 42, 'bar')]
|
||||
|
||||
A `~psycopg2.ProgrammingError` is raised if the previous call to
|
||||
|execute*|_ did not produce any result set or no call was issued yet.
|
||||
|
||||
|
||||
.. method:: scroll(value [, mode='relative'])
|
||||
|
||||
Scroll the cursor in the result set to a new position according
|
||||
to mode.
|
||||
|
||||
If `mode` is ``relative`` (default), value is taken as offset to
|
||||
the current position in the result set, if set to ``absolute``,
|
||||
value states an absolute target position.
|
||||
|
||||
If the scroll operation would leave the result set, a
|
||||
`~psycopg2.ProgrammingError` is raised and the cursor position is
|
||||
not changed.
|
||||
|
||||
.. note::
|
||||
|
||||
According to the |DBAPI|_, the exception raised for a cursor out
|
||||
of bound should have been `!IndexError`. The best option is
|
||||
probably to catch both exceptions in your code::
|
||||
|
||||
try:
|
||||
cur.scroll(1000 * 1000)
|
||||
except (ProgrammingError, IndexError), exc:
|
||||
deal_with_it(exc)
|
||||
|
||||
The method can be used both for client-side cursors and
|
||||
:ref:`server-side cursors <server-side-cursors>`. Server-side cursors
|
||||
can usually scroll backwards only if declared `~cursor.scrollable`.
|
||||
Moving out-of-bound in a server-side cursor doesn't result in an
|
||||
exception, if the backend doesn't raise any (Postgres doesn't tell us
|
||||
in a reliable way if we went out of bound).
|
||||
|
||||
|
||||
.. attribute:: arraysize
|
||||
|
||||
This read/write attribute specifies the number of rows to fetch at a
|
||||
time with `~cursor.fetchmany()`. It defaults to 1 meaning to fetch
|
||||
a single row at a time.
|
||||
|
||||
|
||||
.. attribute:: itersize
|
||||
|
||||
Read/write attribute specifying the number of rows to fetch from the
|
||||
backend at each network roundtrip during :ref:`iteration
|
||||
<cursor-iterable>` on a :ref:`named cursor <server-side-cursors>`. The
|
||||
default is 2000.
|
||||
|
||||
.. versionadded:: 2.4
|
||||
|
||||
.. extension::
|
||||
|
||||
The `itersize` attribute is a Psycopg extension to the |DBAPI|.
|
||||
|
||||
|
||||
.. attribute:: rowcount
|
||||
|
||||
This read-only attribute specifies the number of rows that the last
|
||||
|execute*|_ produced (for :abbr:`DQL (Data Query Language)` statements
|
||||
like :sql:`SELECT`) or affected (for
|
||||
:abbr:`DML (Data Manipulation Language)` statements like :sql:`UPDATE`
|
||||
or :sql:`INSERT`).
|
||||
|
||||
The attribute is -1 in case no |execute*| has been performed on
|
||||
the cursor or the row count of the last operation if it can't be
|
||||
determined by the interface.
|
||||
|
||||
.. note::
|
||||
The |DBAPI|_ interface reserves to redefine the latter case to
|
||||
have the object return `!None` instead of -1 in future versions
|
||||
of the specification.
|
||||
|
||||
|
||||
.. attribute:: rownumber
|
||||
|
||||
This read-only attribute provides the current 0-based index of the
|
||||
cursor in the result set or `!None` if the index cannot be
|
||||
determined.
|
||||
|
||||
The index can be seen as index of the cursor in a sequence (the result
|
||||
set). The next fetch operation will fetch the row indexed by
|
||||
`rownumber` in that sequence.
|
||||
|
||||
|
||||
.. index:: oid
|
||||
|
||||
.. attribute:: lastrowid
|
||||
|
||||
This read-only attribute provides the OID of the last row inserted
|
||||
by the cursor. If the table wasn't created with OID support or the
|
||||
last operation is not a single record insert, the attribute is set to
|
||||
`!None`.
|
||||
|
||||
.. note::
|
||||
|
||||
PostgreSQL currently advices to not create OIDs on the tables and
|
||||
the default for |CREATE-TABLE|__ is to not support them. The
|
||||
|INSERT-RETURNING|__ syntax available from PostgreSQL 8.3 allows
|
||||
more flexibility.
|
||||
|
||||
.. |CREATE-TABLE| replace:: :sql:`CREATE TABLE`
|
||||
.. __: https://www.postgresql.org/docs/current/static/sql-createtable.html
|
||||
|
||||
.. |INSERT-RETURNING| replace:: :sql:`INSERT ... RETURNING`
|
||||
.. __: https://www.postgresql.org/docs/current/static/sql-insert.html
|
||||
|
||||
|
||||
.. attribute:: query
|
||||
|
||||
Read-only attribute containing the body of the last query sent to the
|
||||
backend (including bound arguments) as bytes string. `!None` if no
|
||||
query has been executed yet:
|
||||
|
||||
>>> cur.execute("INSERT INTO test (num, data) VALUES (%s, %s)", (42, 'bar'))
|
||||
>>> cur.query
|
||||
"INSERT INTO test (num, data) VALUES (42, E'bar')"
|
||||
|
||||
.. extension::
|
||||
|
||||
The `query` attribute is a Psycopg extension to the |DBAPI|.
|
||||
|
||||
|
||||
.. attribute:: statusmessage
|
||||
|
||||
Read-only attribute containing the message returned by the last
|
||||
command:
|
||||
|
||||
>>> cur.execute("INSERT INTO test (num, data) VALUES (%s, %s)", (42, 'bar'))
|
||||
>>> cur.statusmessage
|
||||
'INSERT 0 1'
|
||||
|
||||
.. extension::
|
||||
|
||||
The `statusmessage` attribute is a Psycopg extension to the
|
||||
|DBAPI|.
|
||||
|
||||
|
||||
.. method:: cast(oid, s)
|
||||
|
||||
Convert a value from the PostgreSQL string representation to a Python
|
||||
object.
|
||||
|
||||
Use the most specific of the typecasters registered by
|
||||
`~psycopg2.extensions.register_type()`.
|
||||
|
||||
.. versionadded:: 2.4
|
||||
|
||||
.. extension::
|
||||
|
||||
The `cast()` method is a Psycopg extension to the |DBAPI|.
|
||||
|
||||
|
||||
.. attribute:: tzinfo_factory
|
||||
|
||||
The time zone factory used to handle data types such as
|
||||
:sql:`TIMESTAMP WITH TIME ZONE`. It should be a `~datetime.tzinfo`
|
||||
object. Default is `datetime.timezone`.
|
||||
|
||||
.. versionchanged:: 2.9
|
||||
previosly the default factory was `psycopg2.tz.FixedOffsetTimezone`.
|
||||
|
||||
|
||||
.. method:: nextset()
|
||||
|
||||
This method is not supported (PostgreSQL does not have multiple data
|
||||
sets) and will raise a `~psycopg2.NotSupportedError` exception.
|
||||
|
||||
|
||||
.. method:: setoutputsize(size [, column])
|
||||
|
||||
This method is exposed in compliance with the |DBAPI|. It currently
|
||||
does nothing but it is safe to call it.
|
||||
|
||||
|
||||
|
||||
.. rubric:: COPY-related methods
|
||||
|
||||
Efficiently copy data from file-like objects to the database and back. See
|
||||
:ref:`copy` for an overview.
|
||||
|
||||
.. extension::
|
||||
|
||||
The :sql:`COPY` command is a PostgreSQL extension to the SQL standard.
|
||||
As such, its support is a Psycopg extension to the |DBAPI|.
|
||||
|
||||
.. method:: copy_from(file, table, sep='\\t', null='\\\\N', size=8192, columns=None)
|
||||
|
||||
Read data *from* the file-like object *file* appending them to
|
||||
the table named *table*.
|
||||
|
||||
:param file: file-like object to read data from. It must have both
|
||||
`!read()` and `!readline()` methods.
|
||||
:param table: name of the table to copy data into.
|
||||
:param sep: columns separator expected in the file. Defaults to a tab.
|
||||
:param null: textual representation of :sql:`NULL` in the file.
|
||||
The default is the two characters string ``\N``.
|
||||
:param size: size of the buffer used to read from the file.
|
||||
:param columns: iterable with name of the columns to import.
|
||||
The length and types should match the content of the file to read.
|
||||
If not specified, it is assumed that the entire table matches the
|
||||
file structure.
|
||||
|
||||
Example::
|
||||
|
||||
>>> f = StringIO("42\tfoo\n74\tbar\n")
|
||||
>>> cur.copy_from(f, 'test', columns=('num', 'data'))
|
||||
>>> cur.execute("select * from test where id > 5;")
|
||||
>>> cur.fetchall()
|
||||
[(6, 42, 'foo'), (7, 74, 'bar')]
|
||||
|
||||
.. note:: the name of the table is not quoted: if the table name
|
||||
contains uppercase letters or special characters it must be quoted
|
||||
with double quotes::
|
||||
|
||||
cur.copy_from(f, '"TABLE"')
|
||||
|
||||
|
||||
.. versionchanged:: 2.0.6
|
||||
added the *columns* parameter.
|
||||
|
||||
.. versionchanged:: 2.4
|
||||
data read from files implementing the `io.TextIOBase` interface
|
||||
are encoded in the connection `~connection.encoding` when sent to
|
||||
the backend.
|
||||
|
||||
.. method:: copy_to(file, table, sep='\\t', null='\\\\N', columns=None)
|
||||
|
||||
Write the content of the table named *table* *to* the file-like
|
||||
object *file*. See :ref:`copy` for an overview.
|
||||
|
||||
:param file: file-like object to write data into. It must have a
|
||||
`!write()` method.
|
||||
:param table: name of the table to copy data from.
|
||||
:param sep: columns separator expected in the file. Defaults to a tab.
|
||||
:param null: textual representation of :sql:`NULL` in the file.
|
||||
The default is the two characters string ``\N``.
|
||||
:param columns: iterable with name of the columns to export.
|
||||
If not specified, export all the columns.
|
||||
|
||||
Example::
|
||||
|
||||
>>> cur.copy_to(sys.stdout, 'test', sep="|")
|
||||
1|100|abc'def
|
||||
2|\N|dada
|
||||
...
|
||||
|
||||
.. note:: the name of the table is not quoted: if the table name
|
||||
contains uppercase letters or special characters it must be quoted
|
||||
with double quotes::
|
||||
|
||||
cur.copy_to(f, '"TABLE"')
|
||||
|
||||
.. versionchanged:: 2.0.6
|
||||
added the *columns* parameter.
|
||||
|
||||
.. versionchanged:: 2.4
|
||||
data sent to files implementing the `io.TextIOBase` interface
|
||||
are decoded in the connection `~connection.encoding` when read
|
||||
from the backend.
|
||||
|
||||
|
||||
.. method:: copy_expert(sql, file, size=8192)
|
||||
|
||||
Submit a user-composed :sql:`COPY` statement. The method is useful to
|
||||
handle all the parameters that PostgreSQL makes available (see
|
||||
|COPY|__ command documentation).
|
||||
|
||||
:param sql: the :sql:`COPY` statement to execute.
|
||||
:param file: a file-like object to read or write (according to *sql*).
|
||||
:param size: size of the read buffer to be used in :sql:`COPY FROM`.
|
||||
|
||||
The *sql* statement should be in the form :samp:`COPY {table} TO
|
||||
STDOUT` to export :samp:`{table}` to the *file* object passed as
|
||||
argument or :samp:`COPY {table} FROM STDIN` to import the content of
|
||||
the *file* object into :samp:`{table}`. If you need to compose a
|
||||
:sql:`COPY` statement dynamically (because table, fields, or query
|
||||
parameters are in Python variables) you may use the objects provided
|
||||
by the `psycopg2.sql` module.
|
||||
|
||||
*file* must be a readable file-like object (as required by
|
||||
`~cursor.copy_from()`) for *sql* statement :sql:`COPY ... FROM STDIN`
|
||||
or a writable one (as required by `~cursor.copy_to()`) for :sql:`COPY
|
||||
... TO STDOUT`.
|
||||
|
||||
Example:
|
||||
|
||||
>>> cur.copy_expert("COPY test TO STDOUT WITH CSV HEADER", sys.stdout)
|
||||
id,num,data
|
||||
1,100,abc'def
|
||||
2,,dada
|
||||
...
|
||||
|
||||
.. |COPY| replace:: :sql:`COPY`
|
||||
.. __: https://www.postgresql.org/docs/current/static/sql-copy.html
|
||||
|
||||
.. versionadded:: 2.0.6
|
||||
|
||||
.. versionchanged:: 2.4
|
||||
files implementing the `io.TextIOBase` interface are dealt with
|
||||
using Unicode data instead of bytes.
|
||||
|
||||
|
||||
.. rubric:: Interoperation with other C API modules
|
||||
|
||||
.. attribute:: pgresult_ptr
|
||||
|
||||
Return the cursor's internal `!PGresult*` as integer. Useful to pass
|
||||
the libpq raw result structure to C functions, e.g. via `ctypes`::
|
||||
|
||||
>>> import ctypes
|
||||
>>> libpq = ctypes.pydll.LoadLibrary(ctypes.util.find_library('pq'))
|
||||
>>> libpq.PQcmdStatus.argtypes = [ctypes.c_void_p]
|
||||
>>> libpq.PQcmdStatus.restype = ctypes.c_char_p
|
||||
|
||||
>>> curs.execute("select 'x'")
|
||||
>>> libpq.PQcmdStatus(curs.pgresult_ptr)
|
||||
b'SELECT 1'
|
||||
|
||||
.. versionadded:: 2.8
|
||||
|
||||
.. testcode::
|
||||
:hide:
|
||||
|
||||
conn.rollback()
|
||||
76
doc/src/errorcodes.rst
Normal file
76
doc/src/errorcodes.rst
Normal file
@ -0,0 +1,76 @@
|
||||
`psycopg2.errorcodes` -- Error codes defined by PostgreSQL
|
||||
===============================================================
|
||||
|
||||
.. sectionauthor:: Daniele Varrazzo <daniele.varrazzo@gmail.com>
|
||||
|
||||
.. index::
|
||||
single: Error; Codes
|
||||
|
||||
.. module:: psycopg2.errorcodes
|
||||
|
||||
.. testsetup:: *
|
||||
|
||||
from psycopg2 import errorcodes
|
||||
|
||||
.. versionadded:: 2.0.6
|
||||
|
||||
This module contains symbolic names for all PostgreSQL error codes and error
|
||||
classes codes. Subclasses of `~psycopg2.Error` make the PostgreSQL error
|
||||
code available in the `~psycopg2.Error.pgcode` attribute.
|
||||
|
||||
From PostgreSQL documentation:
|
||||
|
||||
All messages emitted by the PostgreSQL server are assigned five-character
|
||||
error codes that follow the SQL standard's conventions for :sql:`SQLSTATE`
|
||||
codes. Applications that need to know which error condition has occurred
|
||||
should usually test the error code, rather than looking at the textual
|
||||
error message. The error codes are less likely to change across
|
||||
PostgreSQL releases, and also are not subject to change due to
|
||||
localization of error messages. Note that some, but not all, of the error
|
||||
codes produced by PostgreSQL are defined by the SQL standard; some
|
||||
additional error codes for conditions not defined by the standard have
|
||||
been invented or borrowed from other databases.
|
||||
|
||||
According to the standard, the first two characters of an error code
|
||||
denote a class of errors, while the last three characters indicate a
|
||||
specific condition within that class. Thus, an application that does not
|
||||
recognize the specific error code can still be able to infer what to do
|
||||
from the error class.
|
||||
|
||||
.. seealso:: `PostgreSQL Error Codes table`__
|
||||
|
||||
.. __: https://www.postgresql.org/docs/current/static/errcodes-appendix.html#ERRCODES-TABLE
|
||||
|
||||
|
||||
An example of the available constants defined in the module:
|
||||
|
||||
>>> errorcodes.CLASS_SYNTAX_ERROR_OR_ACCESS_RULE_VIOLATION
|
||||
'42'
|
||||
>>> errorcodes.UNDEFINED_TABLE
|
||||
'42P01'
|
||||
|
||||
Constants representing all the error values defined by PostgreSQL versions
|
||||
between 8.1 and 13 are included in the module.
|
||||
|
||||
|
||||
.. autofunction:: lookup(code)
|
||||
|
||||
.. doctest::
|
||||
|
||||
>>> try:
|
||||
... cur.execute("SELECT ouch FROM aargh;")
|
||||
... except Exception as e:
|
||||
... pass
|
||||
...
|
||||
>>> errorcodes.lookup(e.pgcode[:2])
|
||||
'CLASS_SYNTAX_ERROR_OR_ACCESS_RULE_VIOLATION'
|
||||
>>> errorcodes.lookup(e.pgcode)
|
||||
'UNDEFINED_TABLE'
|
||||
|
||||
.. versionadded:: 2.0.14
|
||||
|
||||
|
||||
.. testcode::
|
||||
:hide:
|
||||
|
||||
conn.rollback()
|
||||
83
doc/src/errors.rst
Normal file
83
doc/src/errors.rst
Normal file
@ -0,0 +1,83 @@
|
||||
`psycopg2.errors` -- Exception classes mapping PostgreSQL errors
|
||||
================================================================
|
||||
|
||||
.. sectionauthor:: Daniele Varrazzo <daniele.varrazzo@gmail.com>
|
||||
|
||||
.. index::
|
||||
single: Error; Class
|
||||
|
||||
.. module:: psycopg2.errors
|
||||
|
||||
.. versionadded:: 2.8
|
||||
|
||||
.. versionchanged:: 2.8.4 added errors introduced in PostgreSQL 12
|
||||
|
||||
.. versionchanged:: 2.8.6 added errors introduced in PostgreSQL 13
|
||||
|
||||
This module exposes the classes psycopg raises upon receiving an error from
|
||||
the database with a :sql:`SQLSTATE` value attached (available in the
|
||||
`~psycopg2.Error.pgcode` attribute). The content of the module is generated
|
||||
from the PostgreSQL source code and includes classes for every error defined
|
||||
by PostgreSQL in versions between 9.1 and 13.
|
||||
|
||||
Every class in the module is named after what referred as "condition name" `in
|
||||
the documentation`__, converted to CamelCase: e.g. the error 22012,
|
||||
``division_by_zero`` is exposed by this module as the class `!DivisionByZero`.
|
||||
|
||||
.. __: https://www.postgresql.org/docs/current/static/errcodes-appendix.html#ERRCODES-TABLE
|
||||
|
||||
Every exception class is a subclass of one of the :ref:`standard DB-API
|
||||
exception <dbapi-exceptions>` and expose the `~psycopg2.Error` interface.
|
||||
Each class' superclass is what used to be raised by psycopg in versions before
|
||||
the introduction of this module, so everything should be compatible with
|
||||
previously written code catching one the DB-API class: if your code used to
|
||||
catch `!IntegrityError` to detect a duplicate entry, it will keep on working
|
||||
even if a more specialised subclass such as `UniqueViolation` is raised.
|
||||
|
||||
The new classes allow a more idiomatic way to check and process a specific
|
||||
error among the many the database may return. For instance, in order to check
|
||||
that a table is locked, the following code could have been used previously:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
try:
|
||||
cur.execute("LOCK TABLE mytable IN ACCESS EXCLUSIVE MODE NOWAIT")
|
||||
except psycopg2.OperationalError as e:
|
||||
if e.pgcode == psycopg2.errorcodes.LOCK_NOT_AVAILABLE:
|
||||
locked = True
|
||||
else:
|
||||
raise
|
||||
|
||||
While this method is still available, the specialised class allows for a more
|
||||
idiomatic error handler:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
try:
|
||||
cur.execute("LOCK TABLE mytable IN ACCESS EXCLUSIVE MODE NOWAIT")
|
||||
except psycopg2.errors.LockNotAvailable:
|
||||
locked = True
|
||||
|
||||
|
||||
.. autofunction:: lookup
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
try:
|
||||
cur.execute("LOCK TABLE mytable IN ACCESS EXCLUSIVE MODE NOWAIT")
|
||||
except psycopg2.errors.lookup("55P03"):
|
||||
locked = True
|
||||
|
||||
|
||||
SQLSTATE exception classes
|
||||
--------------------------
|
||||
|
||||
The following table contains the list of all the SQLSTATE classes exposed by
|
||||
the module.
|
||||
|
||||
Note that, for completeness, the module also exposes all the
|
||||
:ref:`DB-API-defined exceptions <dbapi-exceptions>` and :ref:`a few
|
||||
psycopg-specific ones <extension-exceptions>` exposed by the `!extensions`
|
||||
module, which are not listed here.
|
||||
|
||||
.. include:: sqlstate_errors.rst
|
||||
1010
doc/src/extensions.rst
Normal file
1010
doc/src/extensions.rst
Normal file
File diff suppressed because it is too large
Load Diff
1085
doc/src/extras.rst
Normal file
1085
doc/src/extras.rst
Normal file
File diff suppressed because it is too large
Load Diff
382
doc/src/faq.rst
Normal file
382
doc/src/faq.rst
Normal file
@ -0,0 +1,382 @@
|
||||
Frequently Asked Questions
|
||||
==========================
|
||||
|
||||
.. sectionauthor:: Daniele Varrazzo <daniele.varrazzo@gmail.com>
|
||||
|
||||
Here are a few gotchas you may encounter using `psycopg2`. Feel free to
|
||||
suggest new entries!
|
||||
|
||||
|
||||
Meta
|
||||
----
|
||||
|
||||
.. _faq-question:
|
||||
.. cssclass:: faq
|
||||
|
||||
How do I ask a question?
|
||||
- Have you first checked if your question is answered already in the
|
||||
documentation?
|
||||
|
||||
- If your question is about installing psycopg, have you checked the
|
||||
:ref:`install FAQ <faq-compile>` and the :ref:`install docs
|
||||
<installation>`?
|
||||
|
||||
- Have you googled for your error message?
|
||||
|
||||
- If you haven't found an answer yet, please write to the `Mailing List`_.
|
||||
|
||||
- If you haven't found a bug, DO NOT write to the bug tracker to ask
|
||||
questions. You will only get piro grumpy.
|
||||
|
||||
.. _mailing list: https://www.postgresql.org/list/psycopg/
|
||||
|
||||
|
||||
.. _faq-transactions:
|
||||
|
||||
Problems with transactions handling
|
||||
-----------------------------------
|
||||
|
||||
.. _faq-idle-in-transaction:
|
||||
.. cssclass:: faq
|
||||
|
||||
Why does `!psycopg2` leave database sessions "idle in transaction"?
|
||||
Psycopg normally starts a new transaction the first time a query is
|
||||
executed, e.g. calling `cursor.execute()`, even if the command is a
|
||||
:sql:`SELECT`. The transaction is not closed until an explicit
|
||||
`~connection.commit()` or `~connection.rollback()`.
|
||||
|
||||
If you are writing a long-living program, you should probably make sure to
|
||||
call one of the transaction closing methods before leaving the connection
|
||||
unused for a long time (which may also be a few seconds, depending on the
|
||||
concurrency level in your database). Alternatively you can use a
|
||||
connection in `~connection.autocommit` mode to avoid a new transaction to
|
||||
be started at the first command.
|
||||
|
||||
|
||||
.. _faq-transaction-aborted:
|
||||
.. cssclass:: faq
|
||||
|
||||
I receive the error *current transaction is aborted, commands ignored until end of transaction block* and can't do anything else!
|
||||
There was a problem *in the previous* command to the database, which
|
||||
resulted in an error. The database will not recover automatically from
|
||||
this condition: you must run a `~connection.rollback()` before sending
|
||||
new commands to the session (if this seems too harsh, remember that
|
||||
PostgreSQL supports nested transactions using the |SAVEPOINT|_ command).
|
||||
|
||||
.. |SAVEPOINT| replace:: :sql:`SAVEPOINT`
|
||||
.. _SAVEPOINT: https://www.postgresql.org/docs/current/static/sql-savepoint.html
|
||||
|
||||
|
||||
.. _faq-transaction-aborted-multiprocess:
|
||||
.. cssclass:: faq
|
||||
|
||||
Why do I get the error *current transaction is aborted, commands ignored until end of transaction block* when I use `!multiprocessing` (or any other forking system) and not when use `!threading`?
|
||||
Psycopg's connections can't be shared across processes (but are thread
|
||||
safe). If you are forking the Python process make sure to create a new
|
||||
connection in each forked child. See :ref:`thread-safety` for further
|
||||
informations.
|
||||
|
||||
|
||||
.. _faq-types:
|
||||
|
||||
Problems with type conversions
|
||||
------------------------------
|
||||
|
||||
.. _faq-cant-adapt:
|
||||
.. cssclass:: faq
|
||||
|
||||
Why does `!cursor.execute()` raise the exception *can't adapt*?
|
||||
Psycopg converts Python objects in a SQL string representation by looking
|
||||
at the object class. The exception is raised when you are trying to pass
|
||||
as query parameter an object for which there is no adapter registered for
|
||||
its class. See :ref:`adapting-new-types` for informations.
|
||||
|
||||
|
||||
.. _faq-number-required:
|
||||
.. cssclass:: faq
|
||||
|
||||
I can't pass an integer or a float parameter to my query: it says *a number is required*, but *it is* a number!
|
||||
In your query string, you always have to use ``%s`` placeholders,
|
||||
even when passing a number. All Python objects are converted by Psycopg
|
||||
in their SQL representation, so they get passed to the query as strings.
|
||||
See :ref:`query-parameters`. ::
|
||||
|
||||
>>> cur.execute("INSERT INTO numbers VALUES (%d)", (42,)) # WRONG
|
||||
>>> cur.execute("INSERT INTO numbers VALUES (%s)", (42,)) # correct
|
||||
|
||||
|
||||
.. _faq-not-all-arguments-converted:
|
||||
.. cssclass:: faq
|
||||
|
||||
I try to execute a query but it fails with the error *not all arguments converted during string formatting* (or *object does not support indexing*). Why?
|
||||
Psycopg always require positional arguments to be passed as a sequence, even
|
||||
when the query takes a single parameter. And remember that to make a
|
||||
single item tuple in Python you need a comma! See :ref:`query-parameters`.
|
||||
::
|
||||
|
||||
>>> cur.execute("INSERT INTO foo VALUES (%s)", "bar") # WRONG
|
||||
>>> cur.execute("INSERT INTO foo VALUES (%s)", ("bar")) # WRONG
|
||||
>>> cur.execute("INSERT INTO foo VALUES (%s)", ("bar",)) # correct
|
||||
>>> cur.execute("INSERT INTO foo VALUES (%s)", ["bar"]) # correct
|
||||
|
||||
|
||||
.. _faq-unicode:
|
||||
.. cssclass:: faq
|
||||
|
||||
My database is Unicode, but I receive all the strings as UTF-8 `!str`. Can I receive `!unicode` objects instead?
|
||||
The following magic formula will do the trick::
|
||||
|
||||
psycopg2.extensions.register_type(psycopg2.extensions.UNICODE)
|
||||
psycopg2.extensions.register_type(psycopg2.extensions.UNICODEARRAY)
|
||||
|
||||
See :ref:`unicode-handling` for the gory details.
|
||||
|
||||
|
||||
.. _faq-bytes:
|
||||
.. cssclass:: faq
|
||||
|
||||
My database is in mixed encoding. My program was working on Python 2 but Python 3 fails decoding the strings. How do I avoid decoding?
|
||||
From psycopg 2.8 you can use the following adapters to always return bytes
|
||||
from strings::
|
||||
|
||||
psycopg2.extensions.register_type(psycopg2.extensions.BYTES)
|
||||
psycopg2.extensions.register_type(psycopg2.extensions.BYTESARRAY)
|
||||
|
||||
See :ref:`unicode-handling` for an example.
|
||||
|
||||
|
||||
.. _faq-float:
|
||||
.. cssclass:: faq
|
||||
|
||||
Psycopg converts :sql:`decimal`\/\ :sql:`numeric` database types into Python `!Decimal` objects. Can I have `!float` instead?
|
||||
You can register a customized adapter for PostgreSQL decimal type::
|
||||
|
||||
DEC2FLOAT = psycopg2.extensions.new_type(
|
||||
psycopg2.extensions.DECIMAL.values,
|
||||
'DEC2FLOAT',
|
||||
lambda value, curs: float(value) if value is not None else None)
|
||||
psycopg2.extensions.register_type(DEC2FLOAT)
|
||||
|
||||
See :ref:`type-casting-from-sql-to-python` to read the relevant
|
||||
documentation. If you find `!psycopg2.extensions.DECIMAL` not available, use
|
||||
`!psycopg2._psycopg.DECIMAL` instead.
|
||||
|
||||
|
||||
.. _faq-json-adapt:
|
||||
.. cssclass:: faq
|
||||
|
||||
Psycopg automatically converts PostgreSQL :sql:`json` data into Python objects. How can I receive strings instead?
|
||||
The easiest way to avoid JSON parsing is to register a no-op function with
|
||||
`~psycopg2.extras.register_default_json()`::
|
||||
|
||||
psycopg2.extras.register_default_json(loads=lambda x: x)
|
||||
|
||||
See :ref:`adapt-json` for further details.
|
||||
|
||||
|
||||
.. _faq-jsonb-adapt:
|
||||
.. cssclass:: faq
|
||||
|
||||
Psycopg converts :sql:`json` values into Python objects but :sql:`jsonb` values are returned as strings. Can :sql:`jsonb` be converted automatically?
|
||||
Automatic conversion of :sql:`jsonb` values is supported from Psycopg
|
||||
release 2.5.4. For previous versions you can register the :sql:`json`
|
||||
typecaster on the :sql:`jsonb` oids (which are known and not supposed to
|
||||
change in future PostgreSQL versions)::
|
||||
|
||||
psycopg2.extras.register_json(oid=3802, array_oid=3807, globally=True)
|
||||
|
||||
See :ref:`adapt-json` for further details.
|
||||
|
||||
|
||||
.. _faq-identifier:
|
||||
.. cssclass:: faq
|
||||
|
||||
How can I pass field/table names to a query?
|
||||
The arguments in the `~cursor.execute()` methods can only represent data
|
||||
to pass to the query: they cannot represent a table or field name::
|
||||
|
||||
# This doesn't work
|
||||
cur.execute("insert into %s values (%s)", ["my_table", 42])
|
||||
|
||||
If you want to build a query dynamically you can use the objects exposed
|
||||
by the `psycopg2.sql` module::
|
||||
|
||||
cur.execute(
|
||||
sql.SQL("insert into %s values (%%s)") % [sql.Identifier("my_table")],
|
||||
[42])
|
||||
|
||||
|
||||
.. _faq-bytea-9.0:
|
||||
.. cssclass:: faq
|
||||
|
||||
Transferring binary data from PostgreSQL 9.0 doesn't work.
|
||||
PostgreSQL 9.0 uses by default `the "hex" format`__ to transfer
|
||||
:sql:`bytea` data: the format can't be parsed by the libpq 8.4 and
|
||||
earlier. The problem is solved in Psycopg 2.4.1, that uses its own parser
|
||||
for the :sql:`bytea` format. For previous Psycopg releases, three options
|
||||
to solve the problem are:
|
||||
|
||||
- set the bytea_output__ parameter to ``escape`` in the server;
|
||||
- execute the database command ``SET bytea_output TO escape;`` in the
|
||||
session before reading binary data;
|
||||
- upgrade the libpq library on the client to at least 9.0.
|
||||
|
||||
.. __: https://www.postgresql.org/docs/current/static/datatype-binary.html
|
||||
.. __: https://www.postgresql.org/docs/current/static/runtime-config-client.html#GUC-BYTEA-OUTPUT
|
||||
|
||||
|
||||
.. _faq-array:
|
||||
.. cssclass:: faq
|
||||
|
||||
Arrays of *TYPE* are not casted to list.
|
||||
Arrays are only casted to list when their oid is known, and an array
|
||||
typecaster is registered for them. If there is no typecaster, the array is
|
||||
returned unparsed from PostgreSQL (e.g. ``{a,b,c}``). It is easy to create
|
||||
a generic arrays typecaster, returning a list of array: an example is
|
||||
provided in the `~psycopg2.extensions.new_array_type()` documentation.
|
||||
|
||||
|
||||
.. _faq-best-practices:
|
||||
|
||||
Best practices
|
||||
--------------
|
||||
|
||||
.. _faq-reuse-cursors:
|
||||
.. cssclass:: faq
|
||||
|
||||
When should I save and re-use a cursor as opposed to creating a new one as needed?
|
||||
Cursors are lightweight objects and creating lots of them should not pose
|
||||
any kind of problem. But note that cursors used to fetch result sets will
|
||||
cache the data and use memory in proportion to the result set size. Our
|
||||
suggestion is to almost always create a new cursor and dispose old ones as
|
||||
soon as the data is not required anymore (call `~cursor.close()` on
|
||||
them.) The only exception are tight loops where one usually use the same
|
||||
cursor for a whole bunch of :sql:`INSERT`\s or :sql:`UPDATE`\s.
|
||||
|
||||
|
||||
.. _faq-reuse-connections:
|
||||
.. cssclass:: faq
|
||||
|
||||
When should I save and re-use a connection as opposed to creating a new one as needed?
|
||||
Creating a connection can be slow (think of SSL over TCP) so the best
|
||||
practice is to create a single connection and keep it open as long as
|
||||
required. It is also good practice to rollback or commit frequently (even
|
||||
after a single :sql:`SELECT` statement) to make sure the backend is never
|
||||
left "idle in transaction". See also `psycopg2.pool` for lightweight
|
||||
connection pooling.
|
||||
|
||||
|
||||
.. _faq-named-cursors:
|
||||
.. cssclass:: faq
|
||||
|
||||
What are the advantages or disadvantages of using named cursors?
|
||||
The only disadvantages is that they use up resources on the server and
|
||||
that there is a little overhead because at least two queries (one to
|
||||
create the cursor and one to fetch the initial result set) are issued to
|
||||
the backend. The advantage is that data is fetched one chunk at a time:
|
||||
using small `~cursor.fetchmany()` values it is possible to use very
|
||||
little memory on the client and to skip or discard parts of the result set.
|
||||
|
||||
|
||||
.. _faq-interrupt-query:
|
||||
.. cssclass:: faq
|
||||
|
||||
How do I interrupt a long-running query in an interactive shell?
|
||||
Normally the interactive shell becomes unresponsive to :kbd:`Ctrl-C` when
|
||||
running a query. Using a connection in green mode allows Python to
|
||||
receive and handle the interrupt, although it may leave the connection
|
||||
broken, if the async callback doesn't handle the `!KeyboardInterrupt`
|
||||
correctly.
|
||||
|
||||
Starting from psycopg 2.6.2, the `~psycopg2.extras.wait_select` callback
|
||||
can handle a :kbd:`Ctrl-C` correctly. For previous versions, you can use
|
||||
`this implementation`__.
|
||||
|
||||
.. __: https://www.psycopg.org/articles/2014/07/20/cancelling-postgresql-statements-python/
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> psycopg2.extensions.set_wait_callback(psycopg2.extras.wait_select)
|
||||
>>> cnn = psycopg2.connect('')
|
||||
>>> cur = cnn.cursor()
|
||||
>>> cur.execute("select pg_sleep(10)")
|
||||
^C
|
||||
Traceback (most recent call last):
|
||||
File "<stdin>", line 1, in <module>
|
||||
QueryCanceledError: canceling statement due to user request
|
||||
|
||||
>>> cnn.rollback()
|
||||
>>> # You can use the connection and cursor again from here
|
||||
|
||||
|
||||
.. _faq-compile:
|
||||
|
||||
Problems compiling and installing psycopg2
|
||||
------------------------------------------
|
||||
|
||||
.. _faq-wheels:
|
||||
.. cssclass:: faq
|
||||
|
||||
Psycopg 2.8 fails to install, Psycopg 2.7 was working fine.
|
||||
With Psycopg 2.7 you were installing binary packages, but they have proven
|
||||
unreliable so now you have to install them explicitly using the
|
||||
``psycopg2-binary`` package. See :ref:`binary-packages` for all the
|
||||
details.
|
||||
|
||||
.. _faq-python-h:
|
||||
.. cssclass:: faq
|
||||
|
||||
I can't compile `!psycopg2`: the compiler says *error: Python.h: No such file or directory*. What am I missing?
|
||||
You need to install a Python development package: it is usually called
|
||||
``python-dev`` or ``python3-dev`` according to your Python version.
|
||||
|
||||
|
||||
.. _faq-libpq-fe-h:
|
||||
.. cssclass:: faq
|
||||
|
||||
I can't compile `!psycopg2`: the compiler says *error: libpq-fe.h: No such file or directory*. What am I missing?
|
||||
You need to install the development version of the libpq: the package is
|
||||
usually called ``libpq-dev``.
|
||||
|
||||
|
||||
.. _faq-lo_truncate:
|
||||
.. cssclass:: faq
|
||||
|
||||
`!psycopg2` raises `!ImportError` with message *_psycopg.so: undefined symbol: lo_truncate* when imported.
|
||||
This means that Psycopg was compiled with |lo_truncate|_ support (*i.e.*
|
||||
the libpq used at compile time was version >= 8.3) but at runtime an older
|
||||
libpq dynamic library is found.
|
||||
|
||||
Fast-forward several years, if the message reports *undefined symbol:
|
||||
lo_truncate64* it means that Psycopg was built with large objects 64 bits
|
||||
API support (*i.e.* the libpq used at compile time was at least 9.3) but
|
||||
at runtime an older libpq dynamic library is found.
|
||||
|
||||
You can use:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
$ ldd /path/to/packages/psycopg2/_psycopg.so | grep libpq
|
||||
|
||||
to find what is the libpq dynamic library used at runtime.
|
||||
|
||||
You can avoid the problem by using the same version of the
|
||||
:program:`pg_config` at install time and the libpq at runtime.
|
||||
|
||||
.. |lo_truncate| replace:: `!lo_truncate()`
|
||||
.. _lo_truncate: https://www.postgresql.org/docs/current/static/lo-interfaces.html#LO-TRUNCATE
|
||||
|
||||
|
||||
.. _faq-import-mod_wsgi:
|
||||
.. cssclass:: faq
|
||||
|
||||
Psycopg raises *ImportError: cannot import name tz* on import in mod_wsgi / ASP, but it works fine otherwise.
|
||||
If `!psycopg2` is installed in an egg_ (e.g. because installed by
|
||||
:program:`easy_install`), the user running the program may be unable to
|
||||
write in the `eggs cache`__. Set the env variable
|
||||
:envvar:`PYTHON_EGG_CACHE` to a writable directory. With modwsgi you can
|
||||
use the WSGIPythonEggs__ directive.
|
||||
|
||||
.. _egg: http://peak.telecommunity.com/DevCenter/PythonEggs
|
||||
.. __: https://stackoverflow.com/questions/2192323/what-is-the-python-egg-cache-python-egg-cache
|
||||
.. __: https://modwsgi.readthedocs.io/en/develop/configuration-directives/WSGIPythonEggs.html
|
||||
70
doc/src/index.rst
Normal file
70
doc/src/index.rst
Normal file
@ -0,0 +1,70 @@
|
||||
=================================================
|
||||
Psycopg -- PostgreSQL database adapter for Python
|
||||
=================================================
|
||||
|
||||
.. sectionauthor:: Daniele Varrazzo <daniele.varrazzo@gmail.com>
|
||||
|
||||
Psycopg_ is the most popular PostgreSQL_ database adapter for the Python_
|
||||
programming language. Its main features are the complete implementation of
|
||||
the Python |DBAPI|_ specification and the thread safety (several threads can
|
||||
share the same connection). It was designed for heavily multi-threaded
|
||||
applications that create and destroy lots of cursors and make a large number
|
||||
of concurrent :sql:`INSERT`\s or :sql:`UPDATE`\s.
|
||||
|
||||
Psycopg 2 is mostly implemented in C as a libpq_ wrapper, resulting in being
|
||||
both efficient and secure. It features client-side and :ref:`server-side
|
||||
<server-side-cursors>` cursors, :ref:`asynchronous communication
|
||||
<async-support>` and :ref:`notifications <async-notify>`, :ref:`COPY <copy>`
|
||||
support. Many Python types are supported out-of-the-box and :ref:`adapted to
|
||||
matching PostgreSQL data types <python-types-adaptation>`; adaptation can be
|
||||
extended and customized thanks to a flexible :ref:`objects adaptation system
|
||||
<adapting-new-types>`.
|
||||
|
||||
Psycopg 2 is both Unicode and Python 3 friendly.
|
||||
|
||||
|
||||
.. _Psycopg: https://psycopg.org/
|
||||
.. _PostgreSQL: https://www.postgresql.org/
|
||||
.. _Python: https://www.python.org/
|
||||
.. _libpq: https://www.postgresql.org/docs/current/static/libpq.html
|
||||
|
||||
|
||||
.. rubric:: Contents
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
install
|
||||
usage
|
||||
module
|
||||
connection
|
||||
cursor
|
||||
advanced
|
||||
extensions
|
||||
extras
|
||||
errors
|
||||
sql
|
||||
tz
|
||||
pool
|
||||
errorcodes
|
||||
faq
|
||||
news
|
||||
license
|
||||
|
||||
|
||||
.. ifconfig:: builder != 'text'
|
||||
|
||||
.. rubric:: Indices and tables
|
||||
|
||||
* :ref:`genindex`
|
||||
* :ref:`modindex`
|
||||
* :ref:`search`
|
||||
|
||||
|
||||
.. ifconfig:: todo_include_todos
|
||||
|
||||
.. note::
|
||||
|
||||
**To Do items in the documentation**
|
||||
|
||||
.. todolist::
|
||||
357
doc/src/install.rst
Normal file
357
doc/src/install.rst
Normal file
@ -0,0 +1,357 @@
|
||||
.. _installation:
|
||||
|
||||
Installation
|
||||
============
|
||||
|
||||
.. sectionauthor:: Daniele Varrazzo <daniele.varrazzo@gmail.com>
|
||||
|
||||
Psycopg is a PostgreSQL_ adapter for the Python_ programming language. It is a
|
||||
wrapper for the libpq_, the official PostgreSQL client library.
|
||||
|
||||
.. _PostgreSQL: https://www.postgresql.org/
|
||||
.. _Python: https://www.python.org/
|
||||
|
||||
|
||||
.. index::
|
||||
single: Install; from PyPI
|
||||
single: Install; wheel
|
||||
single: Wheel
|
||||
|
||||
.. _binary-packages:
|
||||
|
||||
Quick Install
|
||||
-------------
|
||||
|
||||
For most operating systems, the quickest way to install Psycopg is using the
|
||||
wheel_ package available on PyPI_:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ pip install psycopg2-binary
|
||||
|
||||
This will install a pre-compiled binary version of the module which does not
|
||||
require the build or runtime prerequisites described below. Make sure to use
|
||||
an up-date-date version of :program:`pip` (you can upgrade it using something
|
||||
like ``pip install -U pip``).
|
||||
|
||||
You may then import the ``psycopg2`` package, as usual:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import psycopg2
|
||||
|
||||
# Connect to your postgres DB
|
||||
conn = psycopg2.connect("dbname=test user=postgres")
|
||||
|
||||
# Open a cursor to perform database operations
|
||||
cur = conn.cursor()
|
||||
|
||||
# Execute a query
|
||||
cur.execute("SELECT * FROM my_data")
|
||||
|
||||
# Retrieve query results
|
||||
records = cur.fetchall()
|
||||
|
||||
.. _PyPI: https://pypi.org/project/psycopg2-binary/
|
||||
.. _wheel: https://pythonwheels.com/
|
||||
|
||||
|
||||
psycopg vs psycopg-binary
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The ``psycopg2-binary`` package is meant for beginners to start playing
|
||||
with Python and PostgreSQL without the need to meet the build
|
||||
requirements.
|
||||
|
||||
If you are the maintainer of a published package depending on `!psycopg2`
|
||||
you shouldn't use ``psycopg2-binary`` as a module dependency. **For
|
||||
production use you are advised to use the source distribution.**
|
||||
|
||||
The binary packages come with their own versions of a few C libraries,
|
||||
among which ``libpq`` and ``libssl``, which will be used regardless of other
|
||||
libraries available on the client: upgrading the system libraries will not
|
||||
upgrade the libraries used by `!psycopg2`. Please build `!psycopg2` from
|
||||
source if you want to maintain binary upgradeability.
|
||||
|
||||
.. warning::
|
||||
|
||||
The `!psycopg2` wheel package comes packaged, among the others, with its
|
||||
own ``libssl`` binary. This may create conflicts with other extension
|
||||
modules binding with ``libssl`` as well, for instance with the Python
|
||||
`ssl` module: in some cases, under concurrency, the interaction between
|
||||
the two libraries may result in a segfault. In case of doubts you are
|
||||
advised to use a package built from source.
|
||||
|
||||
|
||||
.. index::
|
||||
single: Install; disable wheel
|
||||
single: Wheel; disable
|
||||
|
||||
.. _disable-wheel:
|
||||
|
||||
Change in binary packages between Psycopg 2.7 and 2.8
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
In version 2.7.x, :command:`pip install psycopg2` would have tried to install
|
||||
automatically the binary package of Psycopg. Because of concurrency problems
|
||||
binary packages have displayed, ``psycopg2-binary`` has become a separate
|
||||
package, and from 2.8 it has become the only way to install the binary
|
||||
package.
|
||||
|
||||
If you are using Psycopg 2.7 and you want to disable the use of wheel binary
|
||||
packages, relying on the system libraries available on your client, you
|
||||
can use the :command:`pip` |--no-binary option|__, e.g.:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ pip install --no-binary :all: psycopg2
|
||||
|
||||
.. |--no-binary option| replace:: ``--no-binary`` option
|
||||
.. __: https://pip.pypa.io/en/stable/reference/pip_install/#install-no-binary
|
||||
|
||||
which can be specified in your :file:`requirements.txt` files too, e.g. use:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
psycopg2>=2.7,<2.8 --no-binary psycopg2
|
||||
|
||||
to use the last bugfix release of the `!psycopg2` 2.7 package, specifying to
|
||||
always compile it from source. Of course in this case you will have to meet
|
||||
the :ref:`build prerequisites <build-prerequisites>`.
|
||||
|
||||
|
||||
.. index::
|
||||
single: Prerequisites
|
||||
|
||||
Prerequisites
|
||||
-------------
|
||||
|
||||
The current `!psycopg2` implementation supports:
|
||||
|
||||
..
|
||||
NOTE: keep consistent with setup.py and the /features/ page.
|
||||
|
||||
- Python versions from 3.6 to 3.9
|
||||
- PostgreSQL server versions from 7.4 to 13
|
||||
- PostgreSQL client library version from 9.1
|
||||
|
||||
|
||||
|
||||
.. _build-prerequisites:
|
||||
|
||||
Build prerequisites
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The build prerequisites are to be met in order to install Psycopg from source
|
||||
code, from a source distribution package, GitHub_ or from PyPI.
|
||||
|
||||
.. _GitHub: https://github.com/psycopg/psycopg2
|
||||
|
||||
Psycopg is a C wrapper around the libpq_ PostgreSQL client library. To install
|
||||
it from sources you will need:
|
||||
|
||||
- A C compiler.
|
||||
|
||||
- The Python header files. They are usually installed in a package such as
|
||||
**python-dev** or **python3-dev**. A message such as *error: Python.h: No
|
||||
such file or directory* is an indication that the Python headers are
|
||||
missing.
|
||||
|
||||
- The libpq header files. They are usually installed in a package such as
|
||||
**libpq-dev**. If you get an *error: libpq-fe.h: No such file or directory*
|
||||
you are missing them.
|
||||
|
||||
- The :program:`pg_config` program: it is usually installed by the
|
||||
**libpq-dev** package but sometimes it is not in a :envvar:`PATH` directory.
|
||||
Having it in the :envvar:`PATH` greatly streamlines the installation, so try
|
||||
running ``pg_config --version``: if it returns an error or an unexpected
|
||||
version number then locate the directory containing the :program:`pg_config`
|
||||
shipped with the right libpq version (usually
|
||||
``/usr/lib/postgresql/X.Y/bin/``) and add it to the :envvar:`PATH`:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ export PATH=/usr/lib/postgresql/X.Y/bin/:$PATH
|
||||
|
||||
You only need :program:`pg_config` to compile `!psycopg2`, not for its
|
||||
regular usage.
|
||||
|
||||
Once everything is in place it's just a matter of running the standard:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ pip install psycopg2
|
||||
|
||||
or, from the directory containing the source code:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ python setup.py build
|
||||
$ python setup.py install
|
||||
|
||||
|
||||
Runtime requirements
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Unless you compile `!psycopg2` as a static library, or you install it from a
|
||||
self-contained wheel package, it will need the libpq_ library at runtime
|
||||
(usually distributed in a ``libpq.so`` or ``libpq.dll`` file). `!psycopg2`
|
||||
relies on the host OS to find the library if the library is installed in a
|
||||
standard location there is usually no problem; if the library is in a
|
||||
non-standard location you will have to tell somehow Psycopg how to find it,
|
||||
which is OS-dependent (for instance setting a suitable
|
||||
:envvar:`LD_LIBRARY_PATH` on Linux).
|
||||
|
||||
.. note::
|
||||
|
||||
The libpq header files used to compile `!psycopg2` should match the
|
||||
version of the library linked at runtime. If you get errors about missing
|
||||
or mismatching libraries when importing `!psycopg2` check (e.g. using
|
||||
:program:`ldd`) if the module ``psycopg2/_psycopg.so`` is linked to the
|
||||
right ``libpq.so``.
|
||||
|
||||
.. note::
|
||||
|
||||
Whatever version of libpq `!psycopg2` is compiled with, it will be
|
||||
possible to connect to PostgreSQL servers of any supported version: just
|
||||
install the most recent libpq version or the most practical, without
|
||||
trying to match it to the version of the PostgreSQL server you will have
|
||||
to connect to.
|
||||
|
||||
|
||||
.. index::
|
||||
single: setup.py
|
||||
single: setup.cfg
|
||||
|
||||
Non-standard builds
|
||||
-------------------
|
||||
|
||||
If you have less standard requirements such as:
|
||||
|
||||
- creating a :ref:`debug build <debug-build>`,
|
||||
- using :program:`pg_config` not in the :envvar:`PATH`,
|
||||
|
||||
then take a look at the ``setup.cfg`` file.
|
||||
|
||||
Some of the options available in ``setup.cfg`` are also available as command
|
||||
line arguments of the ``build_ext`` sub-command. For instance you can specify
|
||||
an alternate :program:`pg_config` location using:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ python setup.py build_ext --pg-config /path/to/pg_config build
|
||||
|
||||
Use ``python setup.py build_ext --help`` to get a list of the options
|
||||
supported.
|
||||
|
||||
|
||||
.. index::
|
||||
single: debug
|
||||
single: PSYCOPG_DEBUG
|
||||
|
||||
.. _debug-build:
|
||||
|
||||
Creating a debug build
|
||||
^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
In case of problems, Psycopg can be configured to emit detailed debug
|
||||
messages, which can be very useful for diagnostics and to report a bug. In
|
||||
order to create a debug package:
|
||||
|
||||
- `Download`__ and unpack the Psycopg *source package* (the ``.tar.gz``
|
||||
package).
|
||||
|
||||
- Edit the ``setup.cfg`` file adding the ``PSYCOPG_DEBUG`` flag to the
|
||||
``define`` option.
|
||||
|
||||
- :ref:`Compile and install <build-prerequisites>` the package.
|
||||
|
||||
- Set the :envvar:`PSYCOPG_DEBUG` environment variable:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ export PSYCOPG_DEBUG=1
|
||||
|
||||
- Run your program (making sure that the `!psycopg2` package imported is the
|
||||
one you just compiled and not e.g. the system one): you will have a copious
|
||||
stream of informations printed on stderr.
|
||||
|
||||
.. __: https://pypi.org/project/psycopg2/#files
|
||||
|
||||
|
||||
Non-standard Python Implementation
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The `psycopg2` package is the current mature implementation of the adapter: it
|
||||
is a C extension and as such it is only compatible with CPython_. If you want
|
||||
to use Psycopg on a different Python implementation (PyPy, Jython, IronPython)
|
||||
there is a couple of alternative:
|
||||
|
||||
- a `Ctypes port`__, but it is not as mature as the C implementation yet
|
||||
and it is not as feature-complete;
|
||||
|
||||
- a `CFFI port`__ which is currently more used and reported more efficient on
|
||||
PyPy, but please be careful of its version numbers because they are not
|
||||
aligned to the official psycopg2 ones and some features may differ.
|
||||
|
||||
.. _PostgreSQL: https://www.postgresql.org/
|
||||
.. _Python: https://www.python.org/
|
||||
.. _libpq: https://www.postgresql.org/docs/current/static/libpq.html
|
||||
.. _CPython: https://en.wikipedia.org/wiki/CPython
|
||||
.. _Ctypes: https://docs.python.org/library/ctypes.html
|
||||
.. __: https://github.com/mvantellingen/psycopg2-ctypes
|
||||
.. __: https://github.com/chtd/psycopg2cffi
|
||||
|
||||
|
||||
.. index::
|
||||
single: tests
|
||||
|
||||
.. _test-suite:
|
||||
|
||||
Running the test suite
|
||||
----------------------
|
||||
|
||||
Once `!psycopg2` is installed you can run the test suite to verify it is
|
||||
working correctly. From the source directory, you can run:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ python -c "import tests; tests.unittest.main(defaultTest='tests.test_suite')" --verbose
|
||||
|
||||
The tests run against a database called ``psycopg2_test`` on UNIX socket and
|
||||
the standard port. You can configure a different database to run the test by
|
||||
setting the environment variables:
|
||||
|
||||
- :envvar:`PSYCOPG2_TESTDB`
|
||||
- :envvar:`PSYCOPG2_TESTDB_HOST`
|
||||
- :envvar:`PSYCOPG2_TESTDB_PORT`
|
||||
- :envvar:`PSYCOPG2_TESTDB_USER`
|
||||
|
||||
The database should already exist before running the tests.
|
||||
|
||||
|
||||
.. _other-problems:
|
||||
|
||||
If you still have problems
|
||||
--------------------------
|
||||
|
||||
Try the following. *In order:*
|
||||
|
||||
- Read again the :ref:`build-prerequisites`.
|
||||
|
||||
- Read the :ref:`FAQ <faq-compile>`.
|
||||
|
||||
- Google for `!psycopg2` *your error message*. Especially useful the week
|
||||
after the release of a new OS X version.
|
||||
|
||||
- Write to the `Mailing List`_.
|
||||
|
||||
- If you think that you have discovered a bug, test failure or missing feature
|
||||
please raise a ticket in the `bug tracker`_.
|
||||
|
||||
- Complain on your blog or on Twitter that `!psycopg2` is the worst package
|
||||
ever and about the quality time you have wasted figuring out the correct
|
||||
:envvar:`ARCHFLAGS`. Especially useful from the Starbucks near you.
|
||||
|
||||
.. _mailing list: https://www.postgresql.org/list/psycopg/
|
||||
.. _bug tracker: https://github.com/psycopg/psycopg2/issues
|
||||
7
doc/src/license.rst
Normal file
7
doc/src/license.rst
Normal file
@ -0,0 +1,7 @@
|
||||
.. index::
|
||||
single: License
|
||||
|
||||
License
|
||||
=======
|
||||
|
||||
.. include:: ../../LICENSE
|
||||
388
doc/src/module.rst
Normal file
388
doc/src/module.rst
Normal file
@ -0,0 +1,388 @@
|
||||
The `psycopg2` module content
|
||||
==================================
|
||||
|
||||
.. sectionauthor:: Daniele Varrazzo <daniele.varrazzo@gmail.com>
|
||||
|
||||
.. module:: psycopg2
|
||||
|
||||
The module interface respects the standard defined in the |DBAPI|_.
|
||||
|
||||
.. index::
|
||||
single: Connection string
|
||||
double: Connection; Parameters
|
||||
single: Username; Connection
|
||||
single: Password; Connection
|
||||
single: Host; Connection
|
||||
single: Port; Connection
|
||||
single: DSN (Database Source Name)
|
||||
|
||||
.. function::
|
||||
connect(dsn=None, connection_factory=None, cursor_factory=None, async=False, \*\*kwargs)
|
||||
|
||||
Create a new database session and return a new `connection` object.
|
||||
|
||||
The connection parameters can be specified as a `libpq connection
|
||||
string`__ using the *dsn* parameter::
|
||||
|
||||
conn = psycopg2.connect("dbname=test user=postgres password=secret")
|
||||
|
||||
or using a set of keyword arguments::
|
||||
|
||||
conn = psycopg2.connect(dbname="test", user="postgres", password="secret")
|
||||
|
||||
or using a mix of both: if the same parameter name is specified in both
|
||||
sources, the *kwargs* value will have precedence over the *dsn* value.
|
||||
Note that either the *dsn* or at least one connection-related keyword
|
||||
argument is required.
|
||||
|
||||
The basic connection parameters are:
|
||||
|
||||
- `!dbname` -- the database name (`!database` is a deprecated alias)
|
||||
- `!user` -- user name used to authenticate
|
||||
- `!password` -- password used to authenticate
|
||||
- `!host` -- database host address (defaults to UNIX socket if not provided)
|
||||
- `!port` -- connection port number (defaults to 5432 if not provided)
|
||||
|
||||
Any other connection parameter supported by the client library/server can
|
||||
be passed either in the connection string or as a keyword. The PostgreSQL
|
||||
documentation contains the complete list of the `supported parameters`__.
|
||||
Also note that the same parameters can be passed to the client library
|
||||
using `environment variables`__.
|
||||
|
||||
.. __:
|
||||
.. _connstring: https://www.postgresql.org/docs/current/static/libpq-connect.html#LIBPQ-CONNSTRING
|
||||
.. __:
|
||||
.. _connparams: https://www.postgresql.org/docs/current/static/libpq-connect.html#LIBPQ-PARAMKEYWORDS
|
||||
.. __:
|
||||
.. _connenvvars: https://www.postgresql.org/docs/current/static/libpq-envars.html
|
||||
|
||||
Using the *connection_factory* parameter a different class or
|
||||
connections factory can be specified. It should be a callable object
|
||||
taking a *dsn* string argument. See :ref:`subclassing-connection` for
|
||||
details. If a *cursor_factory* is specified, the connection's
|
||||
`~connection.cursor_factory` is set to it. If you only need customized
|
||||
cursors you can use this parameter instead of subclassing a connection.
|
||||
|
||||
Using *async*\=\ `!True` an asynchronous connection will be created: see
|
||||
:ref:`async-support` to know about advantages and limitations. *async_* is
|
||||
a valid alias for the Python version where ``async`` is a keyword.
|
||||
|
||||
.. versionchanged:: 2.4.3
|
||||
any keyword argument is passed to the connection. Previously only the
|
||||
basic parameters (plus `!sslmode`) were supported as keywords.
|
||||
|
||||
.. versionchanged:: 2.5
|
||||
added the *cursor_factory* parameter.
|
||||
|
||||
.. versionchanged:: 2.7
|
||||
both *dsn* and keyword arguments can be specified.
|
||||
|
||||
.. versionchanged:: 2.7
|
||||
added *async_* alias.
|
||||
|
||||
.. seealso::
|
||||
|
||||
- `~psycopg2.extensions.parse_dsn`
|
||||
- libpq `connection string syntax`__
|
||||
- libpq supported `connection parameters`__
|
||||
- libpq supported `environment variables`__
|
||||
|
||||
.. __: connstring_
|
||||
.. __: connparams_
|
||||
.. __: connenvvars_
|
||||
|
||||
.. extension::
|
||||
|
||||
The non-connection-related keyword parameters are Psycopg extensions
|
||||
to the |DBAPI|_.
|
||||
|
||||
.. data:: apilevel
|
||||
|
||||
String constant stating the supported DB API level. For `psycopg2` is
|
||||
``2.0``.
|
||||
|
||||
.. data:: threadsafety
|
||||
|
||||
Integer constant stating the level of thread safety the interface
|
||||
supports. For `psycopg2` is ``2``, i.e. threads can share the module
|
||||
and the connection. See :ref:`thread-safety` for details.
|
||||
|
||||
.. data:: paramstyle
|
||||
|
||||
String constant stating the type of parameter marker formatting expected
|
||||
by the interface. For `psycopg2` is ``pyformat``. See also
|
||||
:ref:`query-parameters`.
|
||||
|
||||
.. data:: __libpq_version__
|
||||
|
||||
Integer constant reporting the version of the ``libpq`` library this
|
||||
``psycopg2`` module was compiled with (in the same format of
|
||||
`~psycopg2.extensions.ConnectionInfo.server_version`). If this value is
|
||||
greater or equal than ``90100`` then you may query the version of the
|
||||
actually loaded library using the `~psycopg2.extensions.libpq_version()`
|
||||
function.
|
||||
|
||||
|
||||
.. index::
|
||||
single: Exceptions; DB API
|
||||
|
||||
.. _dbapi-exceptions:
|
||||
|
||||
Exceptions
|
||||
----------
|
||||
|
||||
In compliance with the |DBAPI|_, the module makes informations about errors
|
||||
available through the following exceptions:
|
||||
|
||||
.. exception:: Warning
|
||||
|
||||
Exception raised for important warnings like data truncations while
|
||||
inserting, etc. It is a subclass of the Python `StandardError`
|
||||
(`Exception` on Python 3).
|
||||
|
||||
.. exception:: Error
|
||||
|
||||
Exception that is the base class of all other error exceptions. You can
|
||||
use this to catch all errors with one single `!except` statement. Warnings
|
||||
are not considered errors and thus not use this class as base. It
|
||||
is a subclass of the Python `StandardError` (`Exception` on Python 3).
|
||||
|
||||
.. attribute:: pgerror
|
||||
|
||||
String representing the error message returned by the backend,
|
||||
`!None` if not available.
|
||||
|
||||
.. attribute:: pgcode
|
||||
|
||||
String representing the error code returned by the backend, `!None`
|
||||
if not available. The `~psycopg2.errorcodes` module contains
|
||||
symbolic constants representing PostgreSQL error codes.
|
||||
|
||||
.. doctest::
|
||||
:options: +NORMALIZE_WHITESPACE
|
||||
|
||||
>>> try:
|
||||
... cur.execute("SELECT * FROM barf")
|
||||
... except psycopg2.Error as e:
|
||||
... pass
|
||||
|
||||
>>> e.pgcode
|
||||
'42P01'
|
||||
>>> print e.pgerror
|
||||
ERROR: relation "barf" does not exist
|
||||
LINE 1: SELECT * FROM barf
|
||||
^
|
||||
|
||||
.. attribute:: cursor
|
||||
|
||||
The cursor the exception was raised from; `None` if not applicable.
|
||||
|
||||
.. attribute:: diag
|
||||
|
||||
A `~psycopg2.extensions.Diagnostics` object containing further
|
||||
information about the error. ::
|
||||
|
||||
>>> try:
|
||||
... cur.execute("SELECT * FROM barf")
|
||||
... except psycopg2.Error, e:
|
||||
... pass
|
||||
|
||||
>>> e.diag.severity
|
||||
'ERROR'
|
||||
>>> e.diag.message_primary
|
||||
'relation "barf" does not exist'
|
||||
|
||||
.. versionadded:: 2.5
|
||||
|
||||
.. extension::
|
||||
|
||||
The `~Error.pgerror`, `~Error.pgcode`, `~Error.cursor`, and
|
||||
`~Error.diag` attributes are Psycopg extensions.
|
||||
|
||||
|
||||
.. exception:: InterfaceError
|
||||
|
||||
Exception raised for errors that are related to the database interface
|
||||
rather than the database itself. It is a subclass of `Error`.
|
||||
|
||||
.. exception:: DatabaseError
|
||||
|
||||
Exception raised for errors that are related to the database. It is a
|
||||
subclass of `Error`.
|
||||
|
||||
.. exception:: DataError
|
||||
|
||||
Exception raised for errors that are due to problems with the processed
|
||||
data like division by zero, numeric value out of range, etc. It is a
|
||||
subclass of `DatabaseError`.
|
||||
|
||||
.. exception:: OperationalError
|
||||
|
||||
Exception raised for errors that are related to the database's operation
|
||||
and not necessarily under the control of the programmer, e.g. an
|
||||
unexpected disconnect occurs, the data source name is not found, a
|
||||
transaction could not be processed, a memory allocation error occurred
|
||||
during processing, etc. It is a subclass of `DatabaseError`.
|
||||
|
||||
.. exception:: IntegrityError
|
||||
|
||||
Exception raised when the relational integrity of the database is
|
||||
affected, e.g. a foreign key check fails. It is a subclass of
|
||||
`DatabaseError`.
|
||||
|
||||
.. exception:: InternalError
|
||||
|
||||
Exception raised when the database encounters an internal error, e.g. the
|
||||
cursor is not valid anymore, the transaction is out of sync, etc. It is a
|
||||
subclass of `DatabaseError`.
|
||||
|
||||
.. exception:: ProgrammingError
|
||||
|
||||
Exception raised for programming errors, e.g. table not found or already
|
||||
exists, syntax error in the SQL statement, wrong number of parameters
|
||||
specified, etc. It is a subclass of `DatabaseError`.
|
||||
|
||||
.. exception:: NotSupportedError
|
||||
|
||||
Exception raised in case a method or database API was used which is not
|
||||
supported by the database, e.g. requesting a `!rollback()` on a
|
||||
connection that does not support transaction or has transactions turned
|
||||
off. It is a subclass of `DatabaseError`.
|
||||
|
||||
|
||||
.. extension::
|
||||
|
||||
Psycopg actually raises a different exception for each :sql:`SQLSTATE`
|
||||
error returned by the database: the classes are available in the
|
||||
`psycopg2.errors` module. Every exception class is a subclass of one of
|
||||
the exception classes defined here though, so they don't need to be
|
||||
trapped specifically: trapping `!Error` or `!DatabaseError` is usually
|
||||
what needed to write a generic error handler; trapping a specific error
|
||||
such as `!NotNullViolation` can be useful to write specific exception
|
||||
handlers.
|
||||
|
||||
|
||||
This is the exception inheritance layout:
|
||||
|
||||
.. parsed-literal::
|
||||
|
||||
`!StandardError`
|
||||
\|__ `Warning`
|
||||
\|__ `Error`
|
||||
\|__ `InterfaceError`
|
||||
\|__ `DatabaseError`
|
||||
\|__ `DataError`
|
||||
\|__ `OperationalError`
|
||||
\|__ `IntegrityError`
|
||||
\|__ `InternalError`
|
||||
\|__ `ProgrammingError`
|
||||
\|__ `NotSupportedError`
|
||||
|
||||
|
||||
|
||||
.. _type-objects-and-constructors:
|
||||
|
||||
Type Objects and Constructors
|
||||
-----------------------------
|
||||
|
||||
.. note::
|
||||
|
||||
This section is mostly copied verbatim from the |DBAPI|_
|
||||
specification. While these objects are exposed in compliance to the
|
||||
DB API, Psycopg offers very accurate tools to convert data between Python
|
||||
and PostgreSQL formats. See :ref:`adapting-new-types` and
|
||||
:ref:`type-casting-from-sql-to-python`
|
||||
|
||||
Many databases need to have the input in a particular format for
|
||||
binding to an operation's input parameters. For example, if an
|
||||
input is destined for a DATE column, then it must be bound to the
|
||||
database in a particular string format. Similar problems exist
|
||||
for "Row ID" columns or large binary items (e.g. blobs or RAW
|
||||
columns). This presents problems for Python since the parameters
|
||||
to the .execute*() method are untyped. When the database module
|
||||
sees a Python string object, it doesn't know if it should be bound
|
||||
as a simple CHAR column, as a raw BINARY item, or as a DATE.
|
||||
|
||||
To overcome this problem, a module must provide the constructors
|
||||
defined below to create objects that can hold special values.
|
||||
When passed to the cursor methods, the module can then detect the
|
||||
proper type of the input parameter and bind it accordingly.
|
||||
|
||||
A Cursor Object's description attribute returns information about
|
||||
each of the result columns of a query. The type_code must compare
|
||||
equal to one of Type Objects defined below. Type Objects may be
|
||||
equal to more than one type code (e.g. DATETIME could be equal to
|
||||
the type codes for date, time and timestamp columns; see the
|
||||
Implementation Hints below for details).
|
||||
|
||||
The module exports the following constructors and singletons:
|
||||
|
||||
.. function:: Date(year,month,day)
|
||||
|
||||
This function constructs an object holding a date value.
|
||||
|
||||
.. function:: Time(hour,minute,second)
|
||||
|
||||
This function constructs an object holding a time value.
|
||||
|
||||
.. function:: Timestamp(year,month,day,hour,minute,second)
|
||||
|
||||
This function constructs an object holding a time stamp value.
|
||||
|
||||
.. function:: DateFromTicks(ticks)
|
||||
|
||||
This function constructs an object holding a date value from the given
|
||||
ticks value (number of seconds since the epoch; see the documentation of
|
||||
the standard Python time module for details).
|
||||
|
||||
.. function:: TimeFromTicks(ticks)
|
||||
|
||||
This function constructs an object holding a time value from the given
|
||||
ticks value (number of seconds since the epoch; see the documentation of
|
||||
the standard Python time module for details).
|
||||
|
||||
.. function:: TimestampFromTicks(ticks)
|
||||
|
||||
This function constructs an object holding a time stamp value from the
|
||||
given ticks value (number of seconds since the epoch; see the
|
||||
documentation of the standard Python time module for details).
|
||||
|
||||
.. function:: Binary(string)
|
||||
|
||||
This function constructs an object capable of holding a binary (long)
|
||||
string value.
|
||||
|
||||
.. note::
|
||||
|
||||
All the adapters returned by the module level factories (`!Binary`,
|
||||
`!Date`, `!Time`, `!Timestamp` and the `!*FromTicks` variants) expose the
|
||||
wrapped object (a regular Python object such as `!datetime`) in an
|
||||
`!adapted` attribute.
|
||||
|
||||
.. data:: STRING
|
||||
|
||||
This type object is used to describe columns in a database that are
|
||||
string-based (e.g. CHAR).
|
||||
|
||||
.. data:: BINARY
|
||||
|
||||
This type object is used to describe (long) binary columns in a database
|
||||
(e.g. LONG, RAW, BLOBs).
|
||||
|
||||
.. data:: NUMBER
|
||||
|
||||
This type object is used to describe numeric columns in a database.
|
||||
|
||||
.. data:: DATETIME
|
||||
|
||||
This type object is used to describe date/time columns in a database.
|
||||
|
||||
.. data:: ROWID
|
||||
|
||||
This type object is used to describe the "Row ID" column in a database.
|
||||
|
||||
|
||||
.. testcode::
|
||||
:hide:
|
||||
|
||||
conn.rollback()
|
||||
8
doc/src/news.rst
Normal file
8
doc/src/news.rst
Normal file
@ -0,0 +1,8 @@
|
||||
.. index::
|
||||
single: Release notes
|
||||
single: News
|
||||
|
||||
Release notes
|
||||
=============
|
||||
|
||||
.. include:: ../../NEWS
|
||||
60
doc/src/pool.rst
Normal file
60
doc/src/pool.rst
Normal file
@ -0,0 +1,60 @@
|
||||
`psycopg2.pool` -- Connections pooling
|
||||
======================================
|
||||
|
||||
.. sectionauthor:: Daniele Varrazzo <daniele.varrazzo@gmail.com>
|
||||
|
||||
.. index::
|
||||
pair: Connection; Pooling
|
||||
|
||||
.. module:: psycopg2.pool
|
||||
|
||||
Creating new PostgreSQL connections can be an expensive operation. This
|
||||
module offers a few pure Python classes implementing simple connection pooling
|
||||
directly in the client application.
|
||||
|
||||
.. class:: AbstractConnectionPool(minconn, maxconn, \*args, \*\*kwargs)
|
||||
|
||||
Base class implementing generic key-based pooling code.
|
||||
|
||||
New *minconn* connections are created automatically. The pool will support
|
||||
a maximum of about *maxconn* connections. *\*args* and *\*\*kwargs* are
|
||||
passed to the `~psycopg2.connect()` function.
|
||||
|
||||
The following methods are expected to be implemented by subclasses:
|
||||
|
||||
.. method:: getconn(key=None)
|
||||
|
||||
Get a free connection from the pool.
|
||||
|
||||
The *key* parameter is optional: if used, the connection will be
|
||||
associated to the key and calling `!getconn()` with the same key again
|
||||
will return the same connection.
|
||||
|
||||
.. method:: putconn(conn, key=None, close=False)
|
||||
|
||||
Put away a connection.
|
||||
|
||||
If *close* is `!True`, discard the connection from the pool.
|
||||
*key* should be used consistently with `getconn()`.
|
||||
|
||||
.. method:: closeall
|
||||
|
||||
Close all the connections handled by the pool.
|
||||
|
||||
Note that all the connections are closed, including ones
|
||||
eventually in use by the application.
|
||||
|
||||
|
||||
The following classes are `AbstractConnectionPool` subclasses ready to
|
||||
be used.
|
||||
|
||||
.. autoclass:: SimpleConnectionPool
|
||||
|
||||
.. note:: This pool class is useful only for single-threaded applications.
|
||||
|
||||
|
||||
.. index:: Multithread; Connection pooling
|
||||
|
||||
.. autoclass:: ThreadedConnectionPool
|
||||
|
||||
.. note:: This pool class can be safely used in multi-threaded applications.
|
||||
147
doc/src/sql.rst
Normal file
147
doc/src/sql.rst
Normal file
@ -0,0 +1,147 @@
|
||||
`psycopg2.sql` -- SQL string composition
|
||||
========================================
|
||||
|
||||
.. sectionauthor:: Daniele Varrazzo <daniele.varrazzo@gmail.com>
|
||||
|
||||
.. module:: psycopg2.sql
|
||||
|
||||
.. versionadded:: 2.7
|
||||
|
||||
The module contains objects and functions useful to generate SQL dynamically,
|
||||
in a convenient and safe way. SQL identifiers (e.g. names of tables and
|
||||
fields) cannot be passed to the `~cursor.execute()` method like query
|
||||
arguments::
|
||||
|
||||
# This will not work
|
||||
table_name = 'my_table'
|
||||
cur.execute("insert into %s values (%s, %s)", [table_name, 10, 20])
|
||||
|
||||
The SQL query should be composed before the arguments are merged, for
|
||||
instance::
|
||||
|
||||
# This works, but it is not optimal
|
||||
table_name = 'my_table'
|
||||
cur.execute(
|
||||
"insert into %s values (%%s, %%s)" % table_name,
|
||||
[10, 20])
|
||||
|
||||
This sort of works, but it is an accident waiting to happen: the table name
|
||||
may be an invalid SQL literal and need quoting; even more serious is the
|
||||
security problem in case the table name comes from an untrusted source. The
|
||||
name should be escaped using `~psycopg2.extensions.quote_ident()`::
|
||||
|
||||
# This works, but it is not optimal
|
||||
table_name = 'my_table'
|
||||
cur.execute(
|
||||
"insert into %s values (%%s, %%s)" % ext.quote_ident(table_name),
|
||||
[10, 20])
|
||||
|
||||
This is now safe, but it somewhat ad-hoc. In case, for some reason, it is
|
||||
necessary to include a value in the query string (as opposite as in a value)
|
||||
the merging rule is still different (`~psycopg2.extensions.adapt()` should be
|
||||
used...). It is also still relatively dangerous: if `!quote_ident()` is
|
||||
forgotten somewhere, the program will usually work, but will eventually crash
|
||||
in the presence of a table or field name with containing characters to escape,
|
||||
or will present a potentially exploitable weakness.
|
||||
|
||||
The objects exposed by the `!psycopg2.sql` module allow generating SQL
|
||||
statements on the fly, separating clearly the variable parts of the statement
|
||||
from the query parameters::
|
||||
|
||||
from psycopg2 import sql
|
||||
|
||||
cur.execute(
|
||||
sql.SQL("insert into {} values (%s, %s)")
|
||||
.format(sql.Identifier('my_table')),
|
||||
[10, 20])
|
||||
|
||||
|
||||
Module usage
|
||||
------------
|
||||
|
||||
Usually you should express the template of your query as an `SQL` instance
|
||||
with `{}`\-style placeholders and use `~SQL.format()` to merge the variable
|
||||
parts into them, all of which must be `Composable` subclasses. You can still
|
||||
have `%s`\ -style placeholders in your query and pass values to
|
||||
`~cursor.execute()`: such value placeholders will be untouched by
|
||||
`!format()`::
|
||||
|
||||
query = sql.SQL("select {field} from {table} where {pkey} = %s").format(
|
||||
field=sql.Identifier('my_name'),
|
||||
table=sql.Identifier('some_table'),
|
||||
pkey=sql.Identifier('id'))
|
||||
|
||||
The resulting object is meant to be passed directly to cursor methods such as
|
||||
`~cursor.execute()`, `~cursor.executemany()`, `~cursor.copy_expert()`, but can
|
||||
also be used to compose a query as a Python string, using the
|
||||
`~Composable.as_string()` method::
|
||||
|
||||
cur.execute(query, (42,))
|
||||
|
||||
If part of your query is a variable sequence of arguments, such as a
|
||||
comma-separated list of field names, you can use the `SQL.join()` method to
|
||||
pass them to the query::
|
||||
|
||||
query = sql.SQL("select {fields} from {table}").format(
|
||||
fields=sql.SQL(',').join([
|
||||
sql.Identifier('field1'),
|
||||
sql.Identifier('field2'),
|
||||
sql.Identifier('field3'),
|
||||
]),
|
||||
table=sql.Identifier('some_table'))
|
||||
|
||||
|
||||
`!sql` objects
|
||||
--------------
|
||||
|
||||
The `!sql` objects are in the following inheritance hierarchy:
|
||||
|
||||
| `Composable`: the base class exposing the common interface
|
||||
| ``|__`` `SQL`: a literal snippet of an SQL query
|
||||
| ``|__`` `Identifier`: a PostgreSQL identifier or dot-separated sequence of identifiers
|
||||
| ``|__`` `Literal`: a value hardcoded into a query
|
||||
| ``|__`` `Placeholder`: a `%s`\ -style placeholder whose value will be added later e.g. by `~cursor.execute()`
|
||||
| ``|__`` `Composed`: a sequence of `!Composable` instances.
|
||||
|
||||
|
||||
.. autoclass:: Composable
|
||||
|
||||
.. automethod:: as_string
|
||||
|
||||
|
||||
.. autoclass:: SQL
|
||||
|
||||
.. autoattribute:: string
|
||||
|
||||
.. automethod:: format
|
||||
|
||||
.. automethod:: join
|
||||
|
||||
|
||||
.. autoclass:: Identifier
|
||||
|
||||
.. versionchanged:: 2.8
|
||||
added support for multiple strings.
|
||||
|
||||
.. autoattribute:: strings
|
||||
|
||||
.. versionadded:: 2.8
|
||||
previous verions only had a `!string` attribute. The attribute
|
||||
still exists but is deprecate and will only work if the
|
||||
`!Identifier` wraps a single string.
|
||||
|
||||
.. autoclass:: Literal
|
||||
|
||||
.. autoattribute:: wrapped
|
||||
|
||||
|
||||
.. autoclass:: Placeholder
|
||||
|
||||
.. autoattribute:: name
|
||||
|
||||
|
||||
.. autoclass:: Composed
|
||||
|
||||
.. autoattribute:: seq
|
||||
|
||||
.. automethod:: join
|
||||
50
doc/src/tools/lib/dbapi_extension.py
Executable file
50
doc/src/tools/lib/dbapi_extension.py
Executable file
@ -0,0 +1,50 @@
|
||||
"""
|
||||
extension
|
||||
~~~~~~~~~
|
||||
|
||||
A directive to create a box warning that a certain bit of Psycopg is an
|
||||
extension to the DBAPI 2.0.
|
||||
|
||||
:copyright: Copyright 2010 by Daniele Varrazzo.
|
||||
"""
|
||||
|
||||
from docutils import nodes
|
||||
|
||||
from sphinx.locale import _
|
||||
from docutils.parsers.rst import Directive
|
||||
|
||||
class extension_node(nodes.Admonition, nodes.Element): pass
|
||||
|
||||
|
||||
class Extension(Directive):
|
||||
"""
|
||||
An extension entry, displayed as an admonition.
|
||||
"""
|
||||
|
||||
has_content = True
|
||||
required_arguments = 0
|
||||
optional_arguments = 0
|
||||
final_argument_whitespace = False
|
||||
option_spec = {}
|
||||
|
||||
def run(self):
|
||||
node = extension_node('\n'.join(self.content))
|
||||
node += nodes.title(_('DB API extension'), _('DB API extension'))
|
||||
self.state.nested_parse(self.content, self.content_offset, node)
|
||||
node['classes'].append('dbapi-extension')
|
||||
return [node]
|
||||
|
||||
|
||||
def visit_extension_node(self, node):
|
||||
self.visit_admonition(node)
|
||||
|
||||
def depart_extension_node(self, node):
|
||||
self.depart_admonition(node)
|
||||
|
||||
def setup(app):
|
||||
app.add_node(extension_node,
|
||||
html=(visit_extension_node, depart_extension_node),
|
||||
latex=(visit_extension_node, depart_extension_node),
|
||||
text=(visit_extension_node, depart_extension_node))
|
||||
|
||||
app.add_directive('extension', Extension)
|
||||
19
doc/src/tools/lib/sql_role.py
Normal file
19
doc/src/tools/lib/sql_role.py
Normal file
@ -0,0 +1,19 @@
|
||||
"""
|
||||
sql role
|
||||
~~~~~~~~
|
||||
|
||||
An interpreted text role to style SQL syntax in Psycopg documentation.
|
||||
|
||||
:copyright: Copyright 2010 by Daniele Varrazzo.
|
||||
"""
|
||||
|
||||
from docutils import nodes, utils
|
||||
from docutils.parsers.rst import roles
|
||||
|
||||
def sql_role(name, rawtext, text, lineno, inliner, options={}, content=[]):
|
||||
text = utils.unescape(text)
|
||||
options['classes'] = ['sql']
|
||||
return [nodes.literal(rawtext, text, **options)], []
|
||||
|
||||
def setup(app):
|
||||
roles.register_local_role('sql', sql_role)
|
||||
57
doc/src/tools/lib/ticket_role.py
Normal file
57
doc/src/tools/lib/ticket_role.py
Normal file
@ -0,0 +1,57 @@
|
||||
"""
|
||||
ticket role
|
||||
~~~~~~~~~~~
|
||||
|
||||
An interpreted text role to link docs to tickets issues.
|
||||
|
||||
:copyright: Copyright 2013 by Daniele Varrazzo.
|
||||
"""
|
||||
|
||||
import re
|
||||
from docutils import nodes, utils
|
||||
from docutils.parsers.rst import roles
|
||||
|
||||
def ticket_role(name, rawtext, text, lineno, inliner, options={}, content=[]):
|
||||
cfg = inliner.document.settings.env.app.config
|
||||
if cfg.ticket_url is None:
|
||||
msg = inliner.reporter.warning(
|
||||
"ticket not configured: please configure ticket_url in conf.py")
|
||||
prb = inliner.problematic(rawtext, rawtext, msg)
|
||||
return [prb], [msg]
|
||||
|
||||
rv = [nodes.Text(name + ' ')]
|
||||
tokens = re.findall(r'(#?\d+)|([^\d#]+)', text)
|
||||
for ticket, noise in tokens:
|
||||
if ticket:
|
||||
num = int(ticket.replace('#', ''))
|
||||
|
||||
# Push numbers of the oldel tickets ahead.
|
||||
# We moved the tickets from a different tracker to GitHub and the
|
||||
# latter already had a few ticket numbers taken (as merge
|
||||
# requests).
|
||||
remap_until = cfg.ticket_remap_until
|
||||
remap_offset = cfg.ticket_remap_offset
|
||||
if remap_until and remap_offset:
|
||||
if num <= remap_until:
|
||||
num += remap_offset
|
||||
|
||||
url = cfg.ticket_url % num
|
||||
roles.set_classes(options)
|
||||
node = nodes.reference(ticket, utils.unescape(ticket),
|
||||
refuri=url, **options)
|
||||
|
||||
rv.append(node)
|
||||
|
||||
else:
|
||||
assert noise
|
||||
rv.append(nodes.Text(noise))
|
||||
|
||||
return rv, []
|
||||
|
||||
|
||||
def setup(app):
|
||||
app.add_config_value('ticket_url', None, 'env')
|
||||
app.add_config_value('ticket_remap_until', None, 'env')
|
||||
app.add_config_value('ticket_remap_offset', None, 'env')
|
||||
app.add_role('ticket', ticket_role)
|
||||
app.add_role('tickets', ticket_role)
|
||||
57
doc/src/tools/make_sqlstate_docs.py
Normal file
57
doc/src/tools/make_sqlstate_docs.py
Normal file
@ -0,0 +1,57 @@
|
||||
#!/usr/bin/env python
|
||||
"""Create the docs table of the sqlstate errors.
|
||||
"""
|
||||
|
||||
|
||||
import re
|
||||
import sys
|
||||
from collections import namedtuple
|
||||
|
||||
from psycopg2._psycopg import sqlstate_errors
|
||||
|
||||
|
||||
def main():
|
||||
sqlclasses = {}
|
||||
clsfile = sys.argv[1]
|
||||
with open(clsfile) as f:
|
||||
for l in f:
|
||||
m = re.match(r'/\* Class (..) - (.+) \*/', l)
|
||||
if m is not None:
|
||||
sqlclasses[m.group(1)] = m.group(2)
|
||||
|
||||
Line = namedtuple('Line', 'colstate colexc colbase sqlstate')
|
||||
|
||||
lines = [Line('SQLSTATE', 'Exception', 'Base exception', None)]
|
||||
for k in sorted(sqlstate_errors):
|
||||
exc = sqlstate_errors[k]
|
||||
lines.append(Line(
|
||||
f"``{k}``", f"`!{exc.__name__}`",
|
||||
f"`!{get_base_exception(exc).__name__}`", k))
|
||||
|
||||
widths = [max(len(l[c]) for l in lines) for c in range(3)]
|
||||
h = Line(*(['=' * w for w in widths] + [None]))
|
||||
lines.insert(0, h)
|
||||
lines.insert(2, h)
|
||||
lines.append(h)
|
||||
|
||||
h1 = '-' * (sum(widths) + len(widths) - 1)
|
||||
sqlclass = None
|
||||
for l in lines:
|
||||
cls = l.sqlstate[:2] if l.sqlstate else None
|
||||
if cls and cls != sqlclass:
|
||||
print(f"**Class {cls}**: {sqlclasses[cls]}")
|
||||
print(h1)
|
||||
sqlclass = cls
|
||||
|
||||
print("%-*s %-*s %-*s" % (
|
||||
widths[0], l.colstate, widths[1], l.colexc, widths[2], l.colbase))
|
||||
|
||||
|
||||
def get_base_exception(exc):
|
||||
for cls in exc.__mro__:
|
||||
if cls.__module__ == 'psycopg2':
|
||||
return cls
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
19
doc/src/tz.rst
Normal file
19
doc/src/tz.rst
Normal file
@ -0,0 +1,19 @@
|
||||
`psycopg2.tz` -- ``tzinfo`` implementations for Psycopg 2
|
||||
===============================================================
|
||||
|
||||
.. sectionauthor:: Daniele Varrazzo <daniele.varrazzo@gmail.com>
|
||||
|
||||
.. module:: psycopg2.tz
|
||||
|
||||
.. deprecated:: 2.9
|
||||
The module will be dropped in psycopg 2.10. Use `datetime.timezone`
|
||||
instead.
|
||||
|
||||
This module holds two different tzinfo implementations that can be used as the
|
||||
`tzinfo` argument to `~datetime.datetime` constructors, directly passed to
|
||||
Psycopg functions or used to set the `cursor.tzinfo_factory` attribute in
|
||||
cursors.
|
||||
|
||||
.. autoclass:: psycopg2.tz.FixedOffsetTimezone
|
||||
|
||||
.. autoclass:: psycopg2.tz.LocalTimezone
|
||||
1106
doc/src/usage.rst
Normal file
1106
doc/src/usage.rst
Normal file
File diff suppressed because it is too large
Load Diff
126
lib/__init__.py
Normal file
126
lib/__init__.py
Normal file
@ -0,0 +1,126 @@
|
||||
"""A Python driver for PostgreSQL
|
||||
|
||||
psycopg is a PostgreSQL_ database adapter for the Python_ programming
|
||||
language. This is version 2, a complete rewrite of the original code to
|
||||
provide new-style classes for connection and cursor objects and other sweet
|
||||
candies. Like the original, psycopg 2 was written with the aim of being very
|
||||
small and fast, and stable as a rock.
|
||||
|
||||
Homepage: https://psycopg.org/
|
||||
|
||||
.. _PostgreSQL: https://www.postgresql.org/
|
||||
.. _Python: https://www.python.org/
|
||||
|
||||
:Groups:
|
||||
* `Connections creation`: connect
|
||||
* `Value objects constructors`: Binary, Date, DateFromTicks, Time,
|
||||
TimeFromTicks, Timestamp, TimestampFromTicks
|
||||
"""
|
||||
# psycopg/__init__.py - initialization of the psycopg module
|
||||
#
|
||||
# Copyright (C) 2003-2019 Federico Di Gregorio <fog@debian.org>
|
||||
# Copyright (C) 2020-2021 The Psycopg Team
|
||||
#
|
||||
# psycopg2 is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Lesser General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# In addition, as a special exception, the copyright holders give
|
||||
# permission to link this program with the OpenSSL library (or with
|
||||
# modified versions of OpenSSL that use the same license as OpenSSL),
|
||||
# and distribute linked combinations including the two.
|
||||
#
|
||||
# You must obey the GNU Lesser General Public License in all respects for
|
||||
# all of the code used other than OpenSSL.
|
||||
#
|
||||
# psycopg2 is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
|
||||
# License for more details.
|
||||
|
||||
# Import modules needed by _psycopg to allow tools like py2exe to do
|
||||
# their work without bothering about the module dependencies.
|
||||
|
||||
# Note: the first internal import should be _psycopg, otherwise the real cause
|
||||
# of a failed loading of the C module may get hidden, see
|
||||
# https://archives.postgresql.org/psycopg/2011-02/msg00044.php
|
||||
|
||||
# Import the DBAPI-2.0 stuff into top-level module.
|
||||
|
||||
from psycopg2._psycopg import ( # noqa
|
||||
BINARY, NUMBER, STRING, DATETIME, ROWID,
|
||||
|
||||
Binary, Date, Time, Timestamp,
|
||||
DateFromTicks, TimeFromTicks, TimestampFromTicks,
|
||||
|
||||
Error, Warning, DataError, DatabaseError, ProgrammingError, IntegrityError,
|
||||
InterfaceError, InternalError, NotSupportedError, OperationalError,
|
||||
|
||||
_connect, apilevel, threadsafety, paramstyle,
|
||||
__version__, __libpq_version__,
|
||||
)
|
||||
|
||||
|
||||
# Register default adapters.
|
||||
|
||||
from psycopg2 import extensions as _ext
|
||||
_ext.register_adapter(tuple, _ext.SQL_IN)
|
||||
_ext.register_adapter(type(None), _ext.NoneAdapter)
|
||||
|
||||
# Register the Decimal adapter here instead of in the C layer.
|
||||
# This way a new class is registered for each sub-interpreter.
|
||||
# See ticket #52
|
||||
from decimal import Decimal # noqa
|
||||
from psycopg2._psycopg import Decimal as Adapter # noqa
|
||||
_ext.register_adapter(Decimal, Adapter)
|
||||
del Decimal, Adapter
|
||||
|
||||
|
||||
def connect(dsn=None, connection_factory=None, cursor_factory=None, **kwargs):
|
||||
"""
|
||||
Create a new database connection.
|
||||
|
||||
The connection parameters can be specified as a string:
|
||||
|
||||
conn = psycopg2.connect("dbname=test user=postgres password=secret")
|
||||
|
||||
or using a set of keyword arguments:
|
||||
|
||||
conn = psycopg2.connect(database="test", user="postgres", password="secret")
|
||||
|
||||
Or as a mix of both. The basic connection parameters are:
|
||||
|
||||
- *dbname*: the database name
|
||||
- *database*: the database name (only as keyword argument)
|
||||
- *user*: user name used to authenticate
|
||||
- *password*: password used to authenticate
|
||||
- *host*: database host address (defaults to UNIX socket if not provided)
|
||||
- *port*: connection port number (defaults to 5432 if not provided)
|
||||
|
||||
Using the *connection_factory* parameter a different class or connections
|
||||
factory can be specified. It should be a callable object taking a dsn
|
||||
argument.
|
||||
|
||||
Using the *cursor_factory* parameter, a new default cursor factory will be
|
||||
used by cursor().
|
||||
|
||||
Using *async*=True an asynchronous connection will be created. *async_* is
|
||||
a valid alias (for Python versions where ``async`` is a keyword).
|
||||
|
||||
Any other keyword parameter will be passed to the underlying client
|
||||
library: the list of supported parameters depends on the library version.
|
||||
|
||||
"""
|
||||
kwasync = {}
|
||||
if 'async' in kwargs:
|
||||
kwasync['async'] = kwargs.pop('async')
|
||||
if 'async_' in kwargs:
|
||||
kwasync['async_'] = kwargs.pop('async_')
|
||||
|
||||
dsn = _ext.make_dsn(dsn, **kwargs)
|
||||
conn = _connect(dsn, connection_factory=connection_factory, **kwasync)
|
||||
if cursor_factory is not None:
|
||||
conn.cursor_factory = cursor_factory
|
||||
|
||||
return conn
|
||||
90
lib/_ipaddress.py
Normal file
90
lib/_ipaddress.py
Normal file
@ -0,0 +1,90 @@
|
||||
"""Implementation of the ipaddres-based network types adaptation
|
||||
"""
|
||||
|
||||
# psycopg/_ipaddress.py - Ipaddres-based network types adaptation
|
||||
#
|
||||
# Copyright (C) 2016-2019 Daniele Varrazzo <daniele.varrazzo@gmail.com>
|
||||
# Copyright (C) 2020-2021 The Psycopg Team
|
||||
#
|
||||
# psycopg2 is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Lesser General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# In addition, as a special exception, the copyright holders give
|
||||
# permission to link this program with the OpenSSL library (or with
|
||||
# modified versions of OpenSSL that use the same license as OpenSSL),
|
||||
# and distribute linked combinations including the two.
|
||||
#
|
||||
# You must obey the GNU Lesser General Public License in all respects for
|
||||
# all of the code used other than OpenSSL.
|
||||
#
|
||||
# psycopg2 is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
|
||||
# License for more details.
|
||||
|
||||
from psycopg2.extensions import (
|
||||
new_type, new_array_type, register_type, register_adapter, QuotedString)
|
||||
|
||||
# The module is imported on register_ipaddress
|
||||
ipaddress = None
|
||||
|
||||
# The typecasters are created only once
|
||||
_casters = None
|
||||
|
||||
|
||||
def register_ipaddress(conn_or_curs=None):
|
||||
"""
|
||||
Register conversion support between `ipaddress` objects and `network types`__.
|
||||
|
||||
:param conn_or_curs: the scope where to register the type casters.
|
||||
If `!None` register them globally.
|
||||
|
||||
After the function is called, PostgreSQL :sql:`inet` values will be
|
||||
converted into `~ipaddress.IPv4Interface` or `~ipaddress.IPv6Interface`
|
||||
objects, :sql:`cidr` values into into `~ipaddress.IPv4Network` or
|
||||
`~ipaddress.IPv6Network`.
|
||||
|
||||
.. __: https://www.postgresql.org/docs/current/static/datatype-net-types.html
|
||||
"""
|
||||
global ipaddress
|
||||
import ipaddress
|
||||
|
||||
global _casters
|
||||
if _casters is None:
|
||||
_casters = _make_casters()
|
||||
|
||||
for c in _casters:
|
||||
register_type(c, conn_or_curs)
|
||||
|
||||
for t in [ipaddress.IPv4Interface, ipaddress.IPv6Interface,
|
||||
ipaddress.IPv4Network, ipaddress.IPv6Network]:
|
||||
register_adapter(t, adapt_ipaddress)
|
||||
|
||||
|
||||
def _make_casters():
|
||||
inet = new_type((869,), 'INET', cast_interface)
|
||||
ainet = new_array_type((1041,), 'INET[]', inet)
|
||||
|
||||
cidr = new_type((650,), 'CIDR', cast_network)
|
||||
acidr = new_array_type((651,), 'CIDR[]', cidr)
|
||||
|
||||
return [inet, ainet, cidr, acidr]
|
||||
|
||||
|
||||
def cast_interface(s, cur=None):
|
||||
if s is None:
|
||||
return None
|
||||
# Py2 version force the use of unicode. meh.
|
||||
return ipaddress.ip_interface(str(s))
|
||||
|
||||
|
||||
def cast_network(s, cur=None):
|
||||
if s is None:
|
||||
return None
|
||||
return ipaddress.ip_network(str(s))
|
||||
|
||||
|
||||
def adapt_ipaddress(obj):
|
||||
return QuotedString(str(obj))
|
||||
199
lib/_json.py
Normal file
199
lib/_json.py
Normal file
@ -0,0 +1,199 @@
|
||||
"""Implementation of the JSON adaptation objects
|
||||
|
||||
This module exists to avoid a circular import problem: pyscopg2.extras depends
|
||||
on psycopg2.extension, so I can't create the default JSON typecasters in
|
||||
extensions importing register_json from extras.
|
||||
"""
|
||||
|
||||
# psycopg/_json.py - Implementation of the JSON adaptation objects
|
||||
#
|
||||
# Copyright (C) 2012-2019 Daniele Varrazzo <daniele.varrazzo@gmail.com>
|
||||
# Copyright (C) 2020-2021 The Psycopg Team
|
||||
#
|
||||
# psycopg2 is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Lesser General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# In addition, as a special exception, the copyright holders give
|
||||
# permission to link this program with the OpenSSL library (or with
|
||||
# modified versions of OpenSSL that use the same license as OpenSSL),
|
||||
# and distribute linked combinations including the two.
|
||||
#
|
||||
# You must obey the GNU Lesser General Public License in all respects for
|
||||
# all of the code used other than OpenSSL.
|
||||
#
|
||||
# psycopg2 is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
|
||||
# License for more details.
|
||||
|
||||
import json
|
||||
|
||||
from psycopg2._psycopg import ISQLQuote, QuotedString
|
||||
from psycopg2._psycopg import new_type, new_array_type, register_type
|
||||
|
||||
|
||||
# oids from PostgreSQL 9.2
|
||||
JSON_OID = 114
|
||||
JSONARRAY_OID = 199
|
||||
|
||||
# oids from PostgreSQL 9.4
|
||||
JSONB_OID = 3802
|
||||
JSONBARRAY_OID = 3807
|
||||
|
||||
|
||||
class Json:
|
||||
"""
|
||||
An `~psycopg2.extensions.ISQLQuote` wrapper to adapt a Python object to
|
||||
:sql:`json` data type.
|
||||
|
||||
`!Json` can be used to wrap any object supported by the provided *dumps*
|
||||
function. If none is provided, the standard :py:func:`json.dumps()` is
|
||||
used.
|
||||
|
||||
"""
|
||||
def __init__(self, adapted, dumps=None):
|
||||
self.adapted = adapted
|
||||
self._conn = None
|
||||
self._dumps = dumps or json.dumps
|
||||
|
||||
def __conform__(self, proto):
|
||||
if proto is ISQLQuote:
|
||||
return self
|
||||
|
||||
def dumps(self, obj):
|
||||
"""Serialize *obj* in JSON format.
|
||||
|
||||
The default is to call `!json.dumps()` or the *dumps* function
|
||||
provided in the constructor. You can override this method to create a
|
||||
customized JSON wrapper.
|
||||
"""
|
||||
return self._dumps(obj)
|
||||
|
||||
def prepare(self, conn):
|
||||
self._conn = conn
|
||||
|
||||
def getquoted(self):
|
||||
s = self.dumps(self.adapted)
|
||||
qs = QuotedString(s)
|
||||
if self._conn is not None:
|
||||
qs.prepare(self._conn)
|
||||
return qs.getquoted()
|
||||
|
||||
def __str__(self):
|
||||
# getquoted is binary
|
||||
return self.getquoted().decode('ascii', 'replace')
|
||||
|
||||
|
||||
def register_json(conn_or_curs=None, globally=False, loads=None,
|
||||
oid=None, array_oid=None, name='json'):
|
||||
"""Create and register typecasters converting :sql:`json` type to Python objects.
|
||||
|
||||
:param conn_or_curs: a connection or cursor used to find the :sql:`json`
|
||||
and :sql:`json[]` oids; the typecasters are registered in a scope
|
||||
limited to this object, unless *globally* is set to `!True`. It can be
|
||||
`!None` if the oids are provided
|
||||
:param globally: if `!False` register the typecasters only on
|
||||
*conn_or_curs*, otherwise register them globally
|
||||
:param loads: the function used to parse the data into a Python object. If
|
||||
`!None` use `!json.loads()`, where `!json` is the module chosen
|
||||
according to the Python version (see above)
|
||||
:param oid: the OID of the :sql:`json` type if known; If not, it will be
|
||||
queried on *conn_or_curs*
|
||||
:param array_oid: the OID of the :sql:`json[]` array type if known;
|
||||
if not, it will be queried on *conn_or_curs*
|
||||
:param name: the name of the data type to look for in *conn_or_curs*
|
||||
|
||||
The connection or cursor passed to the function will be used to query the
|
||||
database and look for the OID of the :sql:`json` type (or an alternative
|
||||
type if *name* if provided). No query is performed if *oid* and *array_oid*
|
||||
are provided. Raise `~psycopg2.ProgrammingError` if the type is not found.
|
||||
|
||||
"""
|
||||
if oid is None:
|
||||
oid, array_oid = _get_json_oids(conn_or_curs, name)
|
||||
|
||||
JSON, JSONARRAY = _create_json_typecasters(
|
||||
oid, array_oid, loads=loads, name=name.upper())
|
||||
|
||||
register_type(JSON, not globally and conn_or_curs or None)
|
||||
|
||||
if JSONARRAY is not None:
|
||||
register_type(JSONARRAY, not globally and conn_or_curs or None)
|
||||
|
||||
return JSON, JSONARRAY
|
||||
|
||||
|
||||
def register_default_json(conn_or_curs=None, globally=False, loads=None):
|
||||
"""
|
||||
Create and register :sql:`json` typecasters for PostgreSQL 9.2 and following.
|
||||
|
||||
Since PostgreSQL 9.2 :sql:`json` is a builtin type, hence its oid is known
|
||||
and fixed. This function allows specifying a customized *loads* function
|
||||
for the default :sql:`json` type without querying the database.
|
||||
All the parameters have the same meaning of `register_json()`.
|
||||
"""
|
||||
return register_json(conn_or_curs=conn_or_curs, globally=globally,
|
||||
loads=loads, oid=JSON_OID, array_oid=JSONARRAY_OID)
|
||||
|
||||
|
||||
def register_default_jsonb(conn_or_curs=None, globally=False, loads=None):
|
||||
"""
|
||||
Create and register :sql:`jsonb` typecasters for PostgreSQL 9.4 and following.
|
||||
|
||||
As in `register_default_json()`, the function allows to register a
|
||||
customized *loads* function for the :sql:`jsonb` type at its known oid for
|
||||
PostgreSQL 9.4 and following versions. All the parameters have the same
|
||||
meaning of `register_json()`.
|
||||
"""
|
||||
return register_json(conn_or_curs=conn_or_curs, globally=globally,
|
||||
loads=loads, oid=JSONB_OID, array_oid=JSONBARRAY_OID, name='jsonb')
|
||||
|
||||
|
||||
def _create_json_typecasters(oid, array_oid, loads=None, name='JSON'):
|
||||
"""Create typecasters for json data type."""
|
||||
if loads is None:
|
||||
loads = json.loads
|
||||
|
||||
def typecast_json(s, cur):
|
||||
if s is None:
|
||||
return None
|
||||
return loads(s)
|
||||
|
||||
JSON = new_type((oid, ), name, typecast_json)
|
||||
if array_oid is not None:
|
||||
JSONARRAY = new_array_type((array_oid, ), f"{name}ARRAY", JSON)
|
||||
else:
|
||||
JSONARRAY = None
|
||||
|
||||
return JSON, JSONARRAY
|
||||
|
||||
|
||||
def _get_json_oids(conn_or_curs, name='json'):
|
||||
# lazy imports
|
||||
from psycopg2.extensions import STATUS_IN_TRANSACTION
|
||||
from psycopg2.extras import _solve_conn_curs
|
||||
|
||||
conn, curs = _solve_conn_curs(conn_or_curs)
|
||||
|
||||
# Store the transaction status of the connection to revert it after use
|
||||
conn_status = conn.status
|
||||
|
||||
# column typarray not available before PG 8.3
|
||||
typarray = conn.info.server_version >= 80300 and "typarray" or "NULL"
|
||||
|
||||
# get the oid for the hstore
|
||||
curs.execute(
|
||||
"SELECT t.oid, %s FROM pg_type t WHERE t.typname = %%s;"
|
||||
% typarray, (name,))
|
||||
r = curs.fetchone()
|
||||
|
||||
# revert the status of the connection as before the command
|
||||
if conn_status != STATUS_IN_TRANSACTION and not conn.autocommit:
|
||||
conn.rollback()
|
||||
|
||||
if not r:
|
||||
raise conn.ProgrammingError(f"{name} data type not found")
|
||||
|
||||
return r
|
||||
537
lib/_range.py
Normal file
537
lib/_range.py
Normal file
@ -0,0 +1,537 @@
|
||||
"""Implementation of the Range type and adaptation
|
||||
|
||||
"""
|
||||
|
||||
# psycopg/_range.py - Implementation of the Range type and adaptation
|
||||
#
|
||||
# Copyright (C) 2012-2019 Daniele Varrazzo <daniele.varrazzo@gmail.com>
|
||||
# Copyright (C) 2020-2021 The Psycopg Team
|
||||
#
|
||||
# psycopg2 is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Lesser General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# In addition, as a special exception, the copyright holders give
|
||||
# permission to link this program with the OpenSSL library (or with
|
||||
# modified versions of OpenSSL that use the same license as OpenSSL),
|
||||
# and distribute linked combinations including the two.
|
||||
#
|
||||
# You must obey the GNU Lesser General Public License in all respects for
|
||||
# all of the code used other than OpenSSL.
|
||||
#
|
||||
# psycopg2 is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
|
||||
# License for more details.
|
||||
|
||||
import re
|
||||
|
||||
from psycopg2._psycopg import ProgrammingError, InterfaceError
|
||||
from psycopg2.extensions import ISQLQuote, adapt, register_adapter
|
||||
from psycopg2.extensions import new_type, new_array_type, register_type
|
||||
|
||||
|
||||
class Range:
|
||||
"""Python representation for a PostgreSQL |range|_ type.
|
||||
|
||||
:param lower: lower bound for the range. `!None` means unbound
|
||||
:param upper: upper bound for the range. `!None` means unbound
|
||||
:param bounds: one of the literal strings ``()``, ``[)``, ``(]``, ``[]``,
|
||||
representing whether the lower or upper bounds are included
|
||||
:param empty: if `!True`, the range is empty
|
||||
|
||||
"""
|
||||
__slots__ = ('_lower', '_upper', '_bounds')
|
||||
|
||||
def __init__(self, lower=None, upper=None, bounds='[)', empty=False):
|
||||
if not empty:
|
||||
if bounds not in ('[)', '(]', '()', '[]'):
|
||||
raise ValueError(f"bound flags not valid: {bounds!r}")
|
||||
|
||||
self._lower = lower
|
||||
self._upper = upper
|
||||
self._bounds = bounds
|
||||
else:
|
||||
self._lower = self._upper = self._bounds = None
|
||||
|
||||
def __repr__(self):
|
||||
if self._bounds is None:
|
||||
return f"{self.__class__.__name__}(empty=True)"
|
||||
else:
|
||||
return "{}({!r}, {!r}, {!r})".format(self.__class__.__name__,
|
||||
self._lower, self._upper, self._bounds)
|
||||
|
||||
def __str__(self):
|
||||
if self._bounds is None:
|
||||
return 'empty'
|
||||
|
||||
items = [
|
||||
self._bounds[0],
|
||||
str(self._lower),
|
||||
', ',
|
||||
str(self._upper),
|
||||
self._bounds[1]
|
||||
]
|
||||
return ''.join(items)
|
||||
|
||||
@property
|
||||
def lower(self):
|
||||
"""The lower bound of the range. `!None` if empty or unbound."""
|
||||
return self._lower
|
||||
|
||||
@property
|
||||
def upper(self):
|
||||
"""The upper bound of the range. `!None` if empty or unbound."""
|
||||
return self._upper
|
||||
|
||||
@property
|
||||
def isempty(self):
|
||||
"""`!True` if the range is empty."""
|
||||
return self._bounds is None
|
||||
|
||||
@property
|
||||
def lower_inf(self):
|
||||
"""`!True` if the range doesn't have a lower bound."""
|
||||
if self._bounds is None:
|
||||
return False
|
||||
return self._lower is None
|
||||
|
||||
@property
|
||||
def upper_inf(self):
|
||||
"""`!True` if the range doesn't have an upper bound."""
|
||||
if self._bounds is None:
|
||||
return False
|
||||
return self._upper is None
|
||||
|
||||
@property
|
||||
def lower_inc(self):
|
||||
"""`!True` if the lower bound is included in the range."""
|
||||
if self._bounds is None or self._lower is None:
|
||||
return False
|
||||
return self._bounds[0] == '['
|
||||
|
||||
@property
|
||||
def upper_inc(self):
|
||||
"""`!True` if the upper bound is included in the range."""
|
||||
if self._bounds is None or self._upper is None:
|
||||
return False
|
||||
return self._bounds[1] == ']'
|
||||
|
||||
def __contains__(self, x):
|
||||
if self._bounds is None:
|
||||
return False
|
||||
|
||||
if self._lower is not None:
|
||||
if self._bounds[0] == '[':
|
||||
if x < self._lower:
|
||||
return False
|
||||
else:
|
||||
if x <= self._lower:
|
||||
return False
|
||||
|
||||
if self._upper is not None:
|
||||
if self._bounds[1] == ']':
|
||||
if x > self._upper:
|
||||
return False
|
||||
else:
|
||||
if x >= self._upper:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def __bool__(self):
|
||||
return self._bounds is not None
|
||||
|
||||
def __nonzero__(self):
|
||||
# Python 2 compatibility
|
||||
return type(self).__bool__(self)
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, Range):
|
||||
return False
|
||||
return (self._lower == other._lower
|
||||
and self._upper == other._upper
|
||||
and self._bounds == other._bounds)
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self.__eq__(other)
|
||||
|
||||
def __hash__(self):
|
||||
return hash((self._lower, self._upper, self._bounds))
|
||||
|
||||
# as the postgres docs describe for the server-side stuff,
|
||||
# ordering is rather arbitrary, but will remain stable
|
||||
# and consistent.
|
||||
|
||||
def __lt__(self, other):
|
||||
if not isinstance(other, Range):
|
||||
return NotImplemented
|
||||
for attr in ('_lower', '_upper', '_bounds'):
|
||||
self_value = getattr(self, attr)
|
||||
other_value = getattr(other, attr)
|
||||
if self_value == other_value:
|
||||
pass
|
||||
elif self_value is None:
|
||||
return True
|
||||
elif other_value is None:
|
||||
return False
|
||||
else:
|
||||
return self_value < other_value
|
||||
return False
|
||||
|
||||
def __le__(self, other):
|
||||
if self == other:
|
||||
return True
|
||||
else:
|
||||
return self.__lt__(other)
|
||||
|
||||
def __gt__(self, other):
|
||||
if isinstance(other, Range):
|
||||
return other.__lt__(self)
|
||||
else:
|
||||
return NotImplemented
|
||||
|
||||
def __ge__(self, other):
|
||||
if self == other:
|
||||
return True
|
||||
else:
|
||||
return self.__gt__(other)
|
||||
|
||||
def __getstate__(self):
|
||||
return {slot: getattr(self, slot)
|
||||
for slot in self.__slots__ if hasattr(self, slot)}
|
||||
|
||||
def __setstate__(self, state):
|
||||
for slot, value in state.items():
|
||||
setattr(self, slot, value)
|
||||
|
||||
|
||||
def register_range(pgrange, pyrange, conn_or_curs, globally=False):
|
||||
"""Create and register an adapter and the typecasters to convert between
|
||||
a PostgreSQL |range|_ type and a PostgreSQL `Range` subclass.
|
||||
|
||||
:param pgrange: the name of the PostgreSQL |range| type. Can be
|
||||
schema-qualified
|
||||
:param pyrange: a `Range` strict subclass, or just a name to give to a new
|
||||
class
|
||||
:param conn_or_curs: a connection or cursor used to find the oid of the
|
||||
range and its subtype; the typecaster is registered in a scope limited
|
||||
to this object, unless *globally* is set to `!True`
|
||||
:param globally: if `!False` (default) register the typecaster only on
|
||||
*conn_or_curs*, otherwise register it globally
|
||||
:return: `RangeCaster` instance responsible for the conversion
|
||||
|
||||
If a string is passed to *pyrange*, a new `Range` subclass is created
|
||||
with such name and will be available as the `~RangeCaster.range` attribute
|
||||
of the returned `RangeCaster` object.
|
||||
|
||||
The function queries the database on *conn_or_curs* to inspect the
|
||||
*pgrange* type and raises `~psycopg2.ProgrammingError` if the type is not
|
||||
found. If querying the database is not advisable, use directly the
|
||||
`RangeCaster` class and register the adapter and typecasters using the
|
||||
provided functions.
|
||||
|
||||
"""
|
||||
caster = RangeCaster._from_db(pgrange, pyrange, conn_or_curs)
|
||||
caster._register(not globally and conn_or_curs or None)
|
||||
return caster
|
||||
|
||||
|
||||
class RangeAdapter:
|
||||
"""`ISQLQuote` adapter for `Range` subclasses.
|
||||
|
||||
This is an abstract class: concrete classes must set a `name` class
|
||||
attribute or override `getquoted()`.
|
||||
"""
|
||||
name = None
|
||||
|
||||
def __init__(self, adapted):
|
||||
self.adapted = adapted
|
||||
|
||||
def __conform__(self, proto):
|
||||
if self._proto is ISQLQuote:
|
||||
return self
|
||||
|
||||
def prepare(self, conn):
|
||||
self._conn = conn
|
||||
|
||||
def getquoted(self):
|
||||
if self.name is None:
|
||||
raise NotImplementedError(
|
||||
'RangeAdapter must be subclassed overriding its name '
|
||||
'or the getquoted() method')
|
||||
|
||||
r = self.adapted
|
||||
if r.isempty:
|
||||
return b"'empty'::" + self.name.encode('utf8')
|
||||
|
||||
if r.lower is not None:
|
||||
a = adapt(r.lower)
|
||||
if hasattr(a, 'prepare'):
|
||||
a.prepare(self._conn)
|
||||
lower = a.getquoted()
|
||||
else:
|
||||
lower = b'NULL'
|
||||
|
||||
if r.upper is not None:
|
||||
a = adapt(r.upper)
|
||||
if hasattr(a, 'prepare'):
|
||||
a.prepare(self._conn)
|
||||
upper = a.getquoted()
|
||||
else:
|
||||
upper = b'NULL'
|
||||
|
||||
return self.name.encode('utf8') + b'(' + lower + b', ' + upper \
|
||||
+ b", '" + r._bounds.encode('utf8') + b"')"
|
||||
|
||||
|
||||
class RangeCaster:
|
||||
"""Helper class to convert between `Range` and PostgreSQL range types.
|
||||
|
||||
Objects of this class are usually created by `register_range()`. Manual
|
||||
creation could be useful if querying the database is not advisable: in
|
||||
this case the oids must be provided.
|
||||
"""
|
||||
def __init__(self, pgrange, pyrange, oid, subtype_oid, array_oid=None):
|
||||
self.subtype_oid = subtype_oid
|
||||
self._create_ranges(pgrange, pyrange)
|
||||
|
||||
name = self.adapter.name or self.adapter.__class__.__name__
|
||||
|
||||
self.typecaster = new_type((oid,), name, self.parse)
|
||||
|
||||
if array_oid is not None:
|
||||
self.array_typecaster = new_array_type(
|
||||
(array_oid,), name + "ARRAY", self.typecaster)
|
||||
else:
|
||||
self.array_typecaster = None
|
||||
|
||||
def _create_ranges(self, pgrange, pyrange):
|
||||
"""Create Range and RangeAdapter classes if needed."""
|
||||
# if got a string create a new RangeAdapter concrete type (with a name)
|
||||
# else take it as an adapter. Passing an adapter should be considered
|
||||
# an implementation detail and is not documented. It is currently used
|
||||
# for the numeric ranges.
|
||||
self.adapter = None
|
||||
if isinstance(pgrange, str):
|
||||
self.adapter = type(pgrange, (RangeAdapter,), {})
|
||||
self.adapter.name = pgrange
|
||||
else:
|
||||
try:
|
||||
if issubclass(pgrange, RangeAdapter) \
|
||||
and pgrange is not RangeAdapter:
|
||||
self.adapter = pgrange
|
||||
except TypeError:
|
||||
pass
|
||||
|
||||
if self.adapter is None:
|
||||
raise TypeError(
|
||||
'pgrange must be a string or a RangeAdapter strict subclass')
|
||||
|
||||
self.range = None
|
||||
try:
|
||||
if isinstance(pyrange, str):
|
||||
self.range = type(pyrange, (Range,), {})
|
||||
if issubclass(pyrange, Range) and pyrange is not Range:
|
||||
self.range = pyrange
|
||||
except TypeError:
|
||||
pass
|
||||
|
||||
if self.range is None:
|
||||
raise TypeError(
|
||||
'pyrange must be a type or a Range strict subclass')
|
||||
|
||||
@classmethod
|
||||
def _from_db(self, name, pyrange, conn_or_curs):
|
||||
"""Return a `RangeCaster` instance for the type *pgrange*.
|
||||
|
||||
Raise `ProgrammingError` if the type is not found.
|
||||
"""
|
||||
from psycopg2.extensions import STATUS_IN_TRANSACTION
|
||||
from psycopg2.extras import _solve_conn_curs
|
||||
conn, curs = _solve_conn_curs(conn_or_curs)
|
||||
|
||||
if conn.info.server_version < 90200:
|
||||
raise ProgrammingError("range types not available in version %s"
|
||||
% conn.info.server_version)
|
||||
|
||||
# Store the transaction status of the connection to revert it after use
|
||||
conn_status = conn.status
|
||||
|
||||
# Use the correct schema
|
||||
if '.' in name:
|
||||
schema, tname = name.split('.', 1)
|
||||
else:
|
||||
tname = name
|
||||
schema = 'public'
|
||||
|
||||
# get the type oid and attributes
|
||||
try:
|
||||
curs.execute("""\
|
||||
select rngtypid, rngsubtype,
|
||||
(select typarray from pg_type where oid = rngtypid)
|
||||
from pg_range r
|
||||
join pg_type t on t.oid = rngtypid
|
||||
join pg_namespace ns on ns.oid = typnamespace
|
||||
where typname = %s and ns.nspname = %s;
|
||||
""", (tname, schema))
|
||||
|
||||
except ProgrammingError:
|
||||
if not conn.autocommit:
|
||||
conn.rollback()
|
||||
raise
|
||||
else:
|
||||
rec = curs.fetchone()
|
||||
|
||||
# revert the status of the connection as before the command
|
||||
if (conn_status != STATUS_IN_TRANSACTION
|
||||
and not conn.autocommit):
|
||||
conn.rollback()
|
||||
|
||||
if not rec:
|
||||
raise ProgrammingError(
|
||||
f"PostgreSQL type '{name}' not found")
|
||||
|
||||
type, subtype, array = rec
|
||||
|
||||
return RangeCaster(name, pyrange,
|
||||
oid=type, subtype_oid=subtype, array_oid=array)
|
||||
|
||||
_re_range = re.compile(r"""
|
||||
( \(|\[ ) # lower bound flag
|
||||
(?: # lower bound:
|
||||
" ( (?: [^"] | "")* ) " # - a quoted string
|
||||
| ( [^",]+ ) # - or an unquoted string
|
||||
)? # - or empty (not catched)
|
||||
,
|
||||
(?: # upper bound:
|
||||
" ( (?: [^"] | "")* ) " # - a quoted string
|
||||
| ( [^"\)\]]+ ) # - or an unquoted string
|
||||
)? # - or empty (not catched)
|
||||
( \)|\] ) # upper bound flag
|
||||
""", re.VERBOSE)
|
||||
|
||||
_re_undouble = re.compile(r'(["\\])\1')
|
||||
|
||||
def parse(self, s, cur=None):
|
||||
if s is None:
|
||||
return None
|
||||
|
||||
if s == 'empty':
|
||||
return self.range(empty=True)
|
||||
|
||||
m = self._re_range.match(s)
|
||||
if m is None:
|
||||
raise InterfaceError(f"failed to parse range: '{s}'")
|
||||
|
||||
lower = m.group(3)
|
||||
if lower is None:
|
||||
lower = m.group(2)
|
||||
if lower is not None:
|
||||
lower = self._re_undouble.sub(r"\1", lower)
|
||||
|
||||
upper = m.group(5)
|
||||
if upper is None:
|
||||
upper = m.group(4)
|
||||
if upper is not None:
|
||||
upper = self._re_undouble.sub(r"\1", upper)
|
||||
|
||||
if cur is not None:
|
||||
lower = cur.cast(self.subtype_oid, lower)
|
||||
upper = cur.cast(self.subtype_oid, upper)
|
||||
|
||||
bounds = m.group(1) + m.group(6)
|
||||
|
||||
return self.range(lower, upper, bounds)
|
||||
|
||||
def _register(self, scope=None):
|
||||
register_type(self.typecaster, scope)
|
||||
if self.array_typecaster is not None:
|
||||
register_type(self.array_typecaster, scope)
|
||||
|
||||
register_adapter(self.range, self.adapter)
|
||||
|
||||
|
||||
class NumericRange(Range):
|
||||
"""A `Range` suitable to pass Python numeric types to a PostgreSQL range.
|
||||
|
||||
PostgreSQL types :sql:`int4range`, :sql:`int8range`, :sql:`numrange` are
|
||||
casted into `!NumericRange` instances.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class DateRange(Range):
|
||||
"""Represents :sql:`daterange` values."""
|
||||
pass
|
||||
|
||||
|
||||
class DateTimeRange(Range):
|
||||
"""Represents :sql:`tsrange` values."""
|
||||
pass
|
||||
|
||||
|
||||
class DateTimeTZRange(Range):
|
||||
"""Represents :sql:`tstzrange` values."""
|
||||
pass
|
||||
|
||||
|
||||
# Special adaptation for NumericRange. Allows to pass number range regardless
|
||||
# of whether they are ints, floats and what size of ints are, which are
|
||||
# pointless in Python world. On the way back, no numeric range is casted to
|
||||
# NumericRange, but only to their subclasses
|
||||
|
||||
class NumberRangeAdapter(RangeAdapter):
|
||||
"""Adapt a range if the subtype doesn't need quotes."""
|
||||
def getquoted(self):
|
||||
r = self.adapted
|
||||
if r.isempty:
|
||||
return b"'empty'"
|
||||
|
||||
if not r.lower_inf:
|
||||
# not exactly: we are relying that none of these object is really
|
||||
# quoted (they are numbers). Also, I'm lazy and not preparing the
|
||||
# adapter because I assume encoding doesn't matter for these
|
||||
# objects.
|
||||
lower = adapt(r.lower).getquoted().decode('ascii')
|
||||
else:
|
||||
lower = ''
|
||||
|
||||
if not r.upper_inf:
|
||||
upper = adapt(r.upper).getquoted().decode('ascii')
|
||||
else:
|
||||
upper = ''
|
||||
|
||||
return (f"'{r._bounds[0]}{lower},{upper}{r._bounds[1]}'").encode('ascii')
|
||||
|
||||
|
||||
# TODO: probably won't work with infs, nans and other tricky cases.
|
||||
register_adapter(NumericRange, NumberRangeAdapter)
|
||||
|
||||
# Register globally typecasters and adapters for builtin range types.
|
||||
|
||||
# note: the adapter is registered more than once, but this is harmless.
|
||||
int4range_caster = RangeCaster(NumberRangeAdapter, NumericRange,
|
||||
oid=3904, subtype_oid=23, array_oid=3905)
|
||||
int4range_caster._register()
|
||||
|
||||
int8range_caster = RangeCaster(NumberRangeAdapter, NumericRange,
|
||||
oid=3926, subtype_oid=20, array_oid=3927)
|
||||
int8range_caster._register()
|
||||
|
||||
numrange_caster = RangeCaster(NumberRangeAdapter, NumericRange,
|
||||
oid=3906, subtype_oid=1700, array_oid=3907)
|
||||
numrange_caster._register()
|
||||
|
||||
daterange_caster = RangeCaster('daterange', DateRange,
|
||||
oid=3912, subtype_oid=1082, array_oid=3913)
|
||||
daterange_caster._register()
|
||||
|
||||
tsrange_caster = RangeCaster('tsrange', DateTimeRange,
|
||||
oid=3908, subtype_oid=1114, array_oid=3909)
|
||||
tsrange_caster._register()
|
||||
|
||||
tstzrange_caster = RangeCaster('tstzrange', DateTimeTZRange,
|
||||
oid=3910, subtype_oid=1184, array_oid=3911)
|
||||
tstzrange_caster._register()
|
||||
447
lib/errorcodes.py
Normal file
447
lib/errorcodes.py
Normal file
@ -0,0 +1,447 @@
|
||||
"""Error codes for PostgreSQL
|
||||
|
||||
This module contains symbolic names for all PostgreSQL error codes.
|
||||
"""
|
||||
# psycopg2/errorcodes.py - PostgreSQL error codes
|
||||
#
|
||||
# Copyright (C) 2006-2019 Johan Dahlin <jdahlin@async.com.br>
|
||||
# Copyright (C) 2020-2021 The Psycopg Team
|
||||
#
|
||||
# psycopg2 is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Lesser General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# In addition, as a special exception, the copyright holders give
|
||||
# permission to link this program with the OpenSSL library (or with
|
||||
# modified versions of OpenSSL that use the same license as OpenSSL),
|
||||
# and distribute linked combinations including the two.
|
||||
#
|
||||
# You must obey the GNU Lesser General Public License in all respects for
|
||||
# all of the code used other than OpenSSL.
|
||||
#
|
||||
# psycopg2 is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
|
||||
# License for more details.
|
||||
#
|
||||
# Based on:
|
||||
#
|
||||
# https://www.postgresql.org/docs/current/static/errcodes-appendix.html
|
||||
#
|
||||
|
||||
|
||||
def lookup(code, _cache={}):
|
||||
"""Lookup an error code or class code and return its symbolic name.
|
||||
|
||||
Raise `KeyError` if the code is not found.
|
||||
"""
|
||||
if _cache:
|
||||
return _cache[code]
|
||||
|
||||
# Generate the lookup map at first usage.
|
||||
tmp = {}
|
||||
for k, v in globals().items():
|
||||
if isinstance(v, str) and len(v) in (2, 5):
|
||||
# Strip trailing underscore used to disambiguate duplicate values
|
||||
tmp[v] = k.rstrip("_")
|
||||
|
||||
assert tmp
|
||||
|
||||
# Atomic update, to avoid race condition on import (bug #382)
|
||||
_cache.update(tmp)
|
||||
|
||||
return _cache[code]
|
||||
|
||||
|
||||
# autogenerated data: do not edit below this point.
|
||||
|
||||
# Error classes
|
||||
CLASS_SUCCESSFUL_COMPLETION = '00'
|
||||
CLASS_WARNING = '01'
|
||||
CLASS_NO_DATA = '02'
|
||||
CLASS_SQL_STATEMENT_NOT_YET_COMPLETE = '03'
|
||||
CLASS_CONNECTION_EXCEPTION = '08'
|
||||
CLASS_TRIGGERED_ACTION_EXCEPTION = '09'
|
||||
CLASS_FEATURE_NOT_SUPPORTED = '0A'
|
||||
CLASS_INVALID_TRANSACTION_INITIATION = '0B'
|
||||
CLASS_LOCATOR_EXCEPTION = '0F'
|
||||
CLASS_INVALID_GRANTOR = '0L'
|
||||
CLASS_INVALID_ROLE_SPECIFICATION = '0P'
|
||||
CLASS_DIAGNOSTICS_EXCEPTION = '0Z'
|
||||
CLASS_CASE_NOT_FOUND = '20'
|
||||
CLASS_CARDINALITY_VIOLATION = '21'
|
||||
CLASS_DATA_EXCEPTION = '22'
|
||||
CLASS_INTEGRITY_CONSTRAINT_VIOLATION = '23'
|
||||
CLASS_INVALID_CURSOR_STATE = '24'
|
||||
CLASS_INVALID_TRANSACTION_STATE = '25'
|
||||
CLASS_INVALID_SQL_STATEMENT_NAME = '26'
|
||||
CLASS_TRIGGERED_DATA_CHANGE_VIOLATION = '27'
|
||||
CLASS_INVALID_AUTHORIZATION_SPECIFICATION = '28'
|
||||
CLASS_DEPENDENT_PRIVILEGE_DESCRIPTORS_STILL_EXIST = '2B'
|
||||
CLASS_INVALID_TRANSACTION_TERMINATION = '2D'
|
||||
CLASS_SQL_ROUTINE_EXCEPTION = '2F'
|
||||
CLASS_INVALID_CURSOR_NAME = '34'
|
||||
CLASS_EXTERNAL_ROUTINE_EXCEPTION = '38'
|
||||
CLASS_EXTERNAL_ROUTINE_INVOCATION_EXCEPTION = '39'
|
||||
CLASS_SAVEPOINT_EXCEPTION = '3B'
|
||||
CLASS_INVALID_CATALOG_NAME = '3D'
|
||||
CLASS_INVALID_SCHEMA_NAME = '3F'
|
||||
CLASS_TRANSACTION_ROLLBACK = '40'
|
||||
CLASS_SYNTAX_ERROR_OR_ACCESS_RULE_VIOLATION = '42'
|
||||
CLASS_WITH_CHECK_OPTION_VIOLATION = '44'
|
||||
CLASS_INSUFFICIENT_RESOURCES = '53'
|
||||
CLASS_PROGRAM_LIMIT_EXCEEDED = '54'
|
||||
CLASS_OBJECT_NOT_IN_PREREQUISITE_STATE = '55'
|
||||
CLASS_OPERATOR_INTERVENTION = '57'
|
||||
CLASS_SYSTEM_ERROR = '58'
|
||||
CLASS_SNAPSHOT_FAILURE = '72'
|
||||
CLASS_CONFIGURATION_FILE_ERROR = 'F0'
|
||||
CLASS_FOREIGN_DATA_WRAPPER_ERROR = 'HV'
|
||||
CLASS_PL_PGSQL_ERROR = 'P0'
|
||||
CLASS_INTERNAL_ERROR = 'XX'
|
||||
|
||||
# Class 00 - Successful Completion
|
||||
SUCCESSFUL_COMPLETION = '00000'
|
||||
|
||||
# Class 01 - Warning
|
||||
WARNING = '01000'
|
||||
NULL_VALUE_ELIMINATED_IN_SET_FUNCTION = '01003'
|
||||
STRING_DATA_RIGHT_TRUNCATION_ = '01004'
|
||||
PRIVILEGE_NOT_REVOKED = '01006'
|
||||
PRIVILEGE_NOT_GRANTED = '01007'
|
||||
IMPLICIT_ZERO_BIT_PADDING = '01008'
|
||||
DYNAMIC_RESULT_SETS_RETURNED = '0100C'
|
||||
DEPRECATED_FEATURE = '01P01'
|
||||
|
||||
# Class 02 - No Data (this is also a warning class per the SQL standard)
|
||||
NO_DATA = '02000'
|
||||
NO_ADDITIONAL_DYNAMIC_RESULT_SETS_RETURNED = '02001'
|
||||
|
||||
# Class 03 - SQL Statement Not Yet Complete
|
||||
SQL_STATEMENT_NOT_YET_COMPLETE = '03000'
|
||||
|
||||
# Class 08 - Connection Exception
|
||||
CONNECTION_EXCEPTION = '08000'
|
||||
SQLCLIENT_UNABLE_TO_ESTABLISH_SQLCONNECTION = '08001'
|
||||
CONNECTION_DOES_NOT_EXIST = '08003'
|
||||
SQLSERVER_REJECTED_ESTABLISHMENT_OF_SQLCONNECTION = '08004'
|
||||
CONNECTION_FAILURE = '08006'
|
||||
TRANSACTION_RESOLUTION_UNKNOWN = '08007'
|
||||
PROTOCOL_VIOLATION = '08P01'
|
||||
|
||||
# Class 09 - Triggered Action Exception
|
||||
TRIGGERED_ACTION_EXCEPTION = '09000'
|
||||
|
||||
# Class 0A - Feature Not Supported
|
||||
FEATURE_NOT_SUPPORTED = '0A000'
|
||||
|
||||
# Class 0B - Invalid Transaction Initiation
|
||||
INVALID_TRANSACTION_INITIATION = '0B000'
|
||||
|
||||
# Class 0F - Locator Exception
|
||||
LOCATOR_EXCEPTION = '0F000'
|
||||
INVALID_LOCATOR_SPECIFICATION = '0F001'
|
||||
|
||||
# Class 0L - Invalid Grantor
|
||||
INVALID_GRANTOR = '0L000'
|
||||
INVALID_GRANT_OPERATION = '0LP01'
|
||||
|
||||
# Class 0P - Invalid Role Specification
|
||||
INVALID_ROLE_SPECIFICATION = '0P000'
|
||||
|
||||
# Class 0Z - Diagnostics Exception
|
||||
DIAGNOSTICS_EXCEPTION = '0Z000'
|
||||
STACKED_DIAGNOSTICS_ACCESSED_WITHOUT_ACTIVE_HANDLER = '0Z002'
|
||||
|
||||
# Class 20 - Case Not Found
|
||||
CASE_NOT_FOUND = '20000'
|
||||
|
||||
# Class 21 - Cardinality Violation
|
||||
CARDINALITY_VIOLATION = '21000'
|
||||
|
||||
# Class 22 - Data Exception
|
||||
DATA_EXCEPTION = '22000'
|
||||
STRING_DATA_RIGHT_TRUNCATION = '22001'
|
||||
NULL_VALUE_NO_INDICATOR_PARAMETER = '22002'
|
||||
NUMERIC_VALUE_OUT_OF_RANGE = '22003'
|
||||
NULL_VALUE_NOT_ALLOWED_ = '22004'
|
||||
ERROR_IN_ASSIGNMENT = '22005'
|
||||
INVALID_DATETIME_FORMAT = '22007'
|
||||
DATETIME_FIELD_OVERFLOW = '22008'
|
||||
INVALID_TIME_ZONE_DISPLACEMENT_VALUE = '22009'
|
||||
ESCAPE_CHARACTER_CONFLICT = '2200B'
|
||||
INVALID_USE_OF_ESCAPE_CHARACTER = '2200C'
|
||||
INVALID_ESCAPE_OCTET = '2200D'
|
||||
ZERO_LENGTH_CHARACTER_STRING = '2200F'
|
||||
MOST_SPECIFIC_TYPE_MISMATCH = '2200G'
|
||||
SEQUENCE_GENERATOR_LIMIT_EXCEEDED = '2200H'
|
||||
NOT_AN_XML_DOCUMENT = '2200L'
|
||||
INVALID_XML_DOCUMENT = '2200M'
|
||||
INVALID_XML_CONTENT = '2200N'
|
||||
INVALID_XML_COMMENT = '2200S'
|
||||
INVALID_XML_PROCESSING_INSTRUCTION = '2200T'
|
||||
INVALID_INDICATOR_PARAMETER_VALUE = '22010'
|
||||
SUBSTRING_ERROR = '22011'
|
||||
DIVISION_BY_ZERO = '22012'
|
||||
INVALID_PRECEDING_OR_FOLLOWING_SIZE = '22013'
|
||||
INVALID_ARGUMENT_FOR_NTILE_FUNCTION = '22014'
|
||||
INTERVAL_FIELD_OVERFLOW = '22015'
|
||||
INVALID_ARGUMENT_FOR_NTH_VALUE_FUNCTION = '22016'
|
||||
INVALID_CHARACTER_VALUE_FOR_CAST = '22018'
|
||||
INVALID_ESCAPE_CHARACTER = '22019'
|
||||
INVALID_REGULAR_EXPRESSION = '2201B'
|
||||
INVALID_ARGUMENT_FOR_LOGARITHM = '2201E'
|
||||
INVALID_ARGUMENT_FOR_POWER_FUNCTION = '2201F'
|
||||
INVALID_ARGUMENT_FOR_WIDTH_BUCKET_FUNCTION = '2201G'
|
||||
INVALID_ROW_COUNT_IN_LIMIT_CLAUSE = '2201W'
|
||||
INVALID_ROW_COUNT_IN_RESULT_OFFSET_CLAUSE = '2201X'
|
||||
INVALID_LIMIT_VALUE = '22020'
|
||||
CHARACTER_NOT_IN_REPERTOIRE = '22021'
|
||||
INDICATOR_OVERFLOW = '22022'
|
||||
INVALID_PARAMETER_VALUE = '22023'
|
||||
UNTERMINATED_C_STRING = '22024'
|
||||
INVALID_ESCAPE_SEQUENCE = '22025'
|
||||
STRING_DATA_LENGTH_MISMATCH = '22026'
|
||||
TRIM_ERROR = '22027'
|
||||
ARRAY_SUBSCRIPT_ERROR = '2202E'
|
||||
INVALID_TABLESAMPLE_REPEAT = '2202G'
|
||||
INVALID_TABLESAMPLE_ARGUMENT = '2202H'
|
||||
DUPLICATE_JSON_OBJECT_KEY_VALUE = '22030'
|
||||
INVALID_ARGUMENT_FOR_SQL_JSON_DATETIME_FUNCTION = '22031'
|
||||
INVALID_JSON_TEXT = '22032'
|
||||
INVALID_SQL_JSON_SUBSCRIPT = '22033'
|
||||
MORE_THAN_ONE_SQL_JSON_ITEM = '22034'
|
||||
NO_SQL_JSON_ITEM = '22035'
|
||||
NON_NUMERIC_SQL_JSON_ITEM = '22036'
|
||||
NON_UNIQUE_KEYS_IN_A_JSON_OBJECT = '22037'
|
||||
SINGLETON_SQL_JSON_ITEM_REQUIRED = '22038'
|
||||
SQL_JSON_ARRAY_NOT_FOUND = '22039'
|
||||
SQL_JSON_MEMBER_NOT_FOUND = '2203A'
|
||||
SQL_JSON_NUMBER_NOT_FOUND = '2203B'
|
||||
SQL_JSON_OBJECT_NOT_FOUND = '2203C'
|
||||
TOO_MANY_JSON_ARRAY_ELEMENTS = '2203D'
|
||||
TOO_MANY_JSON_OBJECT_MEMBERS = '2203E'
|
||||
SQL_JSON_SCALAR_REQUIRED = '2203F'
|
||||
FLOATING_POINT_EXCEPTION = '22P01'
|
||||
INVALID_TEXT_REPRESENTATION = '22P02'
|
||||
INVALID_BINARY_REPRESENTATION = '22P03'
|
||||
BAD_COPY_FILE_FORMAT = '22P04'
|
||||
UNTRANSLATABLE_CHARACTER = '22P05'
|
||||
NONSTANDARD_USE_OF_ESCAPE_CHARACTER = '22P06'
|
||||
|
||||
# Class 23 - Integrity Constraint Violation
|
||||
INTEGRITY_CONSTRAINT_VIOLATION = '23000'
|
||||
RESTRICT_VIOLATION = '23001'
|
||||
NOT_NULL_VIOLATION = '23502'
|
||||
FOREIGN_KEY_VIOLATION = '23503'
|
||||
UNIQUE_VIOLATION = '23505'
|
||||
CHECK_VIOLATION = '23514'
|
||||
EXCLUSION_VIOLATION = '23P01'
|
||||
|
||||
# Class 24 - Invalid Cursor State
|
||||
INVALID_CURSOR_STATE = '24000'
|
||||
|
||||
# Class 25 - Invalid Transaction State
|
||||
INVALID_TRANSACTION_STATE = '25000'
|
||||
ACTIVE_SQL_TRANSACTION = '25001'
|
||||
BRANCH_TRANSACTION_ALREADY_ACTIVE = '25002'
|
||||
INAPPROPRIATE_ACCESS_MODE_FOR_BRANCH_TRANSACTION = '25003'
|
||||
INAPPROPRIATE_ISOLATION_LEVEL_FOR_BRANCH_TRANSACTION = '25004'
|
||||
NO_ACTIVE_SQL_TRANSACTION_FOR_BRANCH_TRANSACTION = '25005'
|
||||
READ_ONLY_SQL_TRANSACTION = '25006'
|
||||
SCHEMA_AND_DATA_STATEMENT_MIXING_NOT_SUPPORTED = '25007'
|
||||
HELD_CURSOR_REQUIRES_SAME_ISOLATION_LEVEL = '25008'
|
||||
NO_ACTIVE_SQL_TRANSACTION = '25P01'
|
||||
IN_FAILED_SQL_TRANSACTION = '25P02'
|
||||
IDLE_IN_TRANSACTION_SESSION_TIMEOUT = '25P03'
|
||||
|
||||
# Class 26 - Invalid SQL Statement Name
|
||||
INVALID_SQL_STATEMENT_NAME = '26000'
|
||||
|
||||
# Class 27 - Triggered Data Change Violation
|
||||
TRIGGERED_DATA_CHANGE_VIOLATION = '27000'
|
||||
|
||||
# Class 28 - Invalid Authorization Specification
|
||||
INVALID_AUTHORIZATION_SPECIFICATION = '28000'
|
||||
INVALID_PASSWORD = '28P01'
|
||||
|
||||
# Class 2B - Dependent Privilege Descriptors Still Exist
|
||||
DEPENDENT_PRIVILEGE_DESCRIPTORS_STILL_EXIST = '2B000'
|
||||
DEPENDENT_OBJECTS_STILL_EXIST = '2BP01'
|
||||
|
||||
# Class 2D - Invalid Transaction Termination
|
||||
INVALID_TRANSACTION_TERMINATION = '2D000'
|
||||
|
||||
# Class 2F - SQL Routine Exception
|
||||
SQL_ROUTINE_EXCEPTION = '2F000'
|
||||
MODIFYING_SQL_DATA_NOT_PERMITTED_ = '2F002'
|
||||
PROHIBITED_SQL_STATEMENT_ATTEMPTED_ = '2F003'
|
||||
READING_SQL_DATA_NOT_PERMITTED_ = '2F004'
|
||||
FUNCTION_EXECUTED_NO_RETURN_STATEMENT = '2F005'
|
||||
|
||||
# Class 34 - Invalid Cursor Name
|
||||
INVALID_CURSOR_NAME = '34000'
|
||||
|
||||
# Class 38 - External Routine Exception
|
||||
EXTERNAL_ROUTINE_EXCEPTION = '38000'
|
||||
CONTAINING_SQL_NOT_PERMITTED = '38001'
|
||||
MODIFYING_SQL_DATA_NOT_PERMITTED = '38002'
|
||||
PROHIBITED_SQL_STATEMENT_ATTEMPTED = '38003'
|
||||
READING_SQL_DATA_NOT_PERMITTED = '38004'
|
||||
|
||||
# Class 39 - External Routine Invocation Exception
|
||||
EXTERNAL_ROUTINE_INVOCATION_EXCEPTION = '39000'
|
||||
INVALID_SQLSTATE_RETURNED = '39001'
|
||||
NULL_VALUE_NOT_ALLOWED = '39004'
|
||||
TRIGGER_PROTOCOL_VIOLATED = '39P01'
|
||||
SRF_PROTOCOL_VIOLATED = '39P02'
|
||||
EVENT_TRIGGER_PROTOCOL_VIOLATED = '39P03'
|
||||
|
||||
# Class 3B - Savepoint Exception
|
||||
SAVEPOINT_EXCEPTION = '3B000'
|
||||
INVALID_SAVEPOINT_SPECIFICATION = '3B001'
|
||||
|
||||
# Class 3D - Invalid Catalog Name
|
||||
INVALID_CATALOG_NAME = '3D000'
|
||||
|
||||
# Class 3F - Invalid Schema Name
|
||||
INVALID_SCHEMA_NAME = '3F000'
|
||||
|
||||
# Class 40 - Transaction Rollback
|
||||
TRANSACTION_ROLLBACK = '40000'
|
||||
SERIALIZATION_FAILURE = '40001'
|
||||
TRANSACTION_INTEGRITY_CONSTRAINT_VIOLATION = '40002'
|
||||
STATEMENT_COMPLETION_UNKNOWN = '40003'
|
||||
DEADLOCK_DETECTED = '40P01'
|
||||
|
||||
# Class 42 - Syntax Error or Access Rule Violation
|
||||
SYNTAX_ERROR_OR_ACCESS_RULE_VIOLATION = '42000'
|
||||
INSUFFICIENT_PRIVILEGE = '42501'
|
||||
SYNTAX_ERROR = '42601'
|
||||
INVALID_NAME = '42602'
|
||||
INVALID_COLUMN_DEFINITION = '42611'
|
||||
NAME_TOO_LONG = '42622'
|
||||
DUPLICATE_COLUMN = '42701'
|
||||
AMBIGUOUS_COLUMN = '42702'
|
||||
UNDEFINED_COLUMN = '42703'
|
||||
UNDEFINED_OBJECT = '42704'
|
||||
DUPLICATE_OBJECT = '42710'
|
||||
DUPLICATE_ALIAS = '42712'
|
||||
DUPLICATE_FUNCTION = '42723'
|
||||
AMBIGUOUS_FUNCTION = '42725'
|
||||
GROUPING_ERROR = '42803'
|
||||
DATATYPE_MISMATCH = '42804'
|
||||
WRONG_OBJECT_TYPE = '42809'
|
||||
INVALID_FOREIGN_KEY = '42830'
|
||||
CANNOT_COERCE = '42846'
|
||||
UNDEFINED_FUNCTION = '42883'
|
||||
GENERATED_ALWAYS = '428C9'
|
||||
RESERVED_NAME = '42939'
|
||||
UNDEFINED_TABLE = '42P01'
|
||||
UNDEFINED_PARAMETER = '42P02'
|
||||
DUPLICATE_CURSOR = '42P03'
|
||||
DUPLICATE_DATABASE = '42P04'
|
||||
DUPLICATE_PREPARED_STATEMENT = '42P05'
|
||||
DUPLICATE_SCHEMA = '42P06'
|
||||
DUPLICATE_TABLE = '42P07'
|
||||
AMBIGUOUS_PARAMETER = '42P08'
|
||||
AMBIGUOUS_ALIAS = '42P09'
|
||||
INVALID_COLUMN_REFERENCE = '42P10'
|
||||
INVALID_CURSOR_DEFINITION = '42P11'
|
||||
INVALID_DATABASE_DEFINITION = '42P12'
|
||||
INVALID_FUNCTION_DEFINITION = '42P13'
|
||||
INVALID_PREPARED_STATEMENT_DEFINITION = '42P14'
|
||||
INVALID_SCHEMA_DEFINITION = '42P15'
|
||||
INVALID_TABLE_DEFINITION = '42P16'
|
||||
INVALID_OBJECT_DEFINITION = '42P17'
|
||||
INDETERMINATE_DATATYPE = '42P18'
|
||||
INVALID_RECURSION = '42P19'
|
||||
WINDOWING_ERROR = '42P20'
|
||||
COLLATION_MISMATCH = '42P21'
|
||||
INDETERMINATE_COLLATION = '42P22'
|
||||
|
||||
# Class 44 - WITH CHECK OPTION Violation
|
||||
WITH_CHECK_OPTION_VIOLATION = '44000'
|
||||
|
||||
# Class 53 - Insufficient Resources
|
||||
INSUFFICIENT_RESOURCES = '53000'
|
||||
DISK_FULL = '53100'
|
||||
OUT_OF_MEMORY = '53200'
|
||||
TOO_MANY_CONNECTIONS = '53300'
|
||||
CONFIGURATION_LIMIT_EXCEEDED = '53400'
|
||||
|
||||
# Class 54 - Program Limit Exceeded
|
||||
PROGRAM_LIMIT_EXCEEDED = '54000'
|
||||
STATEMENT_TOO_COMPLEX = '54001'
|
||||
TOO_MANY_COLUMNS = '54011'
|
||||
TOO_MANY_ARGUMENTS = '54023'
|
||||
|
||||
# Class 55 - Object Not In Prerequisite State
|
||||
OBJECT_NOT_IN_PREREQUISITE_STATE = '55000'
|
||||
OBJECT_IN_USE = '55006'
|
||||
CANT_CHANGE_RUNTIME_PARAM = '55P02'
|
||||
LOCK_NOT_AVAILABLE = '55P03'
|
||||
UNSAFE_NEW_ENUM_VALUE_USAGE = '55P04'
|
||||
|
||||
# Class 57 - Operator Intervention
|
||||
OPERATOR_INTERVENTION = '57000'
|
||||
QUERY_CANCELED = '57014'
|
||||
ADMIN_SHUTDOWN = '57P01'
|
||||
CRASH_SHUTDOWN = '57P02'
|
||||
CANNOT_CONNECT_NOW = '57P03'
|
||||
DATABASE_DROPPED = '57P04'
|
||||
|
||||
# Class 58 - System Error (errors external to PostgreSQL itself)
|
||||
SYSTEM_ERROR = '58000'
|
||||
IO_ERROR = '58030'
|
||||
UNDEFINED_FILE = '58P01'
|
||||
DUPLICATE_FILE = '58P02'
|
||||
|
||||
# Class 72 - Snapshot Failure
|
||||
SNAPSHOT_TOO_OLD = '72000'
|
||||
|
||||
# Class F0 - Configuration File Error
|
||||
CONFIG_FILE_ERROR = 'F0000'
|
||||
LOCK_FILE_EXISTS = 'F0001'
|
||||
|
||||
# Class HV - Foreign Data Wrapper Error (SQL/MED)
|
||||
FDW_ERROR = 'HV000'
|
||||
FDW_OUT_OF_MEMORY = 'HV001'
|
||||
FDW_DYNAMIC_PARAMETER_VALUE_NEEDED = 'HV002'
|
||||
FDW_INVALID_DATA_TYPE = 'HV004'
|
||||
FDW_COLUMN_NAME_NOT_FOUND = 'HV005'
|
||||
FDW_INVALID_DATA_TYPE_DESCRIPTORS = 'HV006'
|
||||
FDW_INVALID_COLUMN_NAME = 'HV007'
|
||||
FDW_INVALID_COLUMN_NUMBER = 'HV008'
|
||||
FDW_INVALID_USE_OF_NULL_POINTER = 'HV009'
|
||||
FDW_INVALID_STRING_FORMAT = 'HV00A'
|
||||
FDW_INVALID_HANDLE = 'HV00B'
|
||||
FDW_INVALID_OPTION_INDEX = 'HV00C'
|
||||
FDW_INVALID_OPTION_NAME = 'HV00D'
|
||||
FDW_OPTION_NAME_NOT_FOUND = 'HV00J'
|
||||
FDW_REPLY_HANDLE = 'HV00K'
|
||||
FDW_UNABLE_TO_CREATE_EXECUTION = 'HV00L'
|
||||
FDW_UNABLE_TO_CREATE_REPLY = 'HV00M'
|
||||
FDW_UNABLE_TO_ESTABLISH_CONNECTION = 'HV00N'
|
||||
FDW_NO_SCHEMAS = 'HV00P'
|
||||
FDW_SCHEMA_NOT_FOUND = 'HV00Q'
|
||||
FDW_TABLE_NOT_FOUND = 'HV00R'
|
||||
FDW_FUNCTION_SEQUENCE_ERROR = 'HV010'
|
||||
FDW_TOO_MANY_HANDLES = 'HV014'
|
||||
FDW_INCONSISTENT_DESCRIPTOR_INFORMATION = 'HV021'
|
||||
FDW_INVALID_ATTRIBUTE_VALUE = 'HV024'
|
||||
FDW_INVALID_STRING_LENGTH_OR_BUFFER_LENGTH = 'HV090'
|
||||
FDW_INVALID_DESCRIPTOR_FIELD_IDENTIFIER = 'HV091'
|
||||
|
||||
# Class P0 - PL/pgSQL Error
|
||||
PLPGSQL_ERROR = 'P0000'
|
||||
RAISE_EXCEPTION = 'P0001'
|
||||
NO_DATA_FOUND = 'P0002'
|
||||
TOO_MANY_ROWS = 'P0003'
|
||||
ASSERT_FAILURE = 'P0004'
|
||||
|
||||
# Class XX - Internal Error
|
||||
INTERNAL_ERROR = 'XX000'
|
||||
DATA_CORRUPTED = 'XX001'
|
||||
INDEX_CORRUPTED = 'XX002'
|
||||
38
lib/errors.py
Normal file
38
lib/errors.py
Normal file
@ -0,0 +1,38 @@
|
||||
"""Error classes for PostgreSQL error codes
|
||||
"""
|
||||
|
||||
# psycopg/errors.py - SQLSTATE and DB-API exceptions
|
||||
#
|
||||
# Copyright (C) 2018-2019 Daniele Varrazzo <daniele.varrazzo@gmail.com>
|
||||
# Copyright (C) 2020-2021 The Psycopg Team
|
||||
#
|
||||
# psycopg2 is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Lesser General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# In addition, as a special exception, the copyright holders give
|
||||
# permission to link this program with the OpenSSL library (or with
|
||||
# modified versions of OpenSSL that use the same license as OpenSSL),
|
||||
# and distribute linked combinations including the two.
|
||||
#
|
||||
# You must obey the GNU Lesser General Public License in all respects for
|
||||
# all of the code used other than OpenSSL.
|
||||
#
|
||||
# psycopg2 is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
|
||||
# License for more details.
|
||||
|
||||
#
|
||||
# NOTE: the exceptions are injected into this module by the C extention.
|
||||
#
|
||||
|
||||
|
||||
def lookup(code):
|
||||
"""Lookup an error code and return its exception class.
|
||||
|
||||
Raise `!KeyError` if the code is not found.
|
||||
"""
|
||||
from psycopg2._psycopg import sqlstate_errors # avoid circular import
|
||||
return sqlstate_errors[code]
|
||||
213
lib/extensions.py
Normal file
213
lib/extensions.py
Normal file
@ -0,0 +1,213 @@
|
||||
"""psycopg extensions to the DBAPI-2.0
|
||||
|
||||
This module holds all the extensions to the DBAPI-2.0 provided by psycopg.
|
||||
|
||||
- `connection` -- the new-type inheritable connection class
|
||||
- `cursor` -- the new-type inheritable cursor class
|
||||
- `lobject` -- the new-type inheritable large object class
|
||||
- `adapt()` -- exposes the PEP-246_ compatible adapting mechanism used
|
||||
by psycopg to adapt Python types to PostgreSQL ones
|
||||
|
||||
.. _PEP-246: https://www.python.org/dev/peps/pep-0246/
|
||||
"""
|
||||
# psycopg/extensions.py - DBAPI-2.0 extensions specific to psycopg
|
||||
#
|
||||
# Copyright (C) 2003-2019 Federico Di Gregorio <fog@debian.org>
|
||||
# Copyright (C) 2020-2021 The Psycopg Team
|
||||
#
|
||||
# psycopg2 is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Lesser General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# In addition, as a special exception, the copyright holders give
|
||||
# permission to link this program with the OpenSSL library (or with
|
||||
# modified versions of OpenSSL that use the same license as OpenSSL),
|
||||
# and distribute linked combinations including the two.
|
||||
#
|
||||
# You must obey the GNU Lesser General Public License in all respects for
|
||||
# all of the code used other than OpenSSL.
|
||||
#
|
||||
# psycopg2 is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
|
||||
# License for more details.
|
||||
|
||||
import re as _re
|
||||
|
||||
from psycopg2._psycopg import ( # noqa
|
||||
BINARYARRAY, BOOLEAN, BOOLEANARRAY, BYTES, BYTESARRAY, DATE, DATEARRAY,
|
||||
DATETIMEARRAY, DECIMAL, DECIMALARRAY, FLOAT, FLOATARRAY, INTEGER,
|
||||
INTEGERARRAY, INTERVAL, INTERVALARRAY, LONGINTEGER, LONGINTEGERARRAY,
|
||||
ROWIDARRAY, STRINGARRAY, TIME, TIMEARRAY, UNICODE, UNICODEARRAY,
|
||||
AsIs, Binary, Boolean, Float, Int, QuotedString, )
|
||||
|
||||
from psycopg2._psycopg import ( # noqa
|
||||
PYDATE, PYDATETIME, PYDATETIMETZ, PYINTERVAL, PYTIME, PYDATEARRAY,
|
||||
PYDATETIMEARRAY, PYDATETIMETZARRAY, PYINTERVALARRAY, PYTIMEARRAY,
|
||||
DateFromPy, TimeFromPy, TimestampFromPy, IntervalFromPy, )
|
||||
|
||||
from psycopg2._psycopg import ( # noqa
|
||||
adapt, adapters, encodings, connection, cursor,
|
||||
lobject, Xid, libpq_version, parse_dsn, quote_ident,
|
||||
string_types, binary_types, new_type, new_array_type, register_type,
|
||||
ISQLQuote, Notify, Diagnostics, Column, ConnectionInfo,
|
||||
QueryCanceledError, TransactionRollbackError,
|
||||
set_wait_callback, get_wait_callback, encrypt_password, )
|
||||
|
||||
|
||||
"""Isolation level values."""
|
||||
ISOLATION_LEVEL_AUTOCOMMIT = 0
|
||||
ISOLATION_LEVEL_READ_UNCOMMITTED = 4
|
||||
ISOLATION_LEVEL_READ_COMMITTED = 1
|
||||
ISOLATION_LEVEL_REPEATABLE_READ = 2
|
||||
ISOLATION_LEVEL_SERIALIZABLE = 3
|
||||
ISOLATION_LEVEL_DEFAULT = None
|
||||
|
||||
|
||||
"""psycopg connection status values."""
|
||||
STATUS_SETUP = 0
|
||||
STATUS_READY = 1
|
||||
STATUS_BEGIN = 2
|
||||
STATUS_SYNC = 3 # currently unused
|
||||
STATUS_ASYNC = 4 # currently unused
|
||||
STATUS_PREPARED = 5
|
||||
|
||||
# This is a useful mnemonic to check if the connection is in a transaction
|
||||
STATUS_IN_TRANSACTION = STATUS_BEGIN
|
||||
|
||||
|
||||
"""psycopg asynchronous connection polling values"""
|
||||
POLL_OK = 0
|
||||
POLL_READ = 1
|
||||
POLL_WRITE = 2
|
||||
POLL_ERROR = 3
|
||||
|
||||
|
||||
"""Backend transaction status values."""
|
||||
TRANSACTION_STATUS_IDLE = 0
|
||||
TRANSACTION_STATUS_ACTIVE = 1
|
||||
TRANSACTION_STATUS_INTRANS = 2
|
||||
TRANSACTION_STATUS_INERROR = 3
|
||||
TRANSACTION_STATUS_UNKNOWN = 4
|
||||
|
||||
|
||||
def register_adapter(typ, callable):
|
||||
"""Register 'callable' as an ISQLQuote adapter for type 'typ'."""
|
||||
adapters[(typ, ISQLQuote)] = callable
|
||||
|
||||
|
||||
# The SQL_IN class is the official adapter for tuples starting from 2.0.6.
|
||||
class SQL_IN:
|
||||
"""Adapt any iterable to an SQL quotable object."""
|
||||
def __init__(self, seq):
|
||||
self._seq = seq
|
||||
self._conn = None
|
||||
|
||||
def prepare(self, conn):
|
||||
self._conn = conn
|
||||
|
||||
def getquoted(self):
|
||||
# this is the important line: note how every object in the
|
||||
# list is adapted and then how getquoted() is called on it
|
||||
pobjs = [adapt(o) for o in self._seq]
|
||||
if self._conn is not None:
|
||||
for obj in pobjs:
|
||||
if hasattr(obj, 'prepare'):
|
||||
obj.prepare(self._conn)
|
||||
qobjs = [o.getquoted() for o in pobjs]
|
||||
return b'(' + b', '.join(qobjs) + b')'
|
||||
|
||||
def __str__(self):
|
||||
return str(self.getquoted())
|
||||
|
||||
|
||||
class NoneAdapter:
|
||||
"""Adapt None to NULL.
|
||||
|
||||
This adapter is not used normally as a fast path in mogrify uses NULL,
|
||||
but it makes easier to adapt composite types.
|
||||
"""
|
||||
def __init__(self, obj):
|
||||
pass
|
||||
|
||||
def getquoted(self, _null=b"NULL"):
|
||||
return _null
|
||||
|
||||
|
||||
def make_dsn(dsn=None, **kwargs):
|
||||
"""Convert a set of keywords into a connection strings."""
|
||||
if dsn is None and not kwargs:
|
||||
return ''
|
||||
|
||||
# If no kwarg is specified don't mung the dsn, but verify it
|
||||
if not kwargs:
|
||||
parse_dsn(dsn)
|
||||
return dsn
|
||||
|
||||
# Override the dsn with the parameters
|
||||
if 'database' in kwargs:
|
||||
if 'dbname' in kwargs:
|
||||
raise TypeError(
|
||||
"you can't specify both 'database' and 'dbname' arguments")
|
||||
kwargs['dbname'] = kwargs.pop('database')
|
||||
|
||||
# Drop the None arguments
|
||||
kwargs = {k: v for (k, v) in kwargs.items() if v is not None}
|
||||
|
||||
if dsn is not None:
|
||||
tmp = parse_dsn(dsn)
|
||||
tmp.update(kwargs)
|
||||
kwargs = tmp
|
||||
|
||||
dsn = " ".join(["{}={}".format(k, _param_escape(str(v)))
|
||||
for (k, v) in kwargs.items()])
|
||||
|
||||
# verify that the returned dsn is valid
|
||||
parse_dsn(dsn)
|
||||
|
||||
return dsn
|
||||
|
||||
|
||||
def _param_escape(s,
|
||||
re_escape=_re.compile(r"([\\'])"),
|
||||
re_space=_re.compile(r'\s')):
|
||||
"""
|
||||
Apply the escaping rule required by PQconnectdb
|
||||
"""
|
||||
if not s:
|
||||
return "''"
|
||||
|
||||
s = re_escape.sub(r'\\\1', s)
|
||||
if re_space.search(s):
|
||||
s = "'" + s + "'"
|
||||
|
||||
return s
|
||||
|
||||
|
||||
# Create default json typecasters for PostgreSQL 9.2 oids
|
||||
from psycopg2._json import register_default_json, register_default_jsonb # noqa
|
||||
|
||||
try:
|
||||
JSON, JSONARRAY = register_default_json()
|
||||
JSONB, JSONBARRAY = register_default_jsonb()
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
del register_default_json, register_default_jsonb
|
||||
|
||||
|
||||
# Create default Range typecasters
|
||||
from psycopg2. _range import Range # noqa
|
||||
del Range
|
||||
|
||||
|
||||
# Add the "cleaned" version of the encodings to the key.
|
||||
# When the encoding is set its name is cleaned up from - and _ and turned
|
||||
# uppercase, so an encoding not respecting these rules wouldn't be found in the
|
||||
# encodings keys and would raise an exception with the unicode typecaster
|
||||
for k, v in list(encodings.items()):
|
||||
k = k.replace('_', '').replace('-', '').upper()
|
||||
encodings[k] = v
|
||||
|
||||
del k, v
|
||||
1306
lib/extras.py
Normal file
1306
lib/extras.py
Normal file
File diff suppressed because it is too large
Load Diff
187
lib/pool.py
Normal file
187
lib/pool.py
Normal file
@ -0,0 +1,187 @@
|
||||
"""Connection pooling for psycopg2
|
||||
|
||||
This module implements thread-safe (and not) connection pools.
|
||||
"""
|
||||
# psycopg/pool.py - pooling code for psycopg
|
||||
#
|
||||
# Copyright (C) 2003-2019 Federico Di Gregorio <fog@debian.org>
|
||||
# Copyright (C) 2020-2021 The Psycopg Team
|
||||
#
|
||||
# psycopg2 is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Lesser General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# In addition, as a special exception, the copyright holders give
|
||||
# permission to link this program with the OpenSSL library (or with
|
||||
# modified versions of OpenSSL that use the same license as OpenSSL),
|
||||
# and distribute linked combinations including the two.
|
||||
#
|
||||
# You must obey the GNU Lesser General Public License in all respects for
|
||||
# all of the code used other than OpenSSL.
|
||||
#
|
||||
# psycopg2 is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
|
||||
# License for more details.
|
||||
|
||||
import psycopg2
|
||||
from psycopg2 import extensions as _ext
|
||||
|
||||
|
||||
class PoolError(psycopg2.Error):
|
||||
pass
|
||||
|
||||
|
||||
class AbstractConnectionPool:
|
||||
"""Generic key-based pooling code."""
|
||||
|
||||
def __init__(self, minconn, maxconn, *args, **kwargs):
|
||||
"""Initialize the connection pool.
|
||||
|
||||
New 'minconn' connections are created immediately calling 'connfunc'
|
||||
with given parameters. The connection pool will support a maximum of
|
||||
about 'maxconn' connections.
|
||||
"""
|
||||
self.minconn = int(minconn)
|
||||
self.maxconn = int(maxconn)
|
||||
self.closed = False
|
||||
|
||||
self._args = args
|
||||
self._kwargs = kwargs
|
||||
|
||||
self._pool = []
|
||||
self._used = {}
|
||||
self._rused = {} # id(conn) -> key map
|
||||
self._keys = 0
|
||||
|
||||
for i in range(self.minconn):
|
||||
self._connect()
|
||||
|
||||
def _connect(self, key=None):
|
||||
"""Create a new connection and assign it to 'key' if not None."""
|
||||
conn = psycopg2.connect(*self._args, **self._kwargs)
|
||||
if key is not None:
|
||||
self._used[key] = conn
|
||||
self._rused[id(conn)] = key
|
||||
else:
|
||||
self._pool.append(conn)
|
||||
return conn
|
||||
|
||||
def _getkey(self):
|
||||
"""Return a new unique key."""
|
||||
self._keys += 1
|
||||
return self._keys
|
||||
|
||||
def _getconn(self, key=None):
|
||||
"""Get a free connection and assign it to 'key' if not None."""
|
||||
if self.closed:
|
||||
raise PoolError("connection pool is closed")
|
||||
if key is None:
|
||||
key = self._getkey()
|
||||
|
||||
if key in self._used:
|
||||
return self._used[key]
|
||||
|
||||
if self._pool:
|
||||
self._used[key] = conn = self._pool.pop()
|
||||
self._rused[id(conn)] = key
|
||||
return conn
|
||||
else:
|
||||
if len(self._used) == self.maxconn:
|
||||
raise PoolError("connection pool exhausted")
|
||||
return self._connect(key)
|
||||
|
||||
def _putconn(self, conn, key=None, close=False):
|
||||
"""Put away a connection."""
|
||||
if self.closed:
|
||||
raise PoolError("connection pool is closed")
|
||||
|
||||
if key is None:
|
||||
key = self._rused.get(id(conn))
|
||||
if key is None:
|
||||
raise PoolError("trying to put unkeyed connection")
|
||||
|
||||
if len(self._pool) < self.minconn and not close:
|
||||
# Return the connection into a consistent state before putting
|
||||
# it back into the pool
|
||||
if not conn.closed:
|
||||
status = conn.info.transaction_status
|
||||
if status == _ext.TRANSACTION_STATUS_UNKNOWN:
|
||||
# server connection lost
|
||||
conn.close()
|
||||
elif status != _ext.TRANSACTION_STATUS_IDLE:
|
||||
# connection in error or in transaction
|
||||
conn.rollback()
|
||||
self._pool.append(conn)
|
||||
else:
|
||||
# regular idle connection
|
||||
self._pool.append(conn)
|
||||
# If the connection is closed, we just discard it.
|
||||
else:
|
||||
conn.close()
|
||||
|
||||
# here we check for the presence of key because it can happen that a
|
||||
# thread tries to put back a connection after a call to close
|
||||
if not self.closed or key in self._used:
|
||||
del self._used[key]
|
||||
del self._rused[id(conn)]
|
||||
|
||||
def _closeall(self):
|
||||
"""Close all connections.
|
||||
|
||||
Note that this can lead to some code fail badly when trying to use
|
||||
an already closed connection. If you call .closeall() make sure
|
||||
your code can deal with it.
|
||||
"""
|
||||
if self.closed:
|
||||
raise PoolError("connection pool is closed")
|
||||
for conn in self._pool + list(self._used.values()):
|
||||
try:
|
||||
conn.close()
|
||||
except Exception:
|
||||
pass
|
||||
self.closed = True
|
||||
|
||||
|
||||
class SimpleConnectionPool(AbstractConnectionPool):
|
||||
"""A connection pool that can't be shared across different threads."""
|
||||
|
||||
getconn = AbstractConnectionPool._getconn
|
||||
putconn = AbstractConnectionPool._putconn
|
||||
closeall = AbstractConnectionPool._closeall
|
||||
|
||||
|
||||
class ThreadedConnectionPool(AbstractConnectionPool):
|
||||
"""A connection pool that works with the threading module."""
|
||||
|
||||
def __init__(self, minconn, maxconn, *args, **kwargs):
|
||||
"""Initialize the threading lock."""
|
||||
import threading
|
||||
AbstractConnectionPool.__init__(
|
||||
self, minconn, maxconn, *args, **kwargs)
|
||||
self._lock = threading.Lock()
|
||||
|
||||
def getconn(self, key=None):
|
||||
"""Get a free connection and assign it to 'key' if not None."""
|
||||
self._lock.acquire()
|
||||
try:
|
||||
return self._getconn(key)
|
||||
finally:
|
||||
self._lock.release()
|
||||
|
||||
def putconn(self, conn=None, key=None, close=False):
|
||||
"""Put away an unused connection."""
|
||||
self._lock.acquire()
|
||||
try:
|
||||
self._putconn(conn, key, close)
|
||||
finally:
|
||||
self._lock.release()
|
||||
|
||||
def closeall(self):
|
||||
"""Close all connections (even the one currently in use.)"""
|
||||
self._lock.acquire()
|
||||
try:
|
||||
self._closeall()
|
||||
finally:
|
||||
self._lock.release()
|
||||
452
lib/sql.py
Normal file
452
lib/sql.py
Normal file
@ -0,0 +1,452 @@
|
||||
"""SQL composition utility module
|
||||
"""
|
||||
|
||||
# psycopg/sql.py - SQL composition utility module
|
||||
#
|
||||
# Copyright (C) 2016-2019 Daniele Varrazzo <daniele.varrazzo@gmail.com>
|
||||
# Copyright (C) 2020-2021 The Psycopg Team
|
||||
#
|
||||
# psycopg2 is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Lesser General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# In addition, as a special exception, the copyright holders give
|
||||
# permission to link this program with the OpenSSL library (or with
|
||||
# modified versions of OpenSSL that use the same license as OpenSSL),
|
||||
# and distribute linked combinations including the two.
|
||||
#
|
||||
# You must obey the GNU Lesser General Public License in all respects for
|
||||
# all of the code used other than OpenSSL.
|
||||
#
|
||||
# psycopg2 is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
|
||||
# License for more details.
|
||||
|
||||
import string
|
||||
|
||||
from psycopg2 import extensions as ext
|
||||
|
||||
|
||||
_formatter = string.Formatter()
|
||||
|
||||
|
||||
class Composable:
|
||||
"""
|
||||
Abstract base class for objects that can be used to compose an SQL string.
|
||||
|
||||
`!Composable` objects can be passed directly to `~cursor.execute()`,
|
||||
`~cursor.executemany()`, `~cursor.copy_expert()` in place of the query
|
||||
string.
|
||||
|
||||
`!Composable` objects can be joined using the ``+`` operator: the result
|
||||
will be a `Composed` instance containing the objects joined. The operator
|
||||
``*`` is also supported with an integer argument: the result is a
|
||||
`!Composed` instance containing the left argument repeated as many times as
|
||||
requested.
|
||||
"""
|
||||
def __init__(self, wrapped):
|
||||
self._wrapped = wrapped
|
||||
|
||||
def __repr__(self):
|
||||
return f"{self.__class__.__name__}({self._wrapped!r})"
|
||||
|
||||
def as_string(self, context):
|
||||
"""
|
||||
Return the string value of the object.
|
||||
|
||||
:param context: the context to evaluate the string into.
|
||||
:type context: `connection` or `cursor`
|
||||
|
||||
The method is automatically invoked by `~cursor.execute()`,
|
||||
`~cursor.executemany()`, `~cursor.copy_expert()` if a `!Composable` is
|
||||
passed instead of the query string.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def __add__(self, other):
|
||||
if isinstance(other, Composed):
|
||||
return Composed([self]) + other
|
||||
if isinstance(other, Composable):
|
||||
return Composed([self]) + Composed([other])
|
||||
else:
|
||||
return NotImplemented
|
||||
|
||||
def __mul__(self, n):
|
||||
return Composed([self] * n)
|
||||
|
||||
def __eq__(self, other):
|
||||
return type(self) is type(other) and self._wrapped == other._wrapped
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self.__eq__(other)
|
||||
|
||||
|
||||
class Composed(Composable):
|
||||
"""
|
||||
A `Composable` object made of a sequence of `!Composable`.
|
||||
|
||||
The object is usually created using `!Composable` operators and methods.
|
||||
However it is possible to create a `!Composed` directly specifying a
|
||||
sequence of `!Composable` as arguments.
|
||||
|
||||
Example::
|
||||
|
||||
>>> comp = sql.Composed(
|
||||
... [sql.SQL("insert into "), sql.Identifier("table")])
|
||||
>>> print(comp.as_string(conn))
|
||||
insert into "table"
|
||||
|
||||
`!Composed` objects are iterable (so they can be used in `SQL.join` for
|
||||
instance).
|
||||
"""
|
||||
def __init__(self, seq):
|
||||
wrapped = []
|
||||
for i in seq:
|
||||
if not isinstance(i, Composable):
|
||||
raise TypeError(
|
||||
f"Composed elements must be Composable, got {i!r} instead")
|
||||
wrapped.append(i)
|
||||
|
||||
super().__init__(wrapped)
|
||||
|
||||
@property
|
||||
def seq(self):
|
||||
"""The list of the content of the `!Composed`."""
|
||||
return list(self._wrapped)
|
||||
|
||||
def as_string(self, context):
|
||||
rv = []
|
||||
for i in self._wrapped:
|
||||
rv.append(i.as_string(context))
|
||||
return ''.join(rv)
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self._wrapped)
|
||||
|
||||
def __add__(self, other):
|
||||
if isinstance(other, Composed):
|
||||
return Composed(self._wrapped + other._wrapped)
|
||||
if isinstance(other, Composable):
|
||||
return Composed(self._wrapped + [other])
|
||||
else:
|
||||
return NotImplemented
|
||||
|
||||
def join(self, joiner):
|
||||
"""
|
||||
Return a new `!Composed` interposing the *joiner* with the `!Composed` items.
|
||||
|
||||
The *joiner* must be a `SQL` or a string which will be interpreted as
|
||||
an `SQL`.
|
||||
|
||||
Example::
|
||||
|
||||
>>> fields = sql.Identifier('foo') + sql.Identifier('bar') # a Composed
|
||||
>>> print(fields.join(', ').as_string(conn))
|
||||
"foo", "bar"
|
||||
|
||||
"""
|
||||
if isinstance(joiner, str):
|
||||
joiner = SQL(joiner)
|
||||
elif not isinstance(joiner, SQL):
|
||||
raise TypeError(
|
||||
"Composed.join() argument must be a string or an SQL")
|
||||
|
||||
return joiner.join(self)
|
||||
|
||||
|
||||
class SQL(Composable):
|
||||
"""
|
||||
A `Composable` representing a snippet of SQL statement.
|
||||
|
||||
`!SQL` exposes `join()` and `format()` methods useful to create a template
|
||||
where to merge variable parts of a query (for instance field or table
|
||||
names).
|
||||
|
||||
The *string* doesn't undergo any form of escaping, so it is not suitable to
|
||||
represent variable identifiers or values: you should only use it to pass
|
||||
constant strings representing templates or snippets of SQL statements; use
|
||||
other objects such as `Identifier` or `Literal` to represent variable
|
||||
parts.
|
||||
|
||||
Example::
|
||||
|
||||
>>> query = sql.SQL("select {0} from {1}").format(
|
||||
... sql.SQL(', ').join([sql.Identifier('foo'), sql.Identifier('bar')]),
|
||||
... sql.Identifier('table'))
|
||||
>>> print(query.as_string(conn))
|
||||
select "foo", "bar" from "table"
|
||||
"""
|
||||
def __init__(self, string):
|
||||
if not isinstance(string, str):
|
||||
raise TypeError("SQL values must be strings")
|
||||
super().__init__(string)
|
||||
|
||||
@property
|
||||
def string(self):
|
||||
"""The string wrapped by the `!SQL` object."""
|
||||
return self._wrapped
|
||||
|
||||
def as_string(self, context):
|
||||
return self._wrapped
|
||||
|
||||
def format(self, *args, **kwargs):
|
||||
"""
|
||||
Merge `Composable` objects into a template.
|
||||
|
||||
:param `Composable` args: parameters to replace to numbered
|
||||
(``{0}``, ``{1}``) or auto-numbered (``{}``) placeholders
|
||||
:param `Composable` kwargs: parameters to replace to named (``{name}``)
|
||||
placeholders
|
||||
:return: the union of the `!SQL` string with placeholders replaced
|
||||
:rtype: `Composed`
|
||||
|
||||
The method is similar to the Python `str.format()` method: the string
|
||||
template supports auto-numbered (``{}``), numbered (``{0}``,
|
||||
``{1}``...), and named placeholders (``{name}``), with positional
|
||||
arguments replacing the numbered placeholders and keywords replacing
|
||||
the named ones. However placeholder modifiers (``{0!r}``, ``{0:<10}``)
|
||||
are not supported. Only `!Composable` objects can be passed to the
|
||||
template.
|
||||
|
||||
Example::
|
||||
|
||||
>>> print(sql.SQL("select * from {} where {} = %s")
|
||||
... .format(sql.Identifier('people'), sql.Identifier('id'))
|
||||
... .as_string(conn))
|
||||
select * from "people" where "id" = %s
|
||||
|
||||
>>> print(sql.SQL("select * from {tbl} where {pkey} = %s")
|
||||
... .format(tbl=sql.Identifier('people'), pkey=sql.Identifier('id'))
|
||||
... .as_string(conn))
|
||||
select * from "people" where "id" = %s
|
||||
|
||||
"""
|
||||
rv = []
|
||||
autonum = 0
|
||||
for pre, name, spec, conv in _formatter.parse(self._wrapped):
|
||||
if spec:
|
||||
raise ValueError("no format specification supported by SQL")
|
||||
if conv:
|
||||
raise ValueError("no format conversion supported by SQL")
|
||||
if pre:
|
||||
rv.append(SQL(pre))
|
||||
|
||||
if name is None:
|
||||
continue
|
||||
|
||||
if name.isdigit():
|
||||
if autonum:
|
||||
raise ValueError(
|
||||
"cannot switch from automatic field numbering to manual")
|
||||
rv.append(args[int(name)])
|
||||
autonum = None
|
||||
|
||||
elif not name:
|
||||
if autonum is None:
|
||||
raise ValueError(
|
||||
"cannot switch from manual field numbering to automatic")
|
||||
rv.append(args[autonum])
|
||||
autonum += 1
|
||||
|
||||
else:
|
||||
rv.append(kwargs[name])
|
||||
|
||||
return Composed(rv)
|
||||
|
||||
def join(self, seq):
|
||||
"""
|
||||
Join a sequence of `Composable`.
|
||||
|
||||
:param seq: the elements to join.
|
||||
:type seq: iterable of `!Composable`
|
||||
|
||||
Use the `!SQL` object's *string* to separate the elements in *seq*.
|
||||
Note that `Composed` objects are iterable too, so they can be used as
|
||||
argument for this method.
|
||||
|
||||
Example::
|
||||
|
||||
>>> snip = sql.SQL(', ').join(
|
||||
... sql.Identifier(n) for n in ['foo', 'bar', 'baz'])
|
||||
>>> print(snip.as_string(conn))
|
||||
"foo", "bar", "baz"
|
||||
"""
|
||||
rv = []
|
||||
it = iter(seq)
|
||||
try:
|
||||
rv.append(next(it))
|
||||
except StopIteration:
|
||||
pass
|
||||
else:
|
||||
for i in it:
|
||||
rv.append(self)
|
||||
rv.append(i)
|
||||
|
||||
return Composed(rv)
|
||||
|
||||
|
||||
class Identifier(Composable):
|
||||
"""
|
||||
A `Composable` representing an SQL identifier or a dot-separated sequence.
|
||||
|
||||
Identifiers usually represent names of database objects, such as tables or
|
||||
fields. PostgreSQL identifiers follow `different rules`__ than SQL string
|
||||
literals for escaping (e.g. they use double quotes instead of single).
|
||||
|
||||
.. __: https://www.postgresql.org/docs/current/static/sql-syntax-lexical.html# \
|
||||
SQL-SYNTAX-IDENTIFIERS
|
||||
|
||||
Example::
|
||||
|
||||
>>> t1 = sql.Identifier("foo")
|
||||
>>> t2 = sql.Identifier("ba'r")
|
||||
>>> t3 = sql.Identifier('ba"z')
|
||||
>>> print(sql.SQL(', ').join([t1, t2, t3]).as_string(conn))
|
||||
"foo", "ba'r", "ba""z"
|
||||
|
||||
Multiple strings can be passed to the object to represent a qualified name,
|
||||
i.e. a dot-separated sequence of identifiers.
|
||||
|
||||
Example::
|
||||
|
||||
>>> query = sql.SQL("select {} from {}").format(
|
||||
... sql.Identifier("table", "field"),
|
||||
... sql.Identifier("schema", "table"))
|
||||
>>> print(query.as_string(conn))
|
||||
select "table"."field" from "schema"."table"
|
||||
|
||||
"""
|
||||
def __init__(self, *strings):
|
||||
if not strings:
|
||||
raise TypeError("Identifier cannot be empty")
|
||||
|
||||
for s in strings:
|
||||
if not isinstance(s, str):
|
||||
raise TypeError("SQL identifier parts must be strings")
|
||||
|
||||
super().__init__(strings)
|
||||
|
||||
@property
|
||||
def strings(self):
|
||||
"""A tuple with the strings wrapped by the `Identifier`."""
|
||||
return self._wrapped
|
||||
|
||||
@property
|
||||
def string(self):
|
||||
"""The string wrapped by the `Identifier`.
|
||||
"""
|
||||
if len(self._wrapped) == 1:
|
||||
return self._wrapped[0]
|
||||
else:
|
||||
raise AttributeError(
|
||||
"the Identifier wraps more than one than one string")
|
||||
|
||||
def __repr__(self):
|
||||
return f"{self.__class__.__name__}({', '.join(map(repr, self._wrapped))})"
|
||||
|
||||
def as_string(self, context):
|
||||
return '.'.join(ext.quote_ident(s, context) for s in self._wrapped)
|
||||
|
||||
|
||||
class Literal(Composable):
|
||||
"""
|
||||
A `Composable` representing an SQL value to include in a query.
|
||||
|
||||
Usually you will want to include placeholders in the query and pass values
|
||||
as `~cursor.execute()` arguments. If however you really really need to
|
||||
include a literal value in the query you can use this object.
|
||||
|
||||
The string returned by `!as_string()` follows the normal :ref:`adaptation
|
||||
rules <python-types-adaptation>` for Python objects.
|
||||
|
||||
Example::
|
||||
|
||||
>>> s1 = sql.Literal("foo")
|
||||
>>> s2 = sql.Literal("ba'r")
|
||||
>>> s3 = sql.Literal(42)
|
||||
>>> print(sql.SQL(', ').join([s1, s2, s3]).as_string(conn))
|
||||
'foo', 'ba''r', 42
|
||||
|
||||
"""
|
||||
@property
|
||||
def wrapped(self):
|
||||
"""The object wrapped by the `!Literal`."""
|
||||
return self._wrapped
|
||||
|
||||
def as_string(self, context):
|
||||
# is it a connection or cursor?
|
||||
if isinstance(context, ext.connection):
|
||||
conn = context
|
||||
elif isinstance(context, ext.cursor):
|
||||
conn = context.connection
|
||||
else:
|
||||
raise TypeError("context must be a connection or a cursor")
|
||||
|
||||
a = ext.adapt(self._wrapped)
|
||||
if hasattr(a, 'prepare'):
|
||||
a.prepare(conn)
|
||||
|
||||
rv = a.getquoted()
|
||||
if isinstance(rv, bytes):
|
||||
rv = rv.decode(ext.encodings[conn.encoding])
|
||||
|
||||
return rv
|
||||
|
||||
|
||||
class Placeholder(Composable):
|
||||
"""A `Composable` representing a placeholder for query parameters.
|
||||
|
||||
If the name is specified, generate a named placeholder (e.g. ``%(name)s``),
|
||||
otherwise generate a positional placeholder (e.g. ``%s``).
|
||||
|
||||
The object is useful to generate SQL queries with a variable number of
|
||||
arguments.
|
||||
|
||||
Examples::
|
||||
|
||||
>>> names = ['foo', 'bar', 'baz']
|
||||
|
||||
>>> q1 = sql.SQL("insert into table ({}) values ({})").format(
|
||||
... sql.SQL(', ').join(map(sql.Identifier, names)),
|
||||
... sql.SQL(', ').join(sql.Placeholder() * len(names)))
|
||||
>>> print(q1.as_string(conn))
|
||||
insert into table ("foo", "bar", "baz") values (%s, %s, %s)
|
||||
|
||||
>>> q2 = sql.SQL("insert into table ({}) values ({})").format(
|
||||
... sql.SQL(', ').join(map(sql.Identifier, names)),
|
||||
... sql.SQL(', ').join(map(sql.Placeholder, names)))
|
||||
>>> print(q2.as_string(conn))
|
||||
insert into table ("foo", "bar", "baz") values (%(foo)s, %(bar)s, %(baz)s)
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, name=None):
|
||||
if isinstance(name, str):
|
||||
if ')' in name:
|
||||
raise ValueError(f"invalid name: {name!r}")
|
||||
|
||||
elif name is not None:
|
||||
raise TypeError(f"expected string or None as name, got {name!r}")
|
||||
|
||||
super().__init__(name)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""The name of the `!Placeholder`."""
|
||||
return self._wrapped
|
||||
|
||||
def __repr__(self):
|
||||
return f"Placeholder({self._wrapped if self._wrapped is not None else ''!r})"
|
||||
|
||||
def as_string(self, context):
|
||||
if self._wrapped is not None:
|
||||
return f"%({self._wrapped})"
|
||||
else:
|
||||
return "%s"
|
||||
|
||||
|
||||
# Literals
|
||||
NULL = SQL("NULL")
|
||||
DEFAULT = SQL("DEFAULT")
|
||||
158
lib/tz.py
Normal file
158
lib/tz.py
Normal file
@ -0,0 +1,158 @@
|
||||
"""tzinfo implementations for psycopg2
|
||||
|
||||
This module holds two different tzinfo implementations that can be used as
|
||||
the 'tzinfo' argument to datetime constructors, directly passed to psycopg
|
||||
functions or used to set the .tzinfo_factory attribute in cursors.
|
||||
"""
|
||||
# psycopg/tz.py - tzinfo implementation
|
||||
#
|
||||
# Copyright (C) 2003-2019 Federico Di Gregorio <fog@debian.org>
|
||||
# Copyright (C) 2020-2021 The Psycopg Team
|
||||
#
|
||||
# psycopg2 is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Lesser General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# In addition, as a special exception, the copyright holders give
|
||||
# permission to link this program with the OpenSSL library (or with
|
||||
# modified versions of OpenSSL that use the same license as OpenSSL),
|
||||
# and distribute linked combinations including the two.
|
||||
#
|
||||
# You must obey the GNU Lesser General Public License in all respects for
|
||||
# all of the code used other than OpenSSL.
|
||||
#
|
||||
# psycopg2 is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
|
||||
# License for more details.
|
||||
|
||||
import datetime
|
||||
import time
|
||||
|
||||
ZERO = datetime.timedelta(0)
|
||||
|
||||
|
||||
class FixedOffsetTimezone(datetime.tzinfo):
|
||||
"""Fixed offset in minutes east from UTC.
|
||||
|
||||
This is exactly the implementation__ found in Python 2.3.x documentation,
|
||||
with a small change to the `!__init__()` method to allow for pickling
|
||||
and a default name in the form ``sHH:MM`` (``s`` is the sign.).
|
||||
|
||||
The implementation also caches instances. During creation, if a
|
||||
FixedOffsetTimezone instance has previously been created with the same
|
||||
offset and name that instance will be returned. This saves memory and
|
||||
improves comparability.
|
||||
|
||||
.. versionchanged:: 2.9
|
||||
|
||||
The constructor can take either a timedelta or a number of minutes of
|
||||
offset. Previously only minutes were supported.
|
||||
|
||||
.. __: https://docs.python.org/library/datetime.html
|
||||
"""
|
||||
_name = None
|
||||
_offset = ZERO
|
||||
|
||||
_cache = {}
|
||||
|
||||
def __init__(self, offset=None, name=None):
|
||||
if offset is not None:
|
||||
if not isinstance(offset, datetime.timedelta):
|
||||
offset = datetime.timedelta(minutes=offset)
|
||||
self._offset = offset
|
||||
if name is not None:
|
||||
self._name = name
|
||||
|
||||
def __new__(cls, offset=None, name=None):
|
||||
"""Return a suitable instance created earlier if it exists
|
||||
"""
|
||||
key = (offset, name)
|
||||
try:
|
||||
return cls._cache[key]
|
||||
except KeyError:
|
||||
tz = super().__new__(cls, offset, name)
|
||||
cls._cache[key] = tz
|
||||
return tz
|
||||
|
||||
def __repr__(self):
|
||||
return "psycopg2.tz.FixedOffsetTimezone(offset=%r, name=%r)" \
|
||||
% (self._offset, self._name)
|
||||
|
||||
def __eq__(self, other):
|
||||
if isinstance(other, FixedOffsetTimezone):
|
||||
return self._offset == other._offset
|
||||
else:
|
||||
return NotImplemented
|
||||
|
||||
def __ne__(self, other):
|
||||
if isinstance(other, FixedOffsetTimezone):
|
||||
return self._offset != other._offset
|
||||
else:
|
||||
return NotImplemented
|
||||
|
||||
def __getinitargs__(self):
|
||||
return self._offset, self._name
|
||||
|
||||
def utcoffset(self, dt):
|
||||
return self._offset
|
||||
|
||||
def tzname(self, dt):
|
||||
if self._name is not None:
|
||||
return self._name
|
||||
|
||||
minutes, seconds = divmod(self._offset.total_seconds(), 60)
|
||||
hours, minutes = divmod(minutes, 60)
|
||||
rv = "%+03d" % hours
|
||||
if minutes or seconds:
|
||||
rv += ":%02d" % minutes
|
||||
if seconds:
|
||||
rv += ":%02d" % seconds
|
||||
|
||||
return rv
|
||||
|
||||
def dst(self, dt):
|
||||
return ZERO
|
||||
|
||||
|
||||
STDOFFSET = datetime.timedelta(seconds=-time.timezone)
|
||||
if time.daylight:
|
||||
DSTOFFSET = datetime.timedelta(seconds=-time.altzone)
|
||||
else:
|
||||
DSTOFFSET = STDOFFSET
|
||||
DSTDIFF = DSTOFFSET - STDOFFSET
|
||||
|
||||
|
||||
class LocalTimezone(datetime.tzinfo):
|
||||
"""Platform idea of local timezone.
|
||||
|
||||
This is the exact implementation from the Python 2.3 documentation.
|
||||
"""
|
||||
def utcoffset(self, dt):
|
||||
if self._isdst(dt):
|
||||
return DSTOFFSET
|
||||
else:
|
||||
return STDOFFSET
|
||||
|
||||
def dst(self, dt):
|
||||
if self._isdst(dt):
|
||||
return DSTDIFF
|
||||
else:
|
||||
return ZERO
|
||||
|
||||
def tzname(self, dt):
|
||||
return time.tzname[self._isdst(dt)]
|
||||
|
||||
def _isdst(self, dt):
|
||||
tt = (dt.year, dt.month, dt.day,
|
||||
dt.hour, dt.minute, dt.second,
|
||||
dt.weekday(), 0, -1)
|
||||
stamp = time.mktime(tt)
|
||||
tt = time.localtime(stamp)
|
||||
return tt.tm_isdst > 0
|
||||
|
||||
|
||||
LOCAL = LocalTimezone()
|
||||
|
||||
# TODO: pre-generate some interesting time zones?
|
||||
15
psycopg/_psycopg.vc9.amd64.manifest
Normal file
15
psycopg/_psycopg.vc9.amd64.manifest
Normal file
@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
||||
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<security>
|
||||
<requestedPrivileges>
|
||||
<requestedExecutionLevel level="asInvoker" uiAccess="false"></requestedExecutionLevel>
|
||||
</requestedPrivileges>
|
||||
</security>
|
||||
</trustInfo>
|
||||
<dependency>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity type="win32" name="Microsoft.VC90.CRT" version="9.0.21022.8" processorArchitecture="amd64" publicKeyToken="1fc8b3b9a1e18e3b"></assemblyIdentity>
|
||||
</dependentAssembly>
|
||||
</dependency>
|
||||
</assembly>
|
||||
15
psycopg/_psycopg.vc9.x86.manifest
Normal file
15
psycopg/_psycopg.vc9.x86.manifest
Normal file
@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
||||
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<security>
|
||||
<requestedPrivileges>
|
||||
<requestedExecutionLevel level="asInvoker" uiAccess="false"></requestedExecutionLevel>
|
||||
</requestedPrivileges>
|
||||
</security>
|
||||
</trustInfo>
|
||||
<dependency>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity type="win32" name="Microsoft.VC90.CRT" version="9.0.21022.8" processorArchitecture="x86" publicKeyToken="1fc8b3b9a1e18e3b"></assemblyIdentity>
|
||||
</dependentAssembly>
|
||||
</dependency>
|
||||
</assembly>
|
||||
195
psycopg/adapter_asis.c
Normal file
195
psycopg/adapter_asis.c
Normal file
@ -0,0 +1,195 @@
|
||||
/* adapter_asis.c - adapt types as they are
|
||||
*
|
||||
* Copyright (C) 2003-2019 Federico Di Gregorio <fog@debian.org>
|
||||
* Copyright (C) 2020-2021 The Psycopg Team
|
||||
*
|
||||
* This file is part of psycopg.
|
||||
*
|
||||
* psycopg2 is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give
|
||||
* permission to link this program with the OpenSSL library (or with
|
||||
* modified versions of OpenSSL that use the same license as OpenSSL),
|
||||
* and distribute linked combinations including the two.
|
||||
*
|
||||
* You must obey the GNU Lesser General Public License in all respects for
|
||||
* all of the code used other than OpenSSL.
|
||||
*
|
||||
* psycopg2 is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
|
||||
* License for more details.
|
||||
*/
|
||||
|
||||
#define PSYCOPG_MODULE
|
||||
#include "psycopg/psycopg.h"
|
||||
|
||||
#include "psycopg/adapter_asis.h"
|
||||
#include "psycopg/microprotocols_proto.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
|
||||
/** the AsIs object **/
|
||||
|
||||
static PyObject *
|
||||
asis_getquoted(asisObject *self, PyObject *args)
|
||||
{
|
||||
PyObject *rv;
|
||||
if (self->wrapped == Py_None) {
|
||||
Py_INCREF(psyco_null);
|
||||
rv = psyco_null;
|
||||
}
|
||||
else {
|
||||
rv = PyObject_Str(self->wrapped);
|
||||
/* unicode to bytes */
|
||||
if (rv) {
|
||||
PyObject *tmp = PyUnicode_AsUTF8String(rv);
|
||||
Py_DECREF(rv);
|
||||
rv = tmp;
|
||||
}
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
asis_str(asisObject *self)
|
||||
{
|
||||
return psyco_ensure_text(asis_getquoted(self, NULL));
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
asis_conform(asisObject *self, PyObject *args)
|
||||
{
|
||||
PyObject *res, *proto;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "O", &proto)) return NULL;
|
||||
|
||||
if (proto == (PyObject*)&isqlquoteType)
|
||||
res = (PyObject*)self;
|
||||
else
|
||||
res = Py_None;
|
||||
|
||||
Py_INCREF(res);
|
||||
return res;
|
||||
}
|
||||
|
||||
/** the AsIs object */
|
||||
|
||||
/* object member list */
|
||||
|
||||
static struct PyMemberDef asisObject_members[] = {
|
||||
{"adapted", T_OBJECT, offsetof(asisObject, wrapped), READONLY},
|
||||
{NULL}
|
||||
};
|
||||
|
||||
/* object method table */
|
||||
|
||||
static PyMethodDef asisObject_methods[] = {
|
||||
{"getquoted", (PyCFunction)asis_getquoted, METH_NOARGS,
|
||||
"getquoted() -> wrapped object value as SQL-quoted string"},
|
||||
{"__conform__", (PyCFunction)asis_conform, METH_VARARGS, NULL},
|
||||
{NULL} /* Sentinel */
|
||||
};
|
||||
|
||||
/* initialization and finalization methods */
|
||||
|
||||
static int
|
||||
asis_setup(asisObject *self, PyObject *obj)
|
||||
{
|
||||
Dprintf("asis_setup: init asis object at %p, refcnt = "
|
||||
FORMAT_CODE_PY_SSIZE_T,
|
||||
self, Py_REFCNT(self)
|
||||
);
|
||||
|
||||
Py_INCREF(obj);
|
||||
self->wrapped = obj;
|
||||
|
||||
Dprintf("asis_setup: good asis object at %p, refcnt = "
|
||||
FORMAT_CODE_PY_SSIZE_T,
|
||||
self, Py_REFCNT(self)
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
asis_dealloc(PyObject* obj)
|
||||
{
|
||||
asisObject *self = (asisObject *)obj;
|
||||
|
||||
Py_CLEAR(self->wrapped);
|
||||
|
||||
Dprintf("asis_dealloc: deleted asis object at %p, refcnt = "
|
||||
FORMAT_CODE_PY_SSIZE_T,
|
||||
obj, Py_REFCNT(obj)
|
||||
);
|
||||
|
||||
Py_TYPE(obj)->tp_free(obj);
|
||||
}
|
||||
|
||||
static int
|
||||
asis_init(PyObject *obj, PyObject *args, PyObject *kwds)
|
||||
{
|
||||
PyObject *o;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "O", &o))
|
||||
return -1;
|
||||
|
||||
return asis_setup((asisObject *)obj, o);
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
asis_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
|
||||
{
|
||||
return type->tp_alloc(type, 0);
|
||||
}
|
||||
|
||||
|
||||
/* object type */
|
||||
|
||||
#define asisType_doc \
|
||||
"AsIs(str) -> new AsIs adapter object"
|
||||
|
||||
PyTypeObject asisType = {
|
||||
PyVarObject_HEAD_INIT(NULL, 0)
|
||||
"psycopg2.extensions.AsIs",
|
||||
sizeof(asisObject), 0,
|
||||
asis_dealloc, /*tp_dealloc*/
|
||||
0, /*tp_print*/
|
||||
0, /*tp_getattr*/
|
||||
0, /*tp_setattr*/
|
||||
0, /*tp_compare*/
|
||||
0, /*tp_repr*/
|
||||
0, /*tp_as_number*/
|
||||
0, /*tp_as_sequence*/
|
||||
0, /*tp_as_mapping*/
|
||||
0, /*tp_hash */
|
||||
0, /*tp_call*/
|
||||
(reprfunc)asis_str, /*tp_str*/
|
||||
0, /*tp_getattro*/
|
||||
0, /*tp_setattro*/
|
||||
0, /*tp_as_buffer*/
|
||||
Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, /*tp_flags*/
|
||||
asisType_doc, /*tp_doc*/
|
||||
0, /*tp_traverse*/
|
||||
0, /*tp_clear*/
|
||||
0, /*tp_richcompare*/
|
||||
0, /*tp_weaklistoffset*/
|
||||
0, /*tp_iter*/
|
||||
0, /*tp_iternext*/
|
||||
asisObject_methods, /*tp_methods*/
|
||||
asisObject_members, /*tp_members*/
|
||||
0, /*tp_getset*/
|
||||
0, /*tp_base*/
|
||||
0, /*tp_dict*/
|
||||
0, /*tp_descr_get*/
|
||||
0, /*tp_descr_set*/
|
||||
0, /*tp_dictoffset*/
|
||||
asis_init, /*tp_init*/
|
||||
0, /*tp_alloc*/
|
||||
asis_new, /*tp_new*/
|
||||
};
|
||||
48
psycopg/adapter_asis.h
Normal file
48
psycopg/adapter_asis.h
Normal file
@ -0,0 +1,48 @@
|
||||
/* adapter_asis.h - definition for the psycopg AsIs type wrapper
|
||||
*
|
||||
* Copyright (C) 2003-2019 Federico Di Gregorio <fog@debian.org>
|
||||
* Copyright (C) 2020-2021 The Psycopg Team
|
||||
*
|
||||
* This file is part of psycopg.
|
||||
*
|
||||
* psycopg2 is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give
|
||||
* permission to link this program with the OpenSSL library (or with
|
||||
* modified versions of OpenSSL that use the same license as OpenSSL),
|
||||
* and distribute linked combinations including the two.
|
||||
*
|
||||
* You must obey the GNU Lesser General Public License in all respects for
|
||||
* all of the code used other than OpenSSL.
|
||||
*
|
||||
* psycopg2 is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
|
||||
* License for more details.
|
||||
*/
|
||||
|
||||
#ifndef PSYCOPG_ASIS_H
|
||||
#define PSYCOPG_ASIS_H 1
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
extern HIDDEN PyTypeObject asisType;
|
||||
|
||||
typedef struct {
|
||||
PyObject_HEAD
|
||||
|
||||
/* this is the real object we wrap */
|
||||
PyObject *wrapped;
|
||||
|
||||
} asisObject;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* !defined(PSYCOPG_ASIS_H) */
|
||||
281
psycopg/adapter_binary.c
Normal file
281
psycopg/adapter_binary.c
Normal file
@ -0,0 +1,281 @@
|
||||
/* adapter_binary.c - Binary objects
|
||||
*
|
||||
* Copyright (C) 2003-2019 Federico Di Gregorio <fog@debian.org>
|
||||
* Copyright (C) 2020-2021 The Psycopg Team
|
||||
*
|
||||
* This file is part of psycopg.
|
||||
*
|
||||
* psycopg2 is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give
|
||||
* permission to link this program with the OpenSSL library (or with
|
||||
* modified versions of OpenSSL that use the same license as OpenSSL),
|
||||
* and distribute linked combinations including the two.
|
||||
*
|
||||
* You must obey the GNU Lesser General Public License in all respects for
|
||||
* all of the code used other than OpenSSL.
|
||||
*
|
||||
* psycopg2 is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
|
||||
* License for more details.
|
||||
*/
|
||||
|
||||
#define PSYCOPG_MODULE
|
||||
#include "psycopg/psycopg.h"
|
||||
|
||||
#include "psycopg/adapter_binary.h"
|
||||
#include "psycopg/microprotocols_proto.h"
|
||||
#include "psycopg/connection.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
|
||||
/** the quoting code */
|
||||
|
||||
static unsigned char *
|
||||
binary_escape(unsigned char *from, size_t from_length,
|
||||
size_t *to_length, PGconn *conn)
|
||||
{
|
||||
if (conn)
|
||||
return PQescapeByteaConn(conn, from, from_length, to_length);
|
||||
else
|
||||
return PQescapeBytea(from, from_length, to_length);
|
||||
}
|
||||
|
||||
/* binary_quote - do the quote process on plain and unicode strings */
|
||||
|
||||
static PyObject *
|
||||
binary_quote(binaryObject *self)
|
||||
{
|
||||
char *to = NULL;
|
||||
const char *buffer = NULL;
|
||||
Py_ssize_t buffer_len;
|
||||
size_t len = 0;
|
||||
PyObject *rv = NULL;
|
||||
Py_buffer view;
|
||||
int got_view = 0;
|
||||
|
||||
/* Allow Binary(None) to work */
|
||||
if (self->wrapped == Py_None) {
|
||||
Py_INCREF(psyco_null);
|
||||
rv = psyco_null;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
/* if we got a plain string or a buffer we escape it and save the buffer */
|
||||
if (PyObject_CheckBuffer(self->wrapped)) {
|
||||
if (0 > PyObject_GetBuffer(self->wrapped, &view, PyBUF_CONTIG_RO)) {
|
||||
goto exit;
|
||||
}
|
||||
got_view = 1;
|
||||
buffer = (const char *)(view.buf);
|
||||
buffer_len = view.len;
|
||||
}
|
||||
|
||||
if (!buffer) {
|
||||
goto exit;
|
||||
}
|
||||
|
||||
/* escape and build quoted buffer */
|
||||
|
||||
to = (char *)binary_escape((unsigned char*)buffer, (size_t)buffer_len,
|
||||
&len, self->conn ? ((connectionObject*)self->conn)->pgconn : NULL);
|
||||
if (to == NULL) {
|
||||
PyErr_NoMemory();
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (len > 0)
|
||||
rv = Bytes_FromFormat(
|
||||
(self->conn && ((connectionObject*)self->conn)->equote)
|
||||
? "E'%s'::bytea" : "'%s'::bytea" , to);
|
||||
else
|
||||
rv = Bytes_FromString("''::bytea");
|
||||
|
||||
exit:
|
||||
if (to) { PQfreemem(to); }
|
||||
if (got_view) { PyBuffer_Release(&view); }
|
||||
|
||||
/* if the wrapped object is not bytes or a buffer, this is an error */
|
||||
if (!rv && !PyErr_Occurred()) {
|
||||
PyErr_Format(PyExc_TypeError, "can't escape %s to binary",
|
||||
Py_TYPE(self->wrapped)->tp_name);
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
/* binary_str, binary_getquoted - return result of quoting */
|
||||
|
||||
static PyObject *
|
||||
binary_getquoted(binaryObject *self, PyObject *args)
|
||||
{
|
||||
if (self->buffer == NULL) {
|
||||
self->buffer = binary_quote(self);
|
||||
}
|
||||
Py_XINCREF(self->buffer);
|
||||
return self->buffer;
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
binary_str(binaryObject *self)
|
||||
{
|
||||
return psyco_ensure_text(binary_getquoted(self, NULL));
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
binary_prepare(binaryObject *self, PyObject *args)
|
||||
{
|
||||
PyObject *conn;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "O!", &connectionType, &conn))
|
||||
return NULL;
|
||||
|
||||
Py_XDECREF(self->conn);
|
||||
self->conn = conn;
|
||||
Py_INCREF(self->conn);
|
||||
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
binary_conform(binaryObject *self, PyObject *args)
|
||||
{
|
||||
PyObject *res, *proto;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "O", &proto)) return NULL;
|
||||
|
||||
if (proto == (PyObject*)&isqlquoteType)
|
||||
res = (PyObject*)self;
|
||||
else
|
||||
res = Py_None;
|
||||
|
||||
Py_INCREF(res);
|
||||
return res;
|
||||
}
|
||||
|
||||
/** the Binary object **/
|
||||
|
||||
/* object member list */
|
||||
|
||||
static struct PyMemberDef binaryObject_members[] = {
|
||||
{"adapted", T_OBJECT, offsetof(binaryObject, wrapped), READONLY},
|
||||
{"buffer", T_OBJECT, offsetof(binaryObject, buffer), READONLY},
|
||||
{NULL}
|
||||
};
|
||||
|
||||
/* object method table */
|
||||
|
||||
static PyMethodDef binaryObject_methods[] = {
|
||||
{"getquoted", (PyCFunction)binary_getquoted, METH_NOARGS,
|
||||
"getquoted() -> wrapped object value as SQL-quoted binary string"},
|
||||
{"prepare", (PyCFunction)binary_prepare, METH_VARARGS,
|
||||
"prepare(conn) -> prepare for binary encoding using conn"},
|
||||
{"__conform__", (PyCFunction)binary_conform, METH_VARARGS, NULL},
|
||||
{NULL} /* Sentinel */
|
||||
};
|
||||
|
||||
/* initialization and finalization methods */
|
||||
|
||||
static int
|
||||
binary_setup(binaryObject *self, PyObject *str)
|
||||
{
|
||||
Dprintf("binary_setup: init binary object at %p, refcnt = "
|
||||
FORMAT_CODE_PY_SSIZE_T,
|
||||
self, Py_REFCNT(self)
|
||||
);
|
||||
|
||||
self->buffer = NULL;
|
||||
self->conn = NULL;
|
||||
Py_INCREF(str);
|
||||
self->wrapped = str;
|
||||
|
||||
Dprintf("binary_setup: good binary object at %p, refcnt = "
|
||||
FORMAT_CODE_PY_SSIZE_T,
|
||||
self, Py_REFCNT(self));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
binary_dealloc(PyObject* obj)
|
||||
{
|
||||
binaryObject *self = (binaryObject *)obj;
|
||||
|
||||
Py_CLEAR(self->wrapped);
|
||||
Py_CLEAR(self->buffer);
|
||||
Py_CLEAR(self->conn);
|
||||
|
||||
Dprintf("binary_dealloc: deleted binary object at %p, refcnt = "
|
||||
FORMAT_CODE_PY_SSIZE_T,
|
||||
obj, Py_REFCNT(obj)
|
||||
);
|
||||
|
||||
Py_TYPE(obj)->tp_free(obj);
|
||||
}
|
||||
|
||||
static int
|
||||
binary_init(PyObject *obj, PyObject *args, PyObject *kwds)
|
||||
{
|
||||
PyObject *str;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "O", &str))
|
||||
return -1;
|
||||
|
||||
return binary_setup((binaryObject *)obj, str);
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
binary_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
|
||||
{
|
||||
return type->tp_alloc(type, 0);
|
||||
}
|
||||
|
||||
|
||||
/* object type */
|
||||
|
||||
#define binaryType_doc \
|
||||
"Binary(buffer) -> new binary object"
|
||||
|
||||
PyTypeObject binaryType = {
|
||||
PyVarObject_HEAD_INIT(NULL, 0)
|
||||
"psycopg2.extensions.Binary",
|
||||
sizeof(binaryObject), 0,
|
||||
binary_dealloc, /*tp_dealloc*/
|
||||
0, /*tp_print*/
|
||||
0, /*tp_getattr*/
|
||||
0, /*tp_setattr*/
|
||||
0, /*tp_compare*/
|
||||
0, /*tp_repr*/
|
||||
0, /*tp_as_number*/
|
||||
0, /*tp_as_sequence*/
|
||||
0, /*tp_as_mapping*/
|
||||
0, /*tp_hash */
|
||||
0, /*tp_call*/
|
||||
(reprfunc)binary_str, /*tp_str*/
|
||||
0, /*tp_getattro*/
|
||||
0, /*tp_setattro*/
|
||||
0, /*tp_as_buffer*/
|
||||
Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, /*tp_flags*/
|
||||
binaryType_doc, /*tp_doc*/
|
||||
0, /*tp_traverse*/
|
||||
0, /*tp_clear*/
|
||||
0, /*tp_richcompare*/
|
||||
0, /*tp_weaklistoffset*/
|
||||
0, /*tp_iter*/
|
||||
0, /*tp_iternext*/
|
||||
binaryObject_methods, /*tp_methods*/
|
||||
binaryObject_members, /*tp_members*/
|
||||
0, /*tp_getset*/
|
||||
0, /*tp_base*/
|
||||
0, /*tp_dict*/
|
||||
0, /*tp_descr_get*/
|
||||
0, /*tp_descr_set*/
|
||||
0, /*tp_dictoffset*/
|
||||
binary_init, /*tp_init*/
|
||||
0, /*tp_alloc*/
|
||||
binary_new, /*tp_new*/
|
||||
};
|
||||
48
psycopg/adapter_binary.h
Normal file
48
psycopg/adapter_binary.h
Normal file
@ -0,0 +1,48 @@
|
||||
/* adapter_binary.h - definition for the Binary type
|
||||
*
|
||||
* Copyright (C) 2003-2019 Federico Di Gregorio <fog@debian.org>
|
||||
* Copyright (C) 2020-2021 The Psycopg Team
|
||||
*
|
||||
* This file is part of psycopg.
|
||||
*
|
||||
* psycopg2 is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give
|
||||
* permission to link this program with the OpenSSL library (or with
|
||||
* modified versions of OpenSSL that use the same license as OpenSSL),
|
||||
* and distribute linked combinations including the two.
|
||||
*
|
||||
* You must obey the GNU Lesser General Public License in all respects for
|
||||
* all of the code used other than OpenSSL.
|
||||
*
|
||||
* psycopg2 is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
|
||||
* License for more details.
|
||||
*/
|
||||
|
||||
#ifndef PSYCOPG_BINARY_H
|
||||
#define PSYCOPG_BINARY_H 1
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
extern HIDDEN PyTypeObject binaryType;
|
||||
|
||||
typedef struct {
|
||||
PyObject_HEAD
|
||||
|
||||
PyObject *wrapped;
|
||||
PyObject *buffer;
|
||||
PyObject *conn;
|
||||
} binaryObject;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* !defined(PSYCOPG_BINARY_H) */
|
||||
515
psycopg/adapter_datetime.c
Normal file
515
psycopg/adapter_datetime.c
Normal file
@ -0,0 +1,515 @@
|
||||
/* adapter_datetime.c - python date/time objects
|
||||
*
|
||||
* Copyright (C) 2003-2019 Federico Di Gregorio <fog@debian.org>
|
||||
* Copyright (C) 2020-2021 The Psycopg Team
|
||||
*
|
||||
* This file is part of psycopg.
|
||||
*
|
||||
* psycopg2 is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give
|
||||
* permission to link this program with the OpenSSL library (or with
|
||||
* modified versions of OpenSSL that use the same license as OpenSSL),
|
||||
* and distribute linked combinations including the two.
|
||||
*
|
||||
* You must obey the GNU Lesser General Public License in all respects for
|
||||
* all of the code used other than OpenSSL.
|
||||
*
|
||||
* psycopg2 is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
|
||||
* License for more details.
|
||||
*/
|
||||
|
||||
#define PSYCOPG_MODULE
|
||||
#include "psycopg/psycopg.h"
|
||||
|
||||
#include "psycopg/adapter_datetime.h"
|
||||
#include "psycopg/microprotocols_proto.h"
|
||||
|
||||
#include <datetime.h>
|
||||
|
||||
#include <time.h>
|
||||
#include <string.h>
|
||||
|
||||
|
||||
RAISES_NEG int
|
||||
adapter_datetime_init(void)
|
||||
{
|
||||
PyDateTime_IMPORT;
|
||||
|
||||
if (!PyDateTimeAPI) {
|
||||
PyErr_SetString(PyExc_ImportError, "datetime initialization failed");
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* datetime_str, datetime_getquoted - return result of quoting */
|
||||
|
||||
static PyObject *
|
||||
_pydatetime_string_date_time(pydatetimeObject *self)
|
||||
{
|
||||
PyObject *rv = NULL;
|
||||
PyObject *iso = NULL;
|
||||
PyObject *tz;
|
||||
|
||||
/* Select the right PG type to cast into. */
|
||||
char *fmt = NULL;
|
||||
switch (self->type) {
|
||||
case PSYCO_DATETIME_TIME:
|
||||
tz = PyObject_GetAttrString(self->wrapped, "tzinfo");
|
||||
if (!tz) { goto error; }
|
||||
fmt = (tz == Py_None) ? "'%s'::time" : "'%s'::timetz";
|
||||
Py_DECREF(tz);
|
||||
break;
|
||||
case PSYCO_DATETIME_DATE:
|
||||
fmt = "'%s'::date";
|
||||
break;
|
||||
case PSYCO_DATETIME_TIMESTAMP:
|
||||
tz = PyObject_GetAttrString(self->wrapped, "tzinfo");
|
||||
if (!tz) { goto error; }
|
||||
fmt = (tz == Py_None) ? "'%s'::timestamp" : "'%s'::timestamptz";
|
||||
Py_DECREF(tz);
|
||||
break;
|
||||
}
|
||||
|
||||
if (!(iso = psyco_ensure_bytes(
|
||||
PyObject_CallMethod(self->wrapped, "isoformat", NULL)))) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
rv = Bytes_FromFormat(fmt, Bytes_AsString(iso));
|
||||
|
||||
Py_DECREF(iso);
|
||||
return rv;
|
||||
|
||||
error:
|
||||
Py_XDECREF(iso);
|
||||
return rv;
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
_pydatetime_string_delta(pydatetimeObject *self)
|
||||
{
|
||||
PyDateTime_Delta *obj = (PyDateTime_Delta*)self->wrapped;
|
||||
|
||||
char buffer[8];
|
||||
int i;
|
||||
int a = PyDateTime_DELTA_GET_MICROSECONDS(obj);
|
||||
|
||||
for (i=0; i < 6 ; i++) {
|
||||
buffer[5-i] = '0' + (a % 10);
|
||||
a /= 10;
|
||||
}
|
||||
buffer[6] = '\0';
|
||||
|
||||
return Bytes_FromFormat("'%d days %d.%s seconds'::interval",
|
||||
PyDateTime_DELTA_GET_DAYS(obj),
|
||||
PyDateTime_DELTA_GET_SECONDS(obj),
|
||||
buffer);
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
pydatetime_getquoted(pydatetimeObject *self, PyObject *args)
|
||||
{
|
||||
if (self->type <= PSYCO_DATETIME_TIMESTAMP) {
|
||||
return _pydatetime_string_date_time(self);
|
||||
}
|
||||
else {
|
||||
return _pydatetime_string_delta(self);
|
||||
}
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
pydatetime_str(pydatetimeObject *self)
|
||||
{
|
||||
return psyco_ensure_text(pydatetime_getquoted(self, NULL));
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
pydatetime_conform(pydatetimeObject *self, PyObject *args)
|
||||
{
|
||||
PyObject *res, *proto;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "O", &proto)) return NULL;
|
||||
|
||||
if (proto == (PyObject*)&isqlquoteType)
|
||||
res = (PyObject*)self;
|
||||
else
|
||||
res = Py_None;
|
||||
|
||||
Py_INCREF(res);
|
||||
return res;
|
||||
}
|
||||
|
||||
/** the DateTime wrapper object **/
|
||||
|
||||
/* object member list */
|
||||
|
||||
static struct PyMemberDef pydatetimeObject_members[] = {
|
||||
{"adapted", T_OBJECT, offsetof(pydatetimeObject, wrapped), READONLY},
|
||||
{"type", T_INT, offsetof(pydatetimeObject, type), READONLY},
|
||||
{NULL}
|
||||
};
|
||||
|
||||
/* object method table */
|
||||
|
||||
static PyMethodDef pydatetimeObject_methods[] = {
|
||||
{"getquoted", (PyCFunction)pydatetime_getquoted, METH_NOARGS,
|
||||
"getquoted() -> wrapped object value as SQL date/time"},
|
||||
{"__conform__", (PyCFunction)pydatetime_conform, METH_VARARGS, NULL},
|
||||
{NULL} /* Sentinel */
|
||||
};
|
||||
|
||||
/* initialization and finalization methods */
|
||||
|
||||
static int
|
||||
pydatetime_setup(pydatetimeObject *self, PyObject *obj, int type)
|
||||
{
|
||||
Dprintf("pydatetime_setup: init datetime object at %p, refcnt = "
|
||||
FORMAT_CODE_PY_SSIZE_T,
|
||||
self, Py_REFCNT(self));
|
||||
|
||||
self->type = type;
|
||||
Py_INCREF(obj);
|
||||
self->wrapped = obj;
|
||||
|
||||
Dprintf("pydatetime_setup: good pydatetime object at %p, refcnt = "
|
||||
FORMAT_CODE_PY_SSIZE_T,
|
||||
self, Py_REFCNT(self));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
pydatetime_dealloc(PyObject* obj)
|
||||
{
|
||||
pydatetimeObject *self = (pydatetimeObject *)obj;
|
||||
|
||||
Py_CLEAR(self->wrapped);
|
||||
|
||||
Dprintf("mpydatetime_dealloc: deleted pydatetime object at %p, "
|
||||
"refcnt = " FORMAT_CODE_PY_SSIZE_T, obj, Py_REFCNT(obj));
|
||||
|
||||
Py_TYPE(obj)->tp_free(obj);
|
||||
}
|
||||
|
||||
static int
|
||||
pydatetime_init(PyObject *obj, PyObject *args, PyObject *kwds)
|
||||
{
|
||||
PyObject *dt;
|
||||
int type = -1; /* raise an error if type was not passed! */
|
||||
|
||||
if (!PyArg_ParseTuple(args, "O|i", &dt, &type))
|
||||
return -1;
|
||||
|
||||
return pydatetime_setup((pydatetimeObject *)obj, dt, type);
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
pydatetime_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
|
||||
{
|
||||
return type->tp_alloc(type, 0);
|
||||
}
|
||||
|
||||
|
||||
/* object type */
|
||||
|
||||
#define pydatetimeType_doc \
|
||||
"datetime(datetime, type) -> new datetime wrapper object"
|
||||
|
||||
PyTypeObject pydatetimeType = {
|
||||
PyVarObject_HEAD_INIT(NULL, 0)
|
||||
"psycopg2._psycopg.datetime",
|
||||
sizeof(pydatetimeObject), 0,
|
||||
pydatetime_dealloc, /*tp_dealloc*/
|
||||
0, /*tp_print*/
|
||||
0, /*tp_getattr*/
|
||||
0, /*tp_setattr*/
|
||||
0, /*tp_compare*/
|
||||
0, /*tp_repr*/
|
||||
0, /*tp_as_number*/
|
||||
0, /*tp_as_sequence*/
|
||||
0, /*tp_as_mapping*/
|
||||
0, /*tp_hash */
|
||||
0, /*tp_call*/
|
||||
(reprfunc)pydatetime_str, /*tp_str*/
|
||||
0, /*tp_getattro*/
|
||||
0, /*tp_setattro*/
|
||||
0, /*tp_as_buffer*/
|
||||
Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, /*tp_flags*/
|
||||
pydatetimeType_doc, /*tp_doc*/
|
||||
0, /*tp_traverse*/
|
||||
0, /*tp_clear*/
|
||||
0, /*tp_richcompare*/
|
||||
0, /*tp_weaklistoffset*/
|
||||
0, /*tp_iter*/
|
||||
0, /*tp_iternext*/
|
||||
pydatetimeObject_methods, /*tp_methods*/
|
||||
pydatetimeObject_members, /*tp_members*/
|
||||
0, /*tp_getset*/
|
||||
0, /*tp_base*/
|
||||
0, /*tp_dict*/
|
||||
0, /*tp_descr_get*/
|
||||
0, /*tp_descr_set*/
|
||||
0, /*tp_dictoffset*/
|
||||
pydatetime_init, /*tp_init*/
|
||||
0, /*tp_alloc*/
|
||||
pydatetime_new, /*tp_new*/
|
||||
};
|
||||
|
||||
|
||||
/** module-level functions **/
|
||||
|
||||
PyObject *
|
||||
psyco_Date(PyObject *self, PyObject *args)
|
||||
{
|
||||
PyObject *res = NULL;
|
||||
int year, month, day;
|
||||
|
||||
PyObject* obj = NULL;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "iii", &year, &month, &day))
|
||||
return NULL;
|
||||
|
||||
obj = PyObject_CallFunction((PyObject*)PyDateTimeAPI->DateType,
|
||||
"iii", year, month, day);
|
||||
|
||||
if (obj) {
|
||||
res = PyObject_CallFunction((PyObject *)&pydatetimeType,
|
||||
"Oi", obj, PSYCO_DATETIME_DATE);
|
||||
Py_DECREF(obj);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
PyObject *
|
||||
psyco_Time(PyObject *self, PyObject *args)
|
||||
{
|
||||
PyObject *res = NULL;
|
||||
PyObject *tzinfo = NULL;
|
||||
int hours, minutes=0;
|
||||
double micro, second=0.0;
|
||||
|
||||
PyObject* obj = NULL;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "iid|O", &hours, &minutes, &second,
|
||||
&tzinfo))
|
||||
return NULL;
|
||||
|
||||
micro = (second - floor(second)) * 1000000.0;
|
||||
second = floor(second);
|
||||
|
||||
if (tzinfo == NULL)
|
||||
obj = PyObject_CallFunction((PyObject*)PyDateTimeAPI->TimeType, "iiii",
|
||||
hours, minutes, (int)second, (int)round(micro));
|
||||
else
|
||||
obj = PyObject_CallFunction((PyObject*)PyDateTimeAPI->TimeType, "iiiiO",
|
||||
hours, minutes, (int)second, (int)round(micro), tzinfo);
|
||||
|
||||
if (obj) {
|
||||
res = PyObject_CallFunction((PyObject *)&pydatetimeType,
|
||||
"Oi", obj, PSYCO_DATETIME_TIME);
|
||||
Py_DECREF(obj);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
_psyco_Timestamp(int year, int month, int day,
|
||||
int hour, int minute, double second, PyObject *tzinfo)
|
||||
{
|
||||
double micro;
|
||||
PyObject *obj;
|
||||
PyObject *res = NULL;
|
||||
|
||||
micro = (second - floor(second)) * 1000000.0;
|
||||
second = floor(second);
|
||||
|
||||
if (tzinfo == NULL)
|
||||
obj = PyObject_CallFunction((PyObject*)PyDateTimeAPI->DateTimeType,
|
||||
"iiiiiii",
|
||||
year, month, day, hour, minute, (int)second,
|
||||
(int)round(micro));
|
||||
else
|
||||
obj = PyObject_CallFunction((PyObject*)PyDateTimeAPI->DateTimeType,
|
||||
"iiiiiiiO",
|
||||
year, month, day, hour, minute, (int)second,
|
||||
(int)round(micro), tzinfo);
|
||||
|
||||
if (obj) {
|
||||
res = PyObject_CallFunction((PyObject *)&pydatetimeType,
|
||||
"Oi", obj, PSYCO_DATETIME_TIMESTAMP);
|
||||
Py_DECREF(obj);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
PyObject *
|
||||
psyco_Timestamp(PyObject *self, PyObject *args)
|
||||
{
|
||||
PyObject *tzinfo = NULL;
|
||||
int year, month, day;
|
||||
int hour=0, minute=0; /* default to midnight */
|
||||
double second=0.0;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "iii|iidO", &year, &month, &day,
|
||||
&hour, &minute, &second, &tzinfo))
|
||||
return NULL;
|
||||
|
||||
return _psyco_Timestamp(year, month, day, hour, minute, second, tzinfo);
|
||||
}
|
||||
|
||||
PyObject *
|
||||
psyco_DateFromTicks(PyObject *self, PyObject *args)
|
||||
{
|
||||
PyObject *res = NULL;
|
||||
struct tm tm;
|
||||
time_t t;
|
||||
double ticks;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "d", &ticks))
|
||||
return NULL;
|
||||
|
||||
t = (time_t)floor(ticks);
|
||||
if (localtime_r(&t, &tm)) {
|
||||
args = Py_BuildValue("iii", tm.tm_year+1900, tm.tm_mon+1, tm.tm_mday);
|
||||
if (args) {
|
||||
res = psyco_Date(self, args);
|
||||
Py_DECREF(args);
|
||||
}
|
||||
}
|
||||
else {
|
||||
PyErr_SetString(InterfaceError, "failed localtime call");
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
PyObject *
|
||||
psyco_TimeFromTicks(PyObject *self, PyObject *args)
|
||||
{
|
||||
PyObject *res = NULL;
|
||||
struct tm tm;
|
||||
time_t t;
|
||||
double ticks;
|
||||
|
||||
if (!PyArg_ParseTuple(args,"d", &ticks))
|
||||
return NULL;
|
||||
|
||||
t = (time_t)floor(ticks);
|
||||
ticks -= (double)t;
|
||||
if (localtime_r(&t, &tm)) {
|
||||
args = Py_BuildValue("iid", tm.tm_hour, tm.tm_min,
|
||||
(double)tm.tm_sec + ticks);
|
||||
if (args) {
|
||||
res = psyco_Time(self, args);
|
||||
Py_DECREF(args);
|
||||
}
|
||||
}
|
||||
else {
|
||||
PyErr_SetString(InterfaceError, "failed localtime call");
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
PyObject *
|
||||
psyco_TimestampFromTicks(PyObject *self, PyObject *args)
|
||||
{
|
||||
pydatetimeObject *wrapper = NULL;
|
||||
PyObject *dt_aware = NULL;
|
||||
PyObject *res = NULL;
|
||||
struct tm tm;
|
||||
time_t t;
|
||||
double ticks;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "d", &ticks))
|
||||
return NULL;
|
||||
|
||||
t = (time_t)floor(ticks);
|
||||
ticks -= (double)t;
|
||||
if (!localtime_r(&t, &tm)) {
|
||||
PyErr_SetString(InterfaceError, "failed localtime call");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
/* Convert the tm to a wrapper containing a naive datetime.datetime */
|
||||
if (!(wrapper = (pydatetimeObject *)_psyco_Timestamp(
|
||||
tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
|
||||
tm.tm_hour, tm.tm_min, (double)tm.tm_sec + ticks, NULL))) {
|
||||
goto exit;
|
||||
}
|
||||
|
||||
/* Localize the datetime and assign it back to the wrapper */
|
||||
if (!(dt_aware = PyObject_CallMethod(
|
||||
wrapper->wrapped, "astimezone", NULL))) {
|
||||
goto exit;
|
||||
}
|
||||
Py_CLEAR(wrapper->wrapped);
|
||||
wrapper->wrapped = dt_aware;
|
||||
dt_aware = NULL;
|
||||
|
||||
/* the wrapper is ready to be returned */
|
||||
res = (PyObject *)wrapper;
|
||||
wrapper = NULL;
|
||||
|
||||
exit:
|
||||
Py_XDECREF(dt_aware);
|
||||
Py_XDECREF(wrapper);
|
||||
return res;
|
||||
}
|
||||
|
||||
PyObject *
|
||||
psyco_DateFromPy(PyObject *self, PyObject *args)
|
||||
{
|
||||
PyObject *obj;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "O!", PyDateTimeAPI->DateType, &obj))
|
||||
return NULL;
|
||||
|
||||
return PyObject_CallFunction((PyObject *)&pydatetimeType, "Oi", obj,
|
||||
PSYCO_DATETIME_DATE);
|
||||
}
|
||||
|
||||
PyObject *
|
||||
psyco_TimeFromPy(PyObject *self, PyObject *args)
|
||||
{
|
||||
PyObject *obj;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "O!", PyDateTimeAPI->TimeType, &obj))
|
||||
return NULL;
|
||||
|
||||
return PyObject_CallFunction((PyObject *)&pydatetimeType, "Oi", obj,
|
||||
PSYCO_DATETIME_TIME);
|
||||
}
|
||||
|
||||
PyObject *
|
||||
psyco_TimestampFromPy(PyObject *self, PyObject *args)
|
||||
{
|
||||
PyObject *obj;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "O!", PyDateTimeAPI->DateTimeType, &obj))
|
||||
return NULL;
|
||||
|
||||
return PyObject_CallFunction((PyObject *)&pydatetimeType, "Oi", obj,
|
||||
PSYCO_DATETIME_TIMESTAMP);
|
||||
}
|
||||
|
||||
PyObject *
|
||||
psyco_IntervalFromPy(PyObject *self, PyObject *args)
|
||||
{
|
||||
PyObject *obj;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "O!", PyDateTimeAPI->DeltaType, &obj))
|
||||
return NULL;
|
||||
|
||||
return PyObject_CallFunction((PyObject *)&pydatetimeType, "Oi", obj,
|
||||
PSYCO_DATETIME_INTERVAL);
|
||||
}
|
||||
107
psycopg/adapter_datetime.h
Normal file
107
psycopg/adapter_datetime.h
Normal file
@ -0,0 +1,107 @@
|
||||
/* adapter_datetime.h - definition for the python date/time types
|
||||
*
|
||||
* Copyright (C) 2003-2019 Federico Di Gregorio <fog@debian.org>
|
||||
* Copyright (C) 2020-2021 The Psycopg Team
|
||||
*
|
||||
* This file is part of psycopg.
|
||||
*
|
||||
* psycopg2 is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give
|
||||
* permission to link this program with the OpenSSL library (or with
|
||||
* modified versions of OpenSSL that use the same license as OpenSSL),
|
||||
* and distribute linked combinations including the two.
|
||||
*
|
||||
* You must obey the GNU Lesser General Public License in all respects for
|
||||
* all of the code used other than OpenSSL.
|
||||
*
|
||||
* psycopg2 is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
|
||||
* License for more details.
|
||||
*/
|
||||
|
||||
#ifndef PSYCOPG_DATETIME_H
|
||||
#define PSYCOPG_DATETIME_H 1
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
extern HIDDEN PyTypeObject pydatetimeType;
|
||||
|
||||
typedef struct {
|
||||
PyObject_HEAD
|
||||
|
||||
PyObject *wrapped;
|
||||
int type;
|
||||
#define PSYCO_DATETIME_TIME 0
|
||||
#define PSYCO_DATETIME_DATE 1
|
||||
#define PSYCO_DATETIME_TIMESTAMP 2
|
||||
#define PSYCO_DATETIME_INTERVAL 3
|
||||
|
||||
} pydatetimeObject;
|
||||
|
||||
|
||||
RAISES_NEG HIDDEN int adapter_datetime_init(void);
|
||||
|
||||
HIDDEN PyObject *psyco_Date(PyObject *module, PyObject *args);
|
||||
#define psyco_Date_doc \
|
||||
"Date(year, month, day) -> new date\n\n" \
|
||||
"Build an object holding a date value."
|
||||
|
||||
HIDDEN PyObject *psyco_Time(PyObject *module, PyObject *args);
|
||||
#define psyco_Time_doc \
|
||||
"Time(hour, minutes, seconds, tzinfo=None) -> new time\n\n" \
|
||||
"Build an object holding a time value."
|
||||
|
||||
HIDDEN PyObject *psyco_Timestamp(PyObject *module, PyObject *args);
|
||||
#define psyco_Timestamp_doc \
|
||||
"Timestamp(year, month, day, hour, minutes, seconds, tzinfo=None) -> new timestamp\n\n" \
|
||||
"Build an object holding a timestamp value."
|
||||
|
||||
HIDDEN PyObject *psyco_DateFromTicks(PyObject *module, PyObject *args);
|
||||
#define psyco_DateFromTicks_doc \
|
||||
"DateFromTicks(ticks) -> new date\n\n" \
|
||||
"Build an object holding a date value from the given ticks value.\n\n" \
|
||||
"Ticks are the number of seconds since the epoch; see the documentation " \
|
||||
"of the standard Python time module for details)."
|
||||
|
||||
HIDDEN PyObject *psyco_TimeFromTicks(PyObject *module, PyObject *args);
|
||||
#define psyco_TimeFromTicks_doc \
|
||||
"TimeFromTicks(ticks) -> new time\n\n" \
|
||||
"Build an object holding a time value from the given ticks value.\n\n" \
|
||||
"Ticks are the number of seconds since the epoch; see the documentation " \
|
||||
"of the standard Python time module for details)."
|
||||
|
||||
HIDDEN PyObject *psyco_TimestampFromTicks(PyObject *module, PyObject *args);
|
||||
#define psyco_TimestampFromTicks_doc \
|
||||
"TimestampFromTicks(ticks) -> new timestamp\n\n" \
|
||||
"Build an object holding a timestamp value from the given ticks value.\n\n" \
|
||||
"Ticks are the number of seconds since the epoch; see the documentation " \
|
||||
"of the standard Python time module for details)."
|
||||
|
||||
HIDDEN PyObject *psyco_DateFromPy(PyObject *module, PyObject *args);
|
||||
#define psyco_DateFromPy_doc \
|
||||
"DateFromPy(datetime.date) -> new wrapper"
|
||||
|
||||
HIDDEN PyObject *psyco_TimeFromPy(PyObject *module, PyObject *args);
|
||||
#define psyco_TimeFromPy_doc \
|
||||
"TimeFromPy(datetime.time) -> new wrapper"
|
||||
|
||||
HIDDEN PyObject *psyco_TimestampFromPy(PyObject *module, PyObject *args);
|
||||
#define psyco_TimestampFromPy_doc \
|
||||
"TimestampFromPy(datetime.datetime) -> new wrapper"
|
||||
|
||||
HIDDEN PyObject *psyco_IntervalFromPy(PyObject *module, PyObject *args);
|
||||
#define psyco_IntervalFromPy_doc \
|
||||
"IntervalFromPy(datetime.timedelta) -> new wrapper"
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* !defined(PSYCOPG_DATETIME_H) */
|
||||
342
psycopg/adapter_list.c
Normal file
342
psycopg/adapter_list.c
Normal file
@ -0,0 +1,342 @@
|
||||
/* adapter_list.c - python list objects
|
||||
*
|
||||
* Copyright (C) 2004-2019 Federico Di Gregorio <fog@debian.org>
|
||||
* Copyright (C) 2020-2021 The Psycopg Team
|
||||
*
|
||||
* This file is part of psycopg.
|
||||
*
|
||||
* psycopg2 is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give
|
||||
* permission to link this program with the OpenSSL library (or with
|
||||
* modified versions of OpenSSL that use the same license as OpenSSL),
|
||||
* and distribute linked combinations including the two.
|
||||
*
|
||||
* You must obey the GNU Lesser General Public License in all respects for
|
||||
* all of the code used other than OpenSSL.
|
||||
*
|
||||
* psycopg2 is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
|
||||
* License for more details.
|
||||
*/
|
||||
|
||||
#define PSYCOPG_MODULE
|
||||
#include "psycopg/psycopg.h"
|
||||
|
||||
#include "psycopg/adapter_list.h"
|
||||
#include "psycopg/microprotocols.h"
|
||||
#include "psycopg/microprotocols_proto.h"
|
||||
|
||||
|
||||
/* list_str, list_getquoted - return result of quoting */
|
||||
|
||||
static PyObject *
|
||||
list_quote(listObject *self)
|
||||
{
|
||||
/* adapt the list by calling adapt() recursively and then wrapping
|
||||
everything into "ARRAY[]" */
|
||||
PyObject *res = NULL;
|
||||
PyObject **qs = NULL;
|
||||
Py_ssize_t bufsize = 0;
|
||||
char *buf = NULL, *ptr;
|
||||
|
||||
/* list consisting of only NULL don't work with the ARRAY[] construct
|
||||
* so we use the {NULL,...} syntax. The same syntax is also necessary
|
||||
* to convert array of arrays containing only nulls. */
|
||||
int all_nulls = 1;
|
||||
|
||||
Py_ssize_t i, len;
|
||||
|
||||
len = PyList_GET_SIZE(self->wrapped);
|
||||
|
||||
/* empty arrays are converted to NULLs (still searching for a way to
|
||||
insert an empty array in postgresql */
|
||||
if (len == 0) {
|
||||
/* it cannot be ARRAY[] because it would make empty lists unusable
|
||||
* in any() without a cast. But we may convert it into ARRAY[] below */
|
||||
res = Bytes_FromString("'{}'");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (!(qs = PyMem_New(PyObject *, len))) {
|
||||
PyErr_NoMemory();
|
||||
goto exit;
|
||||
}
|
||||
memset(qs, 0, len * sizeof(PyObject *));
|
||||
|
||||
for (i = 0; i < len; i++) {
|
||||
PyObject *wrapped = PyList_GET_ITEM(self->wrapped, i);
|
||||
if (wrapped == Py_None) {
|
||||
Py_INCREF(psyco_null);
|
||||
qs[i] = psyco_null;
|
||||
}
|
||||
else {
|
||||
if (!(qs[i] = microprotocol_getquoted(
|
||||
wrapped, (connectionObject*)self->connection))) {
|
||||
goto exit;
|
||||
}
|
||||
|
||||
/* Lists of arrays containing only nulls are also not supported
|
||||
* by the ARRAY construct so we should do some special casing */
|
||||
if (PyList_Check(wrapped)) {
|
||||
if (Bytes_AS_STRING(qs[i])[0] == 'A') {
|
||||
all_nulls = 0;
|
||||
}
|
||||
else if (0 == strcmp(Bytes_AS_STRING(qs[i]), "'{}'")) {
|
||||
/* case of issue #788: '{{}}' is not supported but
|
||||
* array[array[]] is */
|
||||
all_nulls = 0;
|
||||
Py_CLEAR(qs[i]);
|
||||
if (!(qs[i] = Bytes_FromString("ARRAY[]"))) {
|
||||
goto exit;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
all_nulls = 0;
|
||||
}
|
||||
}
|
||||
bufsize += Bytes_GET_SIZE(qs[i]) + 1; /* this, and a comma */
|
||||
}
|
||||
|
||||
/* Create an array literal, usually ARRAY[...] but if the contents are
|
||||
* all NULL or array of NULL we must use the '{...}' syntax
|
||||
*/
|
||||
if (!(ptr = buf = PyMem_Malloc(bufsize + 8))) {
|
||||
PyErr_NoMemory();
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (!all_nulls) {
|
||||
strcpy(ptr, "ARRAY[");
|
||||
ptr += 6;
|
||||
for (i = 0; i < len; i++) {
|
||||
Py_ssize_t sl;
|
||||
sl = Bytes_GET_SIZE(qs[i]);
|
||||
memcpy(ptr, Bytes_AS_STRING(qs[i]), sl);
|
||||
ptr += sl;
|
||||
*ptr++ = ',';
|
||||
}
|
||||
*(ptr - 1) = ']';
|
||||
}
|
||||
else {
|
||||
*ptr++ = '\'';
|
||||
*ptr++ = '{';
|
||||
for (i = 0; i < len; i++) {
|
||||
/* in case all the adapted things are nulls (or array of nulls),
|
||||
* the quoted string is either NULL or an array of the form
|
||||
* '{NULL,...}', in which case we have to strip the extra quotes */
|
||||
char *s;
|
||||
Py_ssize_t sl;
|
||||
s = Bytes_AS_STRING(qs[i]);
|
||||
sl = Bytes_GET_SIZE(qs[i]);
|
||||
if (s[0] != '\'') {
|
||||
memcpy(ptr, s, sl);
|
||||
ptr += sl;
|
||||
}
|
||||
else {
|
||||
memcpy(ptr, s + 1, sl - 2);
|
||||
ptr += sl - 2;
|
||||
}
|
||||
*ptr++ = ',';
|
||||
}
|
||||
*(ptr - 1) = '}';
|
||||
*ptr++ = '\'';
|
||||
}
|
||||
|
||||
res = Bytes_FromStringAndSize(buf, ptr - buf);
|
||||
|
||||
exit:
|
||||
if (qs) {
|
||||
for (i = 0; i < len; i++) {
|
||||
PyObject *q = qs[i];
|
||||
Py_XDECREF(q);
|
||||
}
|
||||
PyMem_Free(qs);
|
||||
}
|
||||
PyMem_Free(buf);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
list_str(listObject *self)
|
||||
{
|
||||
return psyco_ensure_text(list_quote(self));
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
list_getquoted(listObject *self, PyObject *args)
|
||||
{
|
||||
return list_quote(self);
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
list_prepare(listObject *self, PyObject *args)
|
||||
{
|
||||
PyObject *conn;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "O!", &connectionType, &conn))
|
||||
return NULL;
|
||||
|
||||
Py_CLEAR(self->connection);
|
||||
Py_INCREF(conn);
|
||||
self->connection = conn;
|
||||
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
list_conform(listObject *self, PyObject *args)
|
||||
{
|
||||
PyObject *res, *proto;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "O", &proto)) return NULL;
|
||||
|
||||
if (proto == (PyObject*)&isqlquoteType)
|
||||
res = (PyObject*)self;
|
||||
else
|
||||
res = Py_None;
|
||||
|
||||
Py_INCREF(res);
|
||||
return res;
|
||||
}
|
||||
|
||||
/** the DateTime wrapper object **/
|
||||
|
||||
/* object member list */
|
||||
|
||||
static struct PyMemberDef listObject_members[] = {
|
||||
{"adapted", T_OBJECT, offsetof(listObject, wrapped), READONLY},
|
||||
{NULL}
|
||||
};
|
||||
|
||||
/* object method table */
|
||||
|
||||
static PyMethodDef listObject_methods[] = {
|
||||
{"getquoted", (PyCFunction)list_getquoted, METH_NOARGS,
|
||||
"getquoted() -> wrapped object value as SQL date/time"},
|
||||
{"prepare", (PyCFunction)list_prepare, METH_VARARGS,
|
||||
"prepare(conn) -> set encoding to conn->encoding"},
|
||||
{"__conform__", (PyCFunction)list_conform, METH_VARARGS, NULL},
|
||||
{NULL} /* Sentinel */
|
||||
};
|
||||
|
||||
/* initialization and finalization methods */
|
||||
|
||||
static int
|
||||
list_setup(listObject *self, PyObject *obj)
|
||||
{
|
||||
Dprintf("list_setup: init list object at %p, refcnt = "
|
||||
FORMAT_CODE_PY_SSIZE_T,
|
||||
self, Py_REFCNT(self)
|
||||
);
|
||||
|
||||
if (!PyList_Check(obj))
|
||||
return -1;
|
||||
|
||||
self->connection = NULL;
|
||||
Py_INCREF(obj);
|
||||
self->wrapped = obj;
|
||||
|
||||
Dprintf("list_setup: good list object at %p, refcnt = "
|
||||
FORMAT_CODE_PY_SSIZE_T,
|
||||
self, Py_REFCNT(self)
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
list_traverse(listObject *self, visitproc visit, void *arg)
|
||||
{
|
||||
Py_VISIT(self->wrapped);
|
||||
Py_VISIT(self->connection);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
list_clear(listObject *self)
|
||||
{
|
||||
Py_CLEAR(self->wrapped);
|
||||
Py_CLEAR(self->connection);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
list_dealloc(listObject* self)
|
||||
{
|
||||
PyObject_GC_UnTrack((PyObject *)self);
|
||||
list_clear(self);
|
||||
|
||||
Dprintf("list_dealloc: deleted list object at %p, "
|
||||
"refcnt = " FORMAT_CODE_PY_SSIZE_T, self, Py_REFCNT(self));
|
||||
|
||||
Py_TYPE(self)->tp_free((PyObject *)self);
|
||||
}
|
||||
|
||||
static int
|
||||
list_init(PyObject *obj, PyObject *args, PyObject *kwds)
|
||||
{
|
||||
PyObject *l;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "O", &l))
|
||||
return -1;
|
||||
|
||||
return list_setup((listObject *)obj, l);
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
list_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
|
||||
{
|
||||
return type->tp_alloc(type, 0);
|
||||
}
|
||||
|
||||
|
||||
/* object type */
|
||||
|
||||
#define listType_doc \
|
||||
"List(list) -> new list wrapper object"
|
||||
|
||||
PyTypeObject listType = {
|
||||
PyVarObject_HEAD_INIT(NULL, 0)
|
||||
"psycopg2._psycopg.List",
|
||||
sizeof(listObject), 0,
|
||||
(destructor)list_dealloc, /*tp_dealloc*/
|
||||
0, /*tp_print*/
|
||||
0, /*tp_getattr*/
|
||||
0, /*tp_setattr*/
|
||||
0, /*tp_compare*/
|
||||
0, /*tp_repr*/
|
||||
0, /*tp_as_number*/
|
||||
0, /*tp_as_sequence*/
|
||||
0, /*tp_as_mapping*/
|
||||
0, /*tp_hash */
|
||||
0, /*tp_call*/
|
||||
(reprfunc)list_str, /*tp_str*/
|
||||
0, /*tp_getattro*/
|
||||
0, /*tp_setattro*/
|
||||
0, /*tp_as_buffer*/
|
||||
Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE|Py_TPFLAGS_HAVE_GC, /*tp_flags*/
|
||||
listType_doc, /*tp_doc*/
|
||||
(traverseproc)list_traverse, /*tp_traverse*/
|
||||
(inquiry)list_clear, /*tp_clear*/
|
||||
0, /*tp_richcompare*/
|
||||
0, /*tp_weaklistoffset*/
|
||||
0, /*tp_iter*/
|
||||
0, /*tp_iternext*/
|
||||
listObject_methods, /*tp_methods*/
|
||||
listObject_members, /*tp_members*/
|
||||
0, /*tp_getset*/
|
||||
0, /*tp_base*/
|
||||
0, /*tp_dict*/
|
||||
0, /*tp_descr_get*/
|
||||
0, /*tp_descr_set*/
|
||||
0, /*tp_dictoffset*/
|
||||
list_init, /*tp_init*/
|
||||
0, /*tp_alloc*/
|
||||
list_new, /*tp_new*/
|
||||
};
|
||||
47
psycopg/adapter_list.h
Normal file
47
psycopg/adapter_list.h
Normal file
@ -0,0 +1,47 @@
|
||||
/* adapter_list.h - definition for the python list types
|
||||
*
|
||||
* Copyright (C) 2004-2019 Federico Di Gregorio <fog@debian.org>
|
||||
* Copyright (C) 2020-2021 The Psycopg Team
|
||||
*
|
||||
* This file is part of psycopg.
|
||||
*
|
||||
* psycopg2 is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give
|
||||
* permission to link this program with the OpenSSL library (or with
|
||||
* modified versions of OpenSSL that use the same license as OpenSSL),
|
||||
* and distribute linked combinations including the two.
|
||||
*
|
||||
* You must obey the GNU Lesser General Public License in all respects for
|
||||
* all of the code used other than OpenSSL.
|
||||
*
|
||||
* psycopg2 is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
|
||||
* License for more details.
|
||||
*/
|
||||
|
||||
#ifndef PSYCOPG_LIST_H
|
||||
#define PSYCOPG_LIST_H 1
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
extern HIDDEN PyTypeObject listType;
|
||||
|
||||
typedef struct {
|
||||
PyObject_HEAD
|
||||
|
||||
PyObject *wrapped;
|
||||
PyObject *connection;
|
||||
} listObject;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* !defined(PSYCOPG_LIST_H) */
|
||||
185
psycopg/adapter_pboolean.c
Normal file
185
psycopg/adapter_pboolean.c
Normal file
@ -0,0 +1,185 @@
|
||||
/* adapter_pboolean.c - psycopg boolean type wrapper implementation
|
||||
*
|
||||
* Copyright (C) 2003-2019 Federico Di Gregorio <fog@debian.org>
|
||||
* Copyright (C) 2020-2021 The Psycopg Team
|
||||
*
|
||||
* This file is part of psycopg.
|
||||
*
|
||||
* psycopg2 is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give
|
||||
* permission to link this program with the OpenSSL library (or with
|
||||
* modified versions of OpenSSL that use the same license as OpenSSL),
|
||||
* and distribute linked combinations including the two.
|
||||
*
|
||||
* You must obey the GNU Lesser General Public License in all respects for
|
||||
* all of the code used other than OpenSSL.
|
||||
*
|
||||
* psycopg2 is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
|
||||
* License for more details.
|
||||
*/
|
||||
|
||||
#define PSYCOPG_MODULE
|
||||
#include "psycopg/psycopg.h"
|
||||
|
||||
#include "psycopg/adapter_pboolean.h"
|
||||
#include "psycopg/microprotocols_proto.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
|
||||
/** the Boolean object **/
|
||||
|
||||
static PyObject *
|
||||
pboolean_getquoted(pbooleanObject *self, PyObject *args)
|
||||
{
|
||||
if (PyObject_IsTrue(self->wrapped)) {
|
||||
return Bytes_FromString("true");
|
||||
}
|
||||
else {
|
||||
return Bytes_FromString("false");
|
||||
}
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
pboolean_str(pbooleanObject *self)
|
||||
{
|
||||
return psyco_ensure_text(pboolean_getquoted(self, NULL));
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
pboolean_conform(pbooleanObject *self, PyObject *args)
|
||||
{
|
||||
PyObject *res, *proto;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "O", &proto)) return NULL;
|
||||
|
||||
if (proto == (PyObject*)&isqlquoteType)
|
||||
res = (PyObject*)self;
|
||||
else
|
||||
res = Py_None;
|
||||
|
||||
Py_INCREF(res);
|
||||
return res;
|
||||
}
|
||||
|
||||
/** the Boolean object */
|
||||
|
||||
/* object member list */
|
||||
|
||||
static struct PyMemberDef pbooleanObject_members[] = {
|
||||
{"adapted", T_OBJECT, offsetof(pbooleanObject, wrapped), READONLY},
|
||||
{NULL}
|
||||
};
|
||||
|
||||
/* object method table */
|
||||
|
||||
static PyMethodDef pbooleanObject_methods[] = {
|
||||
{"getquoted", (PyCFunction)pboolean_getquoted, METH_NOARGS,
|
||||
"getquoted() -> wrapped object value as SQL-quoted string"},
|
||||
{"__conform__", (PyCFunction)pboolean_conform, METH_VARARGS, NULL},
|
||||
{NULL} /* Sentinel */
|
||||
};
|
||||
|
||||
/* initialization and finalization methods */
|
||||
|
||||
static int
|
||||
pboolean_setup(pbooleanObject *self, PyObject *obj)
|
||||
{
|
||||
Dprintf("pboolean_setup: init pboolean object at %p, refcnt = "
|
||||
FORMAT_CODE_PY_SSIZE_T,
|
||||
self, Py_REFCNT(self)
|
||||
);
|
||||
|
||||
Py_INCREF(obj);
|
||||
self->wrapped = obj;
|
||||
|
||||
Dprintf("pboolean_setup: good pboolean object at %p, refcnt = "
|
||||
FORMAT_CODE_PY_SSIZE_T,
|
||||
self, Py_REFCNT(self)
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
pboolean_dealloc(PyObject* obj)
|
||||
{
|
||||
pbooleanObject *self = (pbooleanObject *)obj;
|
||||
|
||||
Py_CLEAR(self->wrapped);
|
||||
|
||||
Dprintf("pboolean_dealloc: deleted pboolean object at %p, refcnt = "
|
||||
FORMAT_CODE_PY_SSIZE_T,
|
||||
obj, Py_REFCNT(obj)
|
||||
);
|
||||
|
||||
Py_TYPE(obj)->tp_free(obj);
|
||||
}
|
||||
|
||||
static int
|
||||
pboolean_init(PyObject *obj, PyObject *args, PyObject *kwds)
|
||||
{
|
||||
PyObject *o;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "O", &o))
|
||||
return -1;
|
||||
|
||||
return pboolean_setup((pbooleanObject *)obj, o);
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
pboolean_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
|
||||
{
|
||||
return type->tp_alloc(type, 0);
|
||||
}
|
||||
|
||||
|
||||
/* object type */
|
||||
|
||||
#define pbooleanType_doc \
|
||||
"Boolean(str) -> new Boolean adapter object"
|
||||
|
||||
PyTypeObject pbooleanType = {
|
||||
PyVarObject_HEAD_INIT(NULL, 0)
|
||||
"psycopg2.extensions.Boolean",
|
||||
sizeof(pbooleanObject), 0,
|
||||
pboolean_dealloc, /*tp_dealloc*/
|
||||
0, /*tp_print*/
|
||||
0, /*tp_getattr*/
|
||||
0, /*tp_setattr*/
|
||||
0, /*tp_compare*/
|
||||
0, /*tp_repr*/
|
||||
0, /*tp_as_number*/
|
||||
0, /*tp_as_sequence*/
|
||||
0, /*tp_as_mapping*/
|
||||
0, /*tp_hash */
|
||||
0, /*tp_call*/
|
||||
(reprfunc)pboolean_str, /*tp_str*/
|
||||
0, /*tp_getattro*/
|
||||
0, /*tp_setattro*/
|
||||
0, /*tp_as_buffer*/
|
||||
Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, /*tp_flags*/
|
||||
pbooleanType_doc, /*tp_doc*/
|
||||
0, /*tp_traverse*/
|
||||
0, /*tp_clear*/
|
||||
0, /*tp_richcompare*/
|
||||
0, /*tp_weaklistoffset*/
|
||||
0, /*tp_iter*/
|
||||
0, /*tp_iternext*/
|
||||
pbooleanObject_methods, /*tp_methods*/
|
||||
pbooleanObject_members, /*tp_members*/
|
||||
0, /*tp_getset*/
|
||||
0, /*tp_base*/
|
||||
0, /*tp_dict*/
|
||||
0, /*tp_descr_get*/
|
||||
0, /*tp_descr_set*/
|
||||
0, /*tp_dictoffset*/
|
||||
pboolean_init, /*tp_init*/
|
||||
0, /*tp_alloc*/
|
||||
pboolean_new, /*tp_new*/
|
||||
};
|
||||
48
psycopg/adapter_pboolean.h
Normal file
48
psycopg/adapter_pboolean.h
Normal file
@ -0,0 +1,48 @@
|
||||
/* adapter_pboolean.h - definition for the psycopg boolean type wrapper
|
||||
*
|
||||
* Copyright (C) 2003-2019 Federico Di Gregorio <fog@debian.org>
|
||||
* Copyright (C) 2020-2021 The Psycopg Team
|
||||
*
|
||||
* This file is part of psycopg.
|
||||
*
|
||||
* psycopg2 is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give
|
||||
* permission to link this program with the OpenSSL library (or with
|
||||
* modified versions of OpenSSL that use the same license as OpenSSL),
|
||||
* and distribute linked combinations including the two.
|
||||
*
|
||||
* You must obey the GNU Lesser General Public License in all respects for
|
||||
* all of the code used other than OpenSSL.
|
||||
*
|
||||
* psycopg2 is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
|
||||
* License for more details.
|
||||
*/
|
||||
|
||||
#ifndef PSYCOPG_PBOOLEAN_H
|
||||
#define PSYCOPG_PBOOLEAN_H 1
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
extern HIDDEN PyTypeObject pbooleanType;
|
||||
|
||||
typedef struct {
|
||||
PyObject_HEAD
|
||||
|
||||
/* this is the real object we wrap */
|
||||
PyObject *wrapped;
|
||||
|
||||
} pbooleanObject;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* !defined(PSYCOPG_PBOOLEAN_H) */
|
||||
248
psycopg/adapter_pdecimal.c
Normal file
248
psycopg/adapter_pdecimal.c
Normal file
@ -0,0 +1,248 @@
|
||||
/* adapter_pdecimal.c - psycopg Decimal type wrapper implementation
|
||||
*
|
||||
* Copyright (C) 2003-2019 Federico Di Gregorio <fog@debian.org>
|
||||
* Copyright (C) 2020-2021 The Psycopg Team
|
||||
*
|
||||
* This file is part of psycopg.
|
||||
*
|
||||
* psycopg2 is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give
|
||||
* permission to link this program with the OpenSSL library (or with
|
||||
* modified versions of OpenSSL that use the same license as OpenSSL),
|
||||
* and distribute linked combinations including the two.
|
||||
*
|
||||
* You must obey the GNU Lesser General Public License in all respects for
|
||||
* all of the code used other than OpenSSL.
|
||||
*
|
||||
* psycopg2 is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
|
||||
* License for more details.
|
||||
*/
|
||||
|
||||
#define PSYCOPG_MODULE
|
||||
#include "psycopg/psycopg.h"
|
||||
|
||||
#include "psycopg/adapter_pdecimal.h"
|
||||
#include "psycopg/microprotocols_proto.h"
|
||||
|
||||
#include <floatobject.h>
|
||||
#include <math.h>
|
||||
|
||||
|
||||
/** the Decimal object **/
|
||||
|
||||
static PyObject *
|
||||
pdecimal_getquoted(pdecimalObject *self, PyObject *args)
|
||||
{
|
||||
PyObject *check, *res = NULL;
|
||||
check = PyObject_CallMethod(self->wrapped, "is_finite", NULL);
|
||||
if (check == Py_True) {
|
||||
if (!(res = PyObject_Str(self->wrapped))) {
|
||||
goto end;
|
||||
}
|
||||
goto output;
|
||||
}
|
||||
else if (check) {
|
||||
res = Bytes_FromString("'NaN'::numeric");
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* is_finite() was introduced 2.5.1 < somewhere <= 2.5.4.
|
||||
* We assume we are here because we didn't find the method. */
|
||||
PyErr_Clear();
|
||||
|
||||
if (!(check = PyObject_CallMethod(self->wrapped, "_isnan", NULL))) {
|
||||
goto end;
|
||||
}
|
||||
if (PyObject_IsTrue(check)) {
|
||||
res = Bytes_FromString("'NaN'::numeric");
|
||||
goto end;
|
||||
}
|
||||
|
||||
Py_DECREF(check);
|
||||
if (!(check = PyObject_CallMethod(self->wrapped, "_isinfinity", NULL))) {
|
||||
goto end;
|
||||
}
|
||||
if (PyObject_IsTrue(check)) {
|
||||
res = Bytes_FromString("'NaN'::numeric");
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* wrapped is finite */
|
||||
if (!(res = PyObject_Str(self->wrapped))) {
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* res may be unicode and may suffer for issue #57 */
|
||||
output:
|
||||
|
||||
/* unicode to bytes */
|
||||
{
|
||||
PyObject *tmp = PyUnicode_AsUTF8String(res);
|
||||
Py_DECREF(res);
|
||||
if (!(res = tmp)) {
|
||||
goto end;
|
||||
}
|
||||
}
|
||||
|
||||
if ('-' == Bytes_AS_STRING(res)[0]) {
|
||||
/* Prepend a space in front of negative numbers (ticket #57) */
|
||||
PyObject *tmp;
|
||||
if (!(tmp = Bytes_FromString(" "))) {
|
||||
Py_DECREF(res);
|
||||
res = NULL;
|
||||
goto end;
|
||||
}
|
||||
Bytes_ConcatAndDel(&tmp, res);
|
||||
if (!(res = tmp)) {
|
||||
goto end;
|
||||
}
|
||||
}
|
||||
|
||||
end:
|
||||
Py_XDECREF(check);
|
||||
return res;
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
pdecimal_str(pdecimalObject *self)
|
||||
{
|
||||
return psyco_ensure_text(pdecimal_getquoted(self, NULL));
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
pdecimal_conform(pdecimalObject *self, PyObject *args)
|
||||
{
|
||||
PyObject *res, *proto;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "O", &proto)) return NULL;
|
||||
|
||||
if (proto == (PyObject*)&isqlquoteType)
|
||||
res = (PyObject*)self;
|
||||
else
|
||||
res = Py_None;
|
||||
|
||||
Py_INCREF(res);
|
||||
return res;
|
||||
}
|
||||
|
||||
/** the Decimal object */
|
||||
|
||||
/* object member list */
|
||||
|
||||
static struct PyMemberDef pdecimalObject_members[] = {
|
||||
{"adapted", T_OBJECT, offsetof(pdecimalObject, wrapped), READONLY},
|
||||
{NULL}
|
||||
};
|
||||
|
||||
/* object method table */
|
||||
|
||||
static PyMethodDef pdecimalObject_methods[] = {
|
||||
{"getquoted", (PyCFunction)pdecimal_getquoted, METH_NOARGS,
|
||||
"getquoted() -> wrapped object value as SQL-quoted string"},
|
||||
{"__conform__", (PyCFunction)pdecimal_conform, METH_VARARGS, NULL},
|
||||
{NULL} /* Sentinel */
|
||||
};
|
||||
|
||||
/* initialization and finalization methods */
|
||||
|
||||
static int
|
||||
pdecimal_setup(pdecimalObject *self, PyObject *obj)
|
||||
{
|
||||
Dprintf("pdecimal_setup: init pdecimal object at %p, refcnt = "
|
||||
FORMAT_CODE_PY_SSIZE_T,
|
||||
self, Py_REFCNT(self)
|
||||
);
|
||||
|
||||
Py_INCREF(obj);
|
||||
self->wrapped = obj;
|
||||
|
||||
Dprintf("pdecimal_setup: good pdecimal object at %p, refcnt = "
|
||||
FORMAT_CODE_PY_SSIZE_T,
|
||||
self, Py_REFCNT(self)
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
pdecimal_dealloc(PyObject* obj)
|
||||
{
|
||||
pdecimalObject *self = (pdecimalObject *)obj;
|
||||
|
||||
Py_CLEAR(self->wrapped);
|
||||
|
||||
Dprintf("pdecimal_dealloc: deleted pdecimal object at %p, refcnt = "
|
||||
FORMAT_CODE_PY_SSIZE_T,
|
||||
obj, Py_REFCNT(obj)
|
||||
);
|
||||
|
||||
Py_TYPE(obj)->tp_free(obj);
|
||||
}
|
||||
|
||||
static int
|
||||
pdecimal_init(PyObject *obj, PyObject *args, PyObject *kwds)
|
||||
{
|
||||
PyObject *o;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "O", &o))
|
||||
return -1;
|
||||
|
||||
return pdecimal_setup((pdecimalObject *)obj, o);
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
pdecimal_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
|
||||
{
|
||||
return type->tp_alloc(type, 0);
|
||||
}
|
||||
|
||||
|
||||
/* object type */
|
||||
|
||||
#define pdecimalType_doc \
|
||||
"Decimal(str) -> new Decimal adapter object"
|
||||
|
||||
PyTypeObject pdecimalType = {
|
||||
PyVarObject_HEAD_INIT(NULL, 0)
|
||||
"psycopg2._psycopg.Decimal",
|
||||
sizeof(pdecimalObject), 0,
|
||||
pdecimal_dealloc, /*tp_dealloc*/
|
||||
0, /*tp_print*/
|
||||
0, /*tp_getattr*/
|
||||
0, /*tp_setattr*/
|
||||
0, /*tp_compare*/
|
||||
0, /*tp_repr*/
|
||||
0, /*tp_as_number*/
|
||||
0, /*tp_as_sequence*/
|
||||
0, /*tp_as_mapping*/
|
||||
0, /*tp_hash */
|
||||
0, /*tp_call*/
|
||||
(reprfunc)pdecimal_str, /*tp_str*/
|
||||
0, /*tp_getattro*/
|
||||
0, /*tp_setattro*/
|
||||
0, /*tp_as_buffer*/
|
||||
Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, /*tp_flags*/
|
||||
pdecimalType_doc, /*tp_doc*/
|
||||
0, /*tp_traverse*/
|
||||
0, /*tp_clear*/
|
||||
0, /*tp_richcompare*/
|
||||
0, /*tp_weaklistoffset*/
|
||||
0, /*tp_iter*/
|
||||
0, /*tp_iternext*/
|
||||
pdecimalObject_methods, /*tp_methods*/
|
||||
pdecimalObject_members, /*tp_members*/
|
||||
0, /*tp_getset*/
|
||||
0, /*tp_base*/
|
||||
0, /*tp_dict*/
|
||||
0, /*tp_descr_get*/
|
||||
0, /*tp_descr_set*/
|
||||
0, /*tp_dictoffset*/
|
||||
pdecimal_init, /*tp_init*/
|
||||
0, /*tp_alloc*/
|
||||
pdecimal_new, /*tp_new*/
|
||||
};
|
||||
48
psycopg/adapter_pdecimal.h
Normal file
48
psycopg/adapter_pdecimal.h
Normal file
@ -0,0 +1,48 @@
|
||||
/* adapter_pdecimal.h - definition for the psycopg Decimal type wrapper
|
||||
*
|
||||
* Copyright (C) 2003-2019 Federico Di Gregorio <fog@debian.org>
|
||||
* Copyright (C) 2020-2021 The Psycopg Team
|
||||
*
|
||||
* This file is part of psycopg.
|
||||
*
|
||||
* psycopg2 is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give
|
||||
* permission to link this program with the OpenSSL library (or with
|
||||
* modified versions of OpenSSL that use the same license as OpenSSL),
|
||||
* and distribute linked combinations including the two.
|
||||
*
|
||||
* You must obey the GNU Lesser General Public License in all respects for
|
||||
* all of the code used other than OpenSSL.
|
||||
*
|
||||
* psycopg2 is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
|
||||
* License for more details.
|
||||
*/
|
||||
|
||||
#ifndef PSYCOPG_PDECIMAL_H
|
||||
#define PSYCOPG_PDECIMAL_H 1
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
extern HIDDEN PyTypeObject pdecimalType;
|
||||
|
||||
typedef struct {
|
||||
PyObject_HEAD
|
||||
|
||||
/* this is the real object we wrap */
|
||||
PyObject *wrapped;
|
||||
|
||||
} pdecimalObject;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* !defined(PSYCOPG_PDECIMAL_H) */
|
||||
221
psycopg/adapter_pfloat.c
Normal file
221
psycopg/adapter_pfloat.c
Normal file
@ -0,0 +1,221 @@
|
||||
/* adapter_float.c - psycopg pfloat type wrapper implementation
|
||||
*
|
||||
* Copyright (C) 2003-2019 Federico Di Gregorio <fog@debian.org>
|
||||
* Copyright (C) 2020-2021 The Psycopg Team
|
||||
*
|
||||
* This file is part of psycopg.
|
||||
*
|
||||
* psycopg2 is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give
|
||||
* permission to link this program with the OpenSSL library (or with
|
||||
* modified versions of OpenSSL that use the same license as OpenSSL),
|
||||
* and distribute linked combinations including the two.
|
||||
*
|
||||
* You must obey the GNU Lesser General Public License in all respects for
|
||||
* all of the code used other than OpenSSL.
|
||||
*
|
||||
* psycopg2 is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
|
||||
* License for more details.
|
||||
*/
|
||||
|
||||
#define PSYCOPG_MODULE
|
||||
#include "psycopg/psycopg.h"
|
||||
|
||||
#include "psycopg/adapter_pfloat.h"
|
||||
#include "psycopg/microprotocols_proto.h"
|
||||
|
||||
#include <floatobject.h>
|
||||
#include <math.h>
|
||||
|
||||
|
||||
/** the Float object **/
|
||||
|
||||
static PyObject *
|
||||
pfloat_getquoted(pfloatObject *self, PyObject *args)
|
||||
{
|
||||
PyObject *rv;
|
||||
double n = PyFloat_AsDouble(self->wrapped);
|
||||
if (isnan(n))
|
||||
rv = Bytes_FromString("'NaN'::float");
|
||||
else if (isinf(n)) {
|
||||
if (n > 0)
|
||||
rv = Bytes_FromString("'Infinity'::float");
|
||||
else
|
||||
rv = Bytes_FromString("'-Infinity'::float");
|
||||
}
|
||||
else {
|
||||
if (!(rv = PyObject_Repr(self->wrapped))) {
|
||||
goto exit;
|
||||
}
|
||||
|
||||
/* unicode to bytes */
|
||||
{
|
||||
PyObject *tmp = PyUnicode_AsUTF8String(rv);
|
||||
Py_DECREF(rv);
|
||||
if (!(rv = tmp)) {
|
||||
goto exit;
|
||||
}
|
||||
}
|
||||
|
||||
if ('-' == Bytes_AS_STRING(rv)[0]) {
|
||||
/* Prepend a space in front of negative numbers (ticket #57) */
|
||||
PyObject *tmp;
|
||||
if (!(tmp = Bytes_FromString(" "))) {
|
||||
Py_DECREF(rv);
|
||||
rv = NULL;
|
||||
goto exit;
|
||||
}
|
||||
Bytes_ConcatAndDel(&tmp, rv);
|
||||
if (!(rv = tmp)) {
|
||||
goto exit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
exit:
|
||||
return rv;
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
pfloat_str(pfloatObject *self)
|
||||
{
|
||||
return psyco_ensure_text(pfloat_getquoted(self, NULL));
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
pfloat_conform(pfloatObject *self, PyObject *args)
|
||||
{
|
||||
PyObject *res, *proto;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "O", &proto)) return NULL;
|
||||
|
||||
if (proto == (PyObject*)&isqlquoteType)
|
||||
res = (PyObject*)self;
|
||||
else
|
||||
res = Py_None;
|
||||
|
||||
Py_INCREF(res);
|
||||
return res;
|
||||
}
|
||||
|
||||
/** the Float object */
|
||||
|
||||
/* object member list */
|
||||
|
||||
static struct PyMemberDef pfloatObject_members[] = {
|
||||
{"adapted", T_OBJECT, offsetof(pfloatObject, wrapped), READONLY},
|
||||
{NULL}
|
||||
};
|
||||
|
||||
/* object method table */
|
||||
|
||||
static PyMethodDef pfloatObject_methods[] = {
|
||||
{"getquoted", (PyCFunction)pfloat_getquoted, METH_NOARGS,
|
||||
"getquoted() -> wrapped object value as SQL-quoted string"},
|
||||
{"__conform__", (PyCFunction)pfloat_conform, METH_VARARGS, NULL},
|
||||
{NULL} /* Sentinel */
|
||||
};
|
||||
|
||||
/* initialization and finalization methods */
|
||||
|
||||
static int
|
||||
pfloat_setup(pfloatObject *self, PyObject *obj)
|
||||
{
|
||||
Dprintf("pfloat_setup: init pfloat object at %p, refcnt = "
|
||||
FORMAT_CODE_PY_SSIZE_T,
|
||||
self, Py_REFCNT(self)
|
||||
);
|
||||
|
||||
Py_INCREF(obj);
|
||||
self->wrapped = obj;
|
||||
|
||||
Dprintf("pfloat_setup: good pfloat object at %p, refcnt = "
|
||||
FORMAT_CODE_PY_SSIZE_T,
|
||||
self, Py_REFCNT(self)
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
pfloat_dealloc(PyObject* obj)
|
||||
{
|
||||
pfloatObject *self = (pfloatObject *)obj;
|
||||
|
||||
Py_CLEAR(self->wrapped);
|
||||
|
||||
Dprintf("pfloat_dealloc: deleted pfloat object at %p, refcnt = "
|
||||
FORMAT_CODE_PY_SSIZE_T,
|
||||
obj, Py_REFCNT(obj)
|
||||
);
|
||||
|
||||
Py_TYPE(obj)->tp_free(obj);
|
||||
}
|
||||
|
||||
static int
|
||||
pfloat_init(PyObject *obj, PyObject *args, PyObject *kwds)
|
||||
{
|
||||
PyObject *o;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "O", &o))
|
||||
return -1;
|
||||
|
||||
return pfloat_setup((pfloatObject *)obj, o);
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
pfloat_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
|
||||
{
|
||||
return type->tp_alloc(type, 0);
|
||||
}
|
||||
|
||||
|
||||
/* object type */
|
||||
|
||||
#define pfloatType_doc \
|
||||
"Float(str) -> new Float adapter object"
|
||||
|
||||
PyTypeObject pfloatType = {
|
||||
PyVarObject_HEAD_INIT(NULL, 0)
|
||||
"psycopg2.extensions.Float",
|
||||
sizeof(pfloatObject), 0,
|
||||
pfloat_dealloc, /*tp_dealloc*/
|
||||
0, /*tp_print*/
|
||||
0, /*tp_getattr*/
|
||||
0, /*tp_setattr*/
|
||||
0, /*tp_compare*/
|
||||
0, /*tp_repr*/
|
||||
0, /*tp_as_number*/
|
||||
0, /*tp_as_sequence*/
|
||||
0, /*tp_as_mapping*/
|
||||
0, /*tp_hash */
|
||||
0, /*tp_call*/
|
||||
(reprfunc)pfloat_str, /*tp_str*/
|
||||
0, /*tp_getattro*/
|
||||
0, /*tp_setattro*/
|
||||
0, /*tp_as_buffer*/
|
||||
Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, /*tp_flags*/
|
||||
pfloatType_doc, /*tp_doc*/
|
||||
0, /*tp_traverse*/
|
||||
0, /*tp_clear*/
|
||||
0, /*tp_richcompare*/
|
||||
0, /*tp_weaklistoffset*/
|
||||
0, /*tp_iter*/
|
||||
0, /*tp_iternext*/
|
||||
pfloatObject_methods, /*tp_methods*/
|
||||
pfloatObject_members, /*tp_members*/
|
||||
0, /*tp_getset*/
|
||||
0, /*tp_base*/
|
||||
0, /*tp_dict*/
|
||||
0, /*tp_descr_get*/
|
||||
0, /*tp_descr_set*/
|
||||
0, /*tp_dictoffset*/
|
||||
pfloat_init, /*tp_init*/
|
||||
0, /*tp_alloc*/
|
||||
pfloat_new, /*tp_new*/
|
||||
};
|
||||
48
psycopg/adapter_pfloat.h
Normal file
48
psycopg/adapter_pfloat.h
Normal file
@ -0,0 +1,48 @@
|
||||
/* adapter_pfloat.h - definition for the psycopg float type wrapper
|
||||
*
|
||||
* Copyright (C) 2003-2019 Federico Di Gregorio <fog@debian.org>
|
||||
* Copyright (C) 2020-2021 The Psycopg Team
|
||||
*
|
||||
* This file is part of psycopg.
|
||||
*
|
||||
* psycopg2 is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give
|
||||
* permission to link this program with the OpenSSL library (or with
|
||||
* modified versions of OpenSSL that use the same license as OpenSSL),
|
||||
* and distribute linked combinations including the two.
|
||||
*
|
||||
* You must obey the GNU Lesser General Public License in all respects for
|
||||
* all of the code used other than OpenSSL.
|
||||
*
|
||||
* psycopg2 is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
|
||||
* License for more details.
|
||||
*/
|
||||
|
||||
#ifndef PSYCOPG_PFLOAT_H
|
||||
#define PSYCOPG_PFLOAT_H 1
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
extern HIDDEN PyTypeObject pfloatType;
|
||||
|
||||
typedef struct {
|
||||
PyObject_HEAD
|
||||
|
||||
/* this is the real object we wrap */
|
||||
PyObject *wrapped;
|
||||
|
||||
} pfloatObject;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* !defined(PSYCOPG_PFLOAT_H) */
|
||||
222
psycopg/adapter_pint.c
Normal file
222
psycopg/adapter_pint.c
Normal file
@ -0,0 +1,222 @@
|
||||
/* adapter_int.c - psycopg pint type wrapper implementation
|
||||
*
|
||||
* Copyright (C) 2011-2019 Daniele Varrazzo <daniele.varrazzo@gmail.com>
|
||||
* Copyright (C) 2020-2021 The Psycopg Team
|
||||
*
|
||||
* This file is part of psycopg.
|
||||
*
|
||||
* psycopg2 is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give
|
||||
* permission to link this program with the OpenSSL library (or with
|
||||
* modified versions of OpenSSL that use the same license as OpenSSL),
|
||||
* and distribute linked combinations including the two.
|
||||
*
|
||||
* You must obey the GNU Lesser General Public License in all respects for
|
||||
* all of the code used other than OpenSSL.
|
||||
*
|
||||
* psycopg2 is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
|
||||
* License for more details.
|
||||
*/
|
||||
|
||||
#define PSYCOPG_MODULE
|
||||
#include "psycopg/psycopg.h"
|
||||
|
||||
#include "psycopg/adapter_pint.h"
|
||||
#include "psycopg/microprotocols_proto.h"
|
||||
|
||||
|
||||
/** the Int object **/
|
||||
|
||||
static PyObject *
|
||||
pint_getquoted(pintObject *self, PyObject *args)
|
||||
{
|
||||
PyObject *res = NULL;
|
||||
|
||||
/* Convert subclass to int to handle IntEnum and other subclasses
|
||||
* whose str() is not the number. */
|
||||
if (PyLong_CheckExact(self->wrapped)) {
|
||||
res = PyObject_Str(self->wrapped);
|
||||
} else {
|
||||
PyObject *tmp;
|
||||
if (!(tmp = PyObject_CallFunctionObjArgs(
|
||||
(PyObject *)&PyLong_Type, self->wrapped, NULL))) {
|
||||
goto exit;
|
||||
}
|
||||
res = PyObject_Str(tmp);
|
||||
Py_DECREF(tmp);
|
||||
}
|
||||
|
||||
if (!res) {
|
||||
goto exit;
|
||||
}
|
||||
|
||||
/* unicode to bytes */
|
||||
{
|
||||
PyObject *tmp = PyUnicode_AsUTF8String(res);
|
||||
Py_DECREF(res);
|
||||
if (!(res = tmp)) {
|
||||
goto exit;
|
||||
}
|
||||
}
|
||||
|
||||
if ('-' == Bytes_AS_STRING(res)[0]) {
|
||||
/* Prepend a space in front of negative numbers (ticket #57) */
|
||||
PyObject *tmp;
|
||||
if (!(tmp = Bytes_FromString(" "))) {
|
||||
Py_DECREF(res);
|
||||
res = NULL;
|
||||
goto exit;
|
||||
}
|
||||
Bytes_ConcatAndDel(&tmp, res);
|
||||
if (!(res = tmp)) {
|
||||
goto exit;
|
||||
}
|
||||
}
|
||||
|
||||
exit:
|
||||
return res;
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
pint_str(pintObject *self)
|
||||
{
|
||||
return psyco_ensure_text(pint_getquoted(self, NULL));
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
pint_conform(pintObject *self, PyObject *args)
|
||||
{
|
||||
PyObject *res, *proto;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "O", &proto)) return NULL;
|
||||
|
||||
if (proto == (PyObject*)&isqlquoteType)
|
||||
res = (PyObject*)self;
|
||||
else
|
||||
res = Py_None;
|
||||
|
||||
Py_INCREF(res);
|
||||
return res;
|
||||
}
|
||||
|
||||
/** the int object */
|
||||
|
||||
/* object member list */
|
||||
|
||||
static struct PyMemberDef pintObject_members[] = {
|
||||
{"adapted", T_OBJECT, offsetof(pintObject, wrapped), READONLY},
|
||||
{NULL}
|
||||
};
|
||||
|
||||
/* object method table */
|
||||
|
||||
static PyMethodDef pintObject_methods[] = {
|
||||
{"getquoted", (PyCFunction)pint_getquoted, METH_NOARGS,
|
||||
"getquoted() -> wrapped object value as SQL-quoted string"},
|
||||
{"__conform__", (PyCFunction)pint_conform, METH_VARARGS, NULL},
|
||||
{NULL} /* Sentinel */
|
||||
};
|
||||
|
||||
/* initialization and finalization methods */
|
||||
|
||||
static int
|
||||
pint_setup(pintObject *self, PyObject *obj)
|
||||
{
|
||||
Dprintf("pint_setup: init pint object at %p, refcnt = "
|
||||
FORMAT_CODE_PY_SSIZE_T,
|
||||
self, Py_REFCNT(self)
|
||||
);
|
||||
|
||||
Py_INCREF(obj);
|
||||
self->wrapped = obj;
|
||||
|
||||
Dprintf("pint_setup: good pint object at %p, refcnt = "
|
||||
FORMAT_CODE_PY_SSIZE_T,
|
||||
self, Py_REFCNT(self)
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
pint_dealloc(PyObject* obj)
|
||||
{
|
||||
pintObject *self = (pintObject *)obj;
|
||||
|
||||
Py_CLEAR(self->wrapped);
|
||||
|
||||
Dprintf("pint_dealloc: deleted pint object at %p, refcnt = "
|
||||
FORMAT_CODE_PY_SSIZE_T,
|
||||
obj, Py_REFCNT(obj)
|
||||
);
|
||||
|
||||
Py_TYPE(obj)->tp_free(obj);
|
||||
}
|
||||
|
||||
static int
|
||||
pint_init(PyObject *obj, PyObject *args, PyObject *kwds)
|
||||
{
|
||||
PyObject *o;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "O", &o))
|
||||
return -1;
|
||||
|
||||
return pint_setup((pintObject *)obj, o);
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
pint_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
|
||||
{
|
||||
return type->tp_alloc(type, 0);
|
||||
}
|
||||
|
||||
|
||||
/* object type */
|
||||
|
||||
#define pintType_doc \
|
||||
"Int(str) -> new Int adapter object"
|
||||
|
||||
PyTypeObject pintType = {
|
||||
PyVarObject_HEAD_INIT(NULL, 0)
|
||||
"psycopg2.extensions.Int",
|
||||
sizeof(pintObject), 0,
|
||||
pint_dealloc, /*tp_dealloc*/
|
||||
0, /*tp_print*/
|
||||
0, /*tp_getattr*/
|
||||
0, /*tp_setattr*/
|
||||
0, /*tp_compare*/
|
||||
0, /*tp_repr*/
|
||||
0, /*tp_as_number*/
|
||||
0, /*tp_as_sequence*/
|
||||
0, /*tp_as_mapping*/
|
||||
0, /*tp_hash */
|
||||
0, /*tp_call*/
|
||||
(reprfunc)pint_str, /*tp_str*/
|
||||
0, /*tp_getattro*/
|
||||
0, /*tp_setattro*/
|
||||
0, /*tp_as_buffer*/
|
||||
Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, /*tp_flags*/
|
||||
pintType_doc, /*tp_doc*/
|
||||
0, /*tp_traverse*/
|
||||
0, /*tp_clear*/
|
||||
0, /*tp_richcompare*/
|
||||
0, /*tp_weaklistoffset*/
|
||||
0, /*tp_iter*/
|
||||
0, /*tp_iternext*/
|
||||
pintObject_methods, /*tp_methods*/
|
||||
pintObject_members, /*tp_members*/
|
||||
0, /*tp_getset*/
|
||||
0, /*tp_base*/
|
||||
0, /*tp_dict*/
|
||||
0, /*tp_descr_get*/
|
||||
0, /*tp_descr_set*/
|
||||
0, /*tp_dictoffset*/
|
||||
pint_init, /*tp_init*/
|
||||
0, /*tp_alloc*/
|
||||
pint_new, /*tp_new*/
|
||||
};
|
||||
48
psycopg/adapter_pint.h
Normal file
48
psycopg/adapter_pint.h
Normal file
@ -0,0 +1,48 @@
|
||||
/* adapter_pint.h - definition for the psycopg int type wrapper
|
||||
*
|
||||
* Copyright (C) 2011-2019 Daniele Varrazzo <daniele.varrazzo@gmail.com>
|
||||
* Copyright (C) 2020-2021 The Psycopg Team
|
||||
*
|
||||
* This file is part of psycopg.
|
||||
*
|
||||
* psycopg2 is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give
|
||||
* permission to link this program with the OpenSSL library (or with
|
||||
* modified versions of OpenSSL that use the same license as OpenSSL),
|
||||
* and distribute linked combinations including the two.
|
||||
*
|
||||
* You must obey the GNU Lesser General Public License in all respects for
|
||||
* all of the code used other than OpenSSL.
|
||||
*
|
||||
* psycopg2 is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
|
||||
* License for more details.
|
||||
*/
|
||||
|
||||
#ifndef PSYCOPG_PINT_H
|
||||
#define PSYCOPG_PINT_H 1
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
extern HIDDEN PyTypeObject pintType;
|
||||
|
||||
typedef struct {
|
||||
PyObject_HEAD
|
||||
|
||||
/* this is the real object we wrap */
|
||||
PyObject *wrapped;
|
||||
|
||||
} pintObject;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* !defined(PSYCOPG_PINT_H) */
|
||||
307
psycopg/adapter_qstring.c
Normal file
307
psycopg/adapter_qstring.c
Normal file
@ -0,0 +1,307 @@
|
||||
/* adapter_qstring.c - QuotedString objects
|
||||
*
|
||||
* Copyright (C) 2003-2019 Federico Di Gregorio <fog@debian.org>
|
||||
* Copyright (C) 2020-2021 The Psycopg Team
|
||||
*
|
||||
* This file is part of psycopg.
|
||||
*
|
||||
* psycopg2 is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give
|
||||
* permission to link this program with the OpenSSL library (or with
|
||||
* modified versions of OpenSSL that use the same license as OpenSSL),
|
||||
* and distribute linked combinations including the two.
|
||||
*
|
||||
* You must obey the GNU Lesser General Public License in all respects for
|
||||
* all of the code used other than OpenSSL.
|
||||
*
|
||||
* psycopg2 is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
|
||||
* License for more details.
|
||||
*/
|
||||
|
||||
#define PSYCOPG_MODULE
|
||||
#include "psycopg/psycopg.h"
|
||||
|
||||
#include "psycopg/connection.h"
|
||||
#include "psycopg/adapter_qstring.h"
|
||||
#include "psycopg/microprotocols_proto.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
static const char *default_encoding = "latin1";
|
||||
|
||||
/* qstring_quote - do the quote process on plain and unicode strings */
|
||||
|
||||
static PyObject *
|
||||
qstring_quote(qstringObject *self)
|
||||
{
|
||||
PyObject *str = NULL;
|
||||
char *s, *buffer = NULL;
|
||||
Py_ssize_t len, qlen;
|
||||
const char *encoding;
|
||||
PyObject *rv = NULL;
|
||||
|
||||
if (PyUnicode_Check(self->wrapped)) {
|
||||
if (self->conn) {
|
||||
if (!(str = conn_encode(self->conn, self->wrapped))) { goto exit; }
|
||||
}
|
||||
else {
|
||||
encoding = self->encoding ? self->encoding : default_encoding;
|
||||
if(!(str = PyUnicode_AsEncodedString(self->wrapped, encoding, NULL))) {
|
||||
goto exit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* if the wrapped object is a binary string, we don't know how to
|
||||
(re)encode it, so we pass it as-is */
|
||||
else if (Bytes_Check(self->wrapped)) {
|
||||
str = self->wrapped;
|
||||
/* INCREF to make it ref-wise identical to unicode one */
|
||||
Py_INCREF(str);
|
||||
}
|
||||
|
||||
/* if the wrapped object is not a string, this is an error */
|
||||
else {
|
||||
PyErr_SetString(PyExc_TypeError, "can't quote non-string object");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
/* encode the string into buffer */
|
||||
Bytes_AsStringAndSize(str, &s, &len);
|
||||
if (!(buffer = psyco_escape_string(self->conn, s, len, NULL, &qlen))) {
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (qlen > PY_SSIZE_T_MAX) {
|
||||
PyErr_SetString(PyExc_IndexError,
|
||||
"PG buffer too large to fit in Python buffer.");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
rv = Bytes_FromStringAndSize(buffer, qlen);
|
||||
|
||||
exit:
|
||||
PyMem_Free(buffer);
|
||||
Py_XDECREF(str);
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
/* qstring_str, qstring_getquoted - return result of quoting */
|
||||
|
||||
static PyObject *
|
||||
qstring_getquoted(qstringObject *self, PyObject *args)
|
||||
{
|
||||
if (self->buffer == NULL) {
|
||||
self->buffer = qstring_quote(self);
|
||||
}
|
||||
Py_XINCREF(self->buffer);
|
||||
return self->buffer;
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
qstring_str(qstringObject *self)
|
||||
{
|
||||
return psyco_ensure_text(qstring_getquoted(self, NULL));
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
qstring_prepare(qstringObject *self, PyObject *args)
|
||||
{
|
||||
PyObject *conn;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "O!", &connectionType, &conn))
|
||||
return NULL;
|
||||
|
||||
Py_CLEAR(self->conn);
|
||||
Py_INCREF(conn);
|
||||
self->conn = (connectionObject *)conn;
|
||||
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
qstring_conform(qstringObject *self, PyObject *args)
|
||||
{
|
||||
PyObject *res, *proto;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "O", &proto)) return NULL;
|
||||
|
||||
if (proto == (PyObject*)&isqlquoteType)
|
||||
res = (PyObject*)self;
|
||||
else
|
||||
res = Py_None;
|
||||
|
||||
Py_INCREF(res);
|
||||
return res;
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
qstring_get_encoding(qstringObject *self)
|
||||
{
|
||||
if (self->conn) {
|
||||
return conn_pgenc_to_pyenc(self->conn->encoding, NULL);
|
||||
}
|
||||
else {
|
||||
return Text_FromUTF8(self->encoding ? self->encoding : default_encoding);
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
qstring_set_encoding(qstringObject *self, PyObject *pyenc)
|
||||
{
|
||||
int rv = -1;
|
||||
const char *tmp;
|
||||
char *cenc;
|
||||
|
||||
/* get a C copy of the encoding (which may come from unicode) */
|
||||
Py_INCREF(pyenc);
|
||||
if (!(pyenc = psyco_ensure_bytes(pyenc))) { goto exit; }
|
||||
if (!(tmp = Bytes_AsString(pyenc))) { goto exit; }
|
||||
if (0 > psyco_strdup(&cenc, tmp, -1)) { goto exit; }
|
||||
|
||||
Dprintf("qstring_set_encoding: encoding set to %s", cenc);
|
||||
PyMem_Free((void *)self->encoding);
|
||||
self->encoding = cenc;
|
||||
rv = 0;
|
||||
|
||||
exit:
|
||||
Py_XDECREF(pyenc);
|
||||
return rv;
|
||||
}
|
||||
|
||||
/** the QuotedString object **/
|
||||
|
||||
/* object member list */
|
||||
|
||||
static struct PyMemberDef qstringObject_members[] = {
|
||||
{"adapted", T_OBJECT, offsetof(qstringObject, wrapped), READONLY},
|
||||
{"buffer", T_OBJECT, offsetof(qstringObject, buffer), READONLY},
|
||||
{NULL}
|
||||
};
|
||||
|
||||
/* object method table */
|
||||
|
||||
static PyMethodDef qstringObject_methods[] = {
|
||||
{"getquoted", (PyCFunction)qstring_getquoted, METH_NOARGS,
|
||||
"getquoted() -> wrapped object value as SQL-quoted string"},
|
||||
{"prepare", (PyCFunction)qstring_prepare, METH_VARARGS,
|
||||
"prepare(conn) -> set encoding to conn->encoding and store conn"},
|
||||
{"__conform__", (PyCFunction)qstring_conform, METH_VARARGS, NULL},
|
||||
{NULL} /* Sentinel */
|
||||
};
|
||||
|
||||
static PyGetSetDef qstringObject_getsets[] = {
|
||||
{ "encoding",
|
||||
(getter)qstring_get_encoding,
|
||||
(setter)qstring_set_encoding,
|
||||
"current encoding of the adapter" },
|
||||
{NULL}
|
||||
};
|
||||
|
||||
/* initialization and finalization methods */
|
||||
|
||||
static int
|
||||
qstring_setup(qstringObject *self, PyObject *str)
|
||||
{
|
||||
Dprintf("qstring_setup: init qstring object at %p, refcnt = "
|
||||
FORMAT_CODE_PY_SSIZE_T,
|
||||
self, Py_REFCNT(self)
|
||||
);
|
||||
|
||||
Py_INCREF(str);
|
||||
self->wrapped = str;
|
||||
|
||||
Dprintf("qstring_setup: good qstring object at %p, refcnt = "
|
||||
FORMAT_CODE_PY_SSIZE_T,
|
||||
self, Py_REFCNT(self)
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
qstring_dealloc(PyObject* obj)
|
||||
{
|
||||
qstringObject *self = (qstringObject *)obj;
|
||||
|
||||
Py_CLEAR(self->wrapped);
|
||||
Py_CLEAR(self->buffer);
|
||||
Py_CLEAR(self->conn);
|
||||
PyMem_Free((void *)self->encoding);
|
||||
|
||||
Dprintf("qstring_dealloc: deleted qstring object at %p, refcnt = "
|
||||
FORMAT_CODE_PY_SSIZE_T,
|
||||
obj, Py_REFCNT(obj)
|
||||
);
|
||||
|
||||
Py_TYPE(obj)->tp_free(obj);
|
||||
}
|
||||
|
||||
static int
|
||||
qstring_init(PyObject *obj, PyObject *args, PyObject *kwds)
|
||||
{
|
||||
PyObject *str;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "O", &str))
|
||||
return -1;
|
||||
|
||||
return qstring_setup((qstringObject *)obj, str);
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
qstring_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
|
||||
{
|
||||
return type->tp_alloc(type, 0);
|
||||
}
|
||||
|
||||
|
||||
/* object type */
|
||||
|
||||
#define qstringType_doc \
|
||||
"QuotedString(str) -> new quoted object"
|
||||
|
||||
PyTypeObject qstringType = {
|
||||
PyVarObject_HEAD_INIT(NULL, 0)
|
||||
"psycopg2.extensions.QuotedString",
|
||||
sizeof(qstringObject), 0,
|
||||
qstring_dealloc, /*tp_dealloc*/
|
||||
0, /*tp_print*/
|
||||
0, /*tp_getattr*/
|
||||
0, /*tp_setattr*/
|
||||
0, /*tp_compare*/
|
||||
0, /*tp_repr*/
|
||||
0, /*tp_as_number*/
|
||||
0, /*tp_as_sequence*/
|
||||
0, /*tp_as_mapping*/
|
||||
0, /*tp_hash */
|
||||
0, /*tp_call*/
|
||||
(reprfunc)qstring_str, /*tp_str*/
|
||||
0, /*tp_getattro*/
|
||||
0, /*tp_setattro*/
|
||||
0, /*tp_as_buffer*/
|
||||
Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, /*tp_flags*/
|
||||
qstringType_doc, /*tp_doc*/
|
||||
0, /*tp_traverse*/
|
||||
0, /*tp_clear*/
|
||||
0, /*tp_richcompare*/
|
||||
0, /*tp_weaklistoffset*/
|
||||
0, /*tp_iter*/
|
||||
0, /*tp_iternext*/
|
||||
qstringObject_methods, /*tp_methods*/
|
||||
qstringObject_members, /*tp_members*/
|
||||
qstringObject_getsets, /*tp_getset*/
|
||||
0, /*tp_base*/
|
||||
0, /*tp_dict*/
|
||||
0, /*tp_descr_get*/
|
||||
0, /*tp_descr_set*/
|
||||
0, /*tp_dictoffset*/
|
||||
qstring_init, /*tp_init*/
|
||||
0, /*tp_alloc*/
|
||||
qstring_new, /*tp_new*/
|
||||
};
|
||||
52
psycopg/adapter_qstring.h
Normal file
52
psycopg/adapter_qstring.h
Normal file
@ -0,0 +1,52 @@
|
||||
/* adapter_qstring.h - definition for the QuotedString type
|
||||
*
|
||||
* Copyright (C) 2003-2019 Federico Di Gregorio <fog@debian.org>
|
||||
* Copyright (C) 2020-2021 The Psycopg Team
|
||||
*
|
||||
* This file is part of psycopg.
|
||||
*
|
||||
* psycopg2 is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give
|
||||
* permission to link this program with the OpenSSL library (or with
|
||||
* modified versions of OpenSSL that use the same license as OpenSSL),
|
||||
* and distribute linked combinations including the two.
|
||||
*
|
||||
* You must obey the GNU Lesser General Public License in all respects for
|
||||
* all of the code used other than OpenSSL.
|
||||
*
|
||||
* psycopg2 is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
|
||||
* License for more details.
|
||||
*/
|
||||
|
||||
#ifndef PSYCOPG_QSTRING_H
|
||||
#define PSYCOPG_QSTRING_H 1
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
extern HIDDEN PyTypeObject qstringType;
|
||||
|
||||
typedef struct {
|
||||
PyObject_HEAD
|
||||
|
||||
PyObject *wrapped;
|
||||
PyObject *buffer;
|
||||
|
||||
connectionObject *conn;
|
||||
|
||||
const char *encoding;
|
||||
|
||||
} qstringObject;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* !defined(PSYCOPG_QSTRING_H) */
|
||||
58
psycopg/aix_support.c
Normal file
58
psycopg/aix_support.c
Normal file
@ -0,0 +1,58 @@
|
||||
/* aix_support.c - emulate functions missing on AIX
|
||||
*
|
||||
* Copyright (C) 2017 My Karlsson <mk@acc.umu.se>
|
||||
* Copyright (c) 2018, Joyent, Inc.
|
||||
* Copyright (C) 2020-2021 The Psycopg Team
|
||||
*
|
||||
* This file is part of psycopg.
|
||||
*
|
||||
* psycopg2 is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give
|
||||
* permission to link this program with the OpenSSL library (or with
|
||||
* modified versions of OpenSSL that use the same license as OpenSSL),
|
||||
* and distribute linked combinations including the two.
|
||||
*
|
||||
* You must obey the GNU Lesser General Public License in all respects for
|
||||
* all of the code used other than OpenSSL.
|
||||
*
|
||||
* psycopg2 is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
|
||||
* License for more details.
|
||||
*/
|
||||
|
||||
#define PSYCOPG_MODULE
|
||||
#include "psycopg/psycopg.h"
|
||||
#include "psycopg/aix_support.h"
|
||||
|
||||
#if defined(_AIX)
|
||||
/* timeradd is missing on AIX */
|
||||
#ifndef timeradd
|
||||
void
|
||||
timeradd(struct timeval *a, struct timeval *b, struct timeval *c)
|
||||
{
|
||||
c->tv_sec = a->tv_sec + b->tv_sec;
|
||||
c->tv_usec = a->tv_usec + b->tv_usec;
|
||||
if (c->tv_usec >= 1000000) {
|
||||
c->tv_usec -= 1000000;
|
||||
c->tv_sec += 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* timersub is missing on AIX */
|
||||
void
|
||||
timersub(struct timeval *a, struct timeval *b, struct timeval *c)
|
||||
{
|
||||
c->tv_sec = a->tv_sec - b->tv_sec;
|
||||
c->tv_usec = a->tv_usec - b->tv_usec;
|
||||
if (c->tv_usec < 0) {
|
||||
c->tv_usec += 1000000;
|
||||
c->tv_sec -= 1;
|
||||
}
|
||||
}
|
||||
#endif /* timeradd */
|
||||
#endif /* defined(_AIX)*/
|
||||
48
psycopg/aix_support.h
Normal file
48
psycopg/aix_support.h
Normal file
@ -0,0 +1,48 @@
|
||||
/* aix_support.h - definitions for aix_support.c
|
||||
*
|
||||
* Copyright (C) 2017 My Karlsson <mk@acc.umu.se>
|
||||
* Copyright (c) 2018-2019, Joyent, Inc.
|
||||
* Copyright (C) 2020-2021 The Psycopg Team
|
||||
*
|
||||
* This file is part of psycopg.
|
||||
*
|
||||
* psycopg2 is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give
|
||||
* permission to link this program with the OpenSSL library (or with
|
||||
* modified versions of OpenSSL that use the same license as OpenSSL),
|
||||
* and distribute linked combinations including the two.
|
||||
*
|
||||
* You must obey the GNU Lesser General Public License in all respects for
|
||||
* all of the code used other than OpenSSL.
|
||||
*
|
||||
* psycopg2 is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
|
||||
* License for more details.
|
||||
*/
|
||||
#ifndef PSYCOPG_AIX_SUPPORT_H
|
||||
#define PSYCOPG_AIX_SUPPORT_H
|
||||
|
||||
#include "psycopg/config.h"
|
||||
|
||||
#ifdef _AIX
|
||||
#include <sys/time.h>
|
||||
|
||||
#ifndef timeradd
|
||||
extern HIDDEN void timeradd(struct timeval *a, struct timeval *b, struct timeval *c);
|
||||
extern HIDDEN void timersub(struct timeval *a, struct timeval *b, struct timeval *c);
|
||||
#endif
|
||||
|
||||
#ifndef timercmp
|
||||
#define timercmp(a, b, cmp) \
|
||||
(((a)->tv_sec == (b)->tv_sec) ? \
|
||||
((a)->tv_usec cmp (b)->tv_usec) : \
|
||||
((a)->tv_sec cmp (b)->tv_sec))
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#endif /* !defined(PSYCOPG_AIX_SUPPORT_H) */
|
||||
309
psycopg/bytes_format.c
Normal file
309
psycopg/bytes_format.c
Normal file
@ -0,0 +1,309 @@
|
||||
/* bytes_format.c - bytes-oriented version of PyString_Format
|
||||
*
|
||||
* Copyright (C) 2010-2019 Daniele Varrazzo <daniele.varrazzo@gmail.com>
|
||||
* Copyright (C) 2020-2021 The Psycopg Team
|
||||
*
|
||||
* This file is part of psycopg.
|
||||
*
|
||||
* psycopg2 is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give
|
||||
* permission to link this program with the OpenSSL library (or with
|
||||
* modified versions of OpenSSL that use the same license as OpenSSL),
|
||||
* and distribute linked combinations including the two.
|
||||
*
|
||||
* You must obey the GNU Lesser General Public License in all respects for
|
||||
* all of the code used other than OpenSSL.
|
||||
*
|
||||
* psycopg2 is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
|
||||
* License for more details.
|
||||
*/
|
||||
|
||||
/* This implementation is based on the PyString_Format function available in
|
||||
* Python 2.7.1. The function is altered to be used with both Python 2 strings
|
||||
* and Python 3 bytes and is stripped of the support of formats different than
|
||||
* 's'. Original license follows.
|
||||
*
|
||||
* PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
|
||||
* --------------------------------------------
|
||||
*
|
||||
* 1. This LICENSE AGREEMENT is between the Python Software Foundation
|
||||
* ("PSF"), and the Individual or Organization ("Licensee") accessing and
|
||||
* otherwise using this software ("Python") in source or binary form and
|
||||
* its associated documentation.
|
||||
*
|
||||
* 2. Subject to the terms and conditions of this License Agreement, PSF hereby
|
||||
* grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce,
|
||||
* analyze, test, perform and/or display publicly, prepare derivative works,
|
||||
* distribute, and otherwise use Python alone or in any derivative version,
|
||||
* provided, however, that PSF's License Agreement and PSF's notice of copyright,
|
||||
* i.e., "Copyright (c) 2001-2019, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010
|
||||
* Python Software Foundation; All Rights Reserved" are retained in Python alone or
|
||||
* in any derivative version prepared by Licensee.
|
||||
*
|
||||
* 3. In the event Licensee prepares a derivative work that is based on
|
||||
* or incorporates Python or any part thereof, and wants to make
|
||||
* the derivative work available to others as provided herein, then
|
||||
* Licensee hereby agrees to include in any such work a brief summary of
|
||||
* the changes made to Python.
|
||||
*
|
||||
* 4. PSF is making Python available to Licensee on an "AS IS"
|
||||
* basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
|
||||
* IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND
|
||||
* DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
|
||||
* FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT
|
||||
* INFRINGE ANY THIRD PARTY RIGHTS.
|
||||
*
|
||||
* 5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
|
||||
* FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
|
||||
* A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON,
|
||||
* OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
|
||||
*
|
||||
* 6. This License Agreement will automatically terminate upon a material
|
||||
* breach of its terms and conditions.
|
||||
*
|
||||
* 7. Nothing in this License Agreement shall be deemed to create any
|
||||
* relationship of agency, partnership, or joint venture between PSF and
|
||||
* Licensee. This License Agreement does not grant permission to use PSF
|
||||
* trademarks or trade name in a trademark sense to endorse or promote
|
||||
* products or services of Licensee, or any third party.
|
||||
*
|
||||
* 8. By copying, installing or otherwise using Python, Licensee
|
||||
* agrees to be bound by the terms and conditions of this License
|
||||
* Agreement.
|
||||
*/
|
||||
|
||||
#define PSYCOPG_MODULE
|
||||
#include "psycopg/psycopg.h"
|
||||
#include "pyport.h"
|
||||
|
||||
/* Helpers for formatstring */
|
||||
|
||||
BORROWED Py_LOCAL_INLINE(PyObject *)
|
||||
getnextarg(PyObject *args, Py_ssize_t arglen, Py_ssize_t *p_argidx)
|
||||
{
|
||||
Py_ssize_t argidx = *p_argidx;
|
||||
if (argidx < arglen) {
|
||||
(*p_argidx)++;
|
||||
if (arglen < 0)
|
||||
return args;
|
||||
else
|
||||
return PyTuple_GetItem(args, argidx);
|
||||
}
|
||||
PyErr_SetString(PyExc_TypeError,
|
||||
"not enough arguments for format string");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* wrapper around _Bytes_Resize offering normal Python call semantics */
|
||||
|
||||
STEALS(1)
|
||||
Py_LOCAL_INLINE(PyObject *)
|
||||
resize_bytes(PyObject *b, Py_ssize_t newsize) {
|
||||
if (0 == _Bytes_Resize(&b, newsize)) {
|
||||
return b;
|
||||
}
|
||||
else {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/* fmt%(v1,v2,...) is roughly equivalent to sprintf(fmt, v1, v2, ...) */
|
||||
|
||||
PyObject *
|
||||
Bytes_Format(PyObject *format, PyObject *args)
|
||||
{
|
||||
char *fmt, *res;
|
||||
Py_ssize_t arglen, argidx;
|
||||
Py_ssize_t reslen, rescnt, fmtcnt;
|
||||
int args_owned = 0;
|
||||
PyObject *result;
|
||||
PyObject *dict = NULL;
|
||||
if (format == NULL || !Bytes_Check(format) || args == NULL) {
|
||||
PyErr_SetString(PyExc_SystemError, "bad argument to internal function");
|
||||
return NULL;
|
||||
}
|
||||
fmt = Bytes_AS_STRING(format);
|
||||
fmtcnt = Bytes_GET_SIZE(format);
|
||||
reslen = rescnt = fmtcnt + 100;
|
||||
result = Bytes_FromStringAndSize((char *)NULL, reslen);
|
||||
if (result == NULL)
|
||||
return NULL;
|
||||
res = Bytes_AS_STRING(result);
|
||||
if (PyTuple_Check(args)) {
|
||||
arglen = PyTuple_GET_SIZE(args);
|
||||
argidx = 0;
|
||||
}
|
||||
else {
|
||||
arglen = -1;
|
||||
argidx = -2;
|
||||
}
|
||||
if (Py_TYPE(args)->tp_as_mapping && !PyTuple_Check(args) &&
|
||||
!PyObject_TypeCheck(args, &Bytes_Type))
|
||||
dict = args;
|
||||
while (--fmtcnt >= 0) {
|
||||
if (*fmt != '%') {
|
||||
if (--rescnt < 0) {
|
||||
rescnt = fmtcnt + 100;
|
||||
reslen += rescnt;
|
||||
if (!(result = resize_bytes(result, reslen))) {
|
||||
return NULL;
|
||||
}
|
||||
res = Bytes_AS_STRING(result) + reslen - rescnt;
|
||||
--rescnt;
|
||||
}
|
||||
*res++ = *fmt++;
|
||||
}
|
||||
else {
|
||||
/* Got a format specifier */
|
||||
Py_ssize_t width = -1;
|
||||
int c = '\0';
|
||||
PyObject *v = NULL;
|
||||
PyObject *temp = NULL;
|
||||
char *pbuf;
|
||||
Py_ssize_t len;
|
||||
fmt++;
|
||||
if (*fmt == '(') {
|
||||
char *keystart;
|
||||
Py_ssize_t keylen;
|
||||
PyObject *key;
|
||||
int pcount = 1;
|
||||
|
||||
if (dict == NULL) {
|
||||
PyErr_SetString(PyExc_TypeError,
|
||||
"format requires a mapping");
|
||||
goto error;
|
||||
}
|
||||
++fmt;
|
||||
--fmtcnt;
|
||||
keystart = fmt;
|
||||
/* Skip over balanced parentheses */
|
||||
while (pcount > 0 && --fmtcnt >= 0) {
|
||||
if (*fmt == ')')
|
||||
--pcount;
|
||||
else if (*fmt == '(')
|
||||
++pcount;
|
||||
fmt++;
|
||||
}
|
||||
keylen = fmt - keystart - 1;
|
||||
if (fmtcnt < 0 || pcount > 0) {
|
||||
PyErr_SetString(PyExc_ValueError,
|
||||
"incomplete format key");
|
||||
goto error;
|
||||
}
|
||||
key = Text_FromUTF8AndSize(keystart, keylen);
|
||||
if (key == NULL)
|
||||
goto error;
|
||||
if (args_owned) {
|
||||
Py_DECREF(args);
|
||||
args_owned = 0;
|
||||
}
|
||||
args = PyObject_GetItem(dict, key);
|
||||
Py_DECREF(key);
|
||||
if (args == NULL) {
|
||||
goto error;
|
||||
}
|
||||
args_owned = 1;
|
||||
arglen = -1;
|
||||
argidx = -2;
|
||||
}
|
||||
while (--fmtcnt >= 0) {
|
||||
c = *fmt++;
|
||||
break;
|
||||
}
|
||||
if (fmtcnt < 0) {
|
||||
PyErr_SetString(PyExc_ValueError,
|
||||
"incomplete format");
|
||||
goto error;
|
||||
}
|
||||
switch (c) {
|
||||
case '%':
|
||||
pbuf = "%";
|
||||
len = 1;
|
||||
break;
|
||||
case 's':
|
||||
/* only bytes! */
|
||||
if (!(v = getnextarg(args, arglen, &argidx)))
|
||||
goto error;
|
||||
if (!Bytes_CheckExact(v)) {
|
||||
PyErr_Format(PyExc_ValueError,
|
||||
"only bytes values expected, got %s",
|
||||
Py_TYPE(v)->tp_name);
|
||||
goto error;
|
||||
}
|
||||
temp = v;
|
||||
Py_INCREF(v);
|
||||
pbuf = Bytes_AS_STRING(temp);
|
||||
len = Bytes_GET_SIZE(temp);
|
||||
break;
|
||||
default:
|
||||
PyErr_Format(PyExc_ValueError,
|
||||
"unsupported format character '%c' (0x%x) "
|
||||
"at index " FORMAT_CODE_PY_SSIZE_T,
|
||||
c, c,
|
||||
(Py_ssize_t)(fmt - 1 - Bytes_AS_STRING(format)));
|
||||
goto error;
|
||||
}
|
||||
if (width < len)
|
||||
width = len;
|
||||
if (rescnt < width) {
|
||||
reslen -= rescnt;
|
||||
rescnt = width + fmtcnt + 100;
|
||||
reslen += rescnt;
|
||||
if (reslen < 0) {
|
||||
Py_DECREF(result);
|
||||
Py_XDECREF(temp);
|
||||
if (args_owned)
|
||||
Py_DECREF(args);
|
||||
return PyErr_NoMemory();
|
||||
}
|
||||
if (!(result = resize_bytes(result, reslen))) {
|
||||
Py_XDECREF(temp);
|
||||
if (args_owned)
|
||||
Py_DECREF(args);
|
||||
return NULL;
|
||||
}
|
||||
res = Bytes_AS_STRING(result)
|
||||
+ reslen - rescnt;
|
||||
}
|
||||
Py_MEMCPY(res, pbuf, len);
|
||||
res += len;
|
||||
rescnt -= len;
|
||||
while (--width >= len) {
|
||||
--rescnt;
|
||||
*res++ = ' ';
|
||||
}
|
||||
if (dict && (argidx < arglen) && c != '%') {
|
||||
PyErr_SetString(PyExc_TypeError,
|
||||
"not all arguments converted during string formatting");
|
||||
Py_XDECREF(temp);
|
||||
goto error;
|
||||
}
|
||||
Py_XDECREF(temp);
|
||||
} /* '%' */
|
||||
} /* until end */
|
||||
if (argidx < arglen && !dict) {
|
||||
PyErr_SetString(PyExc_TypeError,
|
||||
"not all arguments converted during string formatting");
|
||||
goto error;
|
||||
}
|
||||
if (args_owned) {
|
||||
Py_DECREF(args);
|
||||
}
|
||||
if (!(result = resize_bytes(result, reslen - rescnt))) {
|
||||
return NULL;
|
||||
}
|
||||
return result;
|
||||
|
||||
error:
|
||||
Py_DECREF(result);
|
||||
if (args_owned) {
|
||||
Py_DECREF(args);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
49
psycopg/column.h
Normal file
49
psycopg/column.h
Normal file
@ -0,0 +1,49 @@
|
||||
/* column.h - definition for a column in cursor.description type
|
||||
*
|
||||
* Copyright (C) 2018-2019 Daniele Varrazzo <daniele.varrazzo@gmail.com>
|
||||
* Copyright (C) 2020-2021 The Psycopg Team
|
||||
*
|
||||
* This file is part of psycopg.
|
||||
*
|
||||
* psycopg2 is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give
|
||||
* permission to link this program with the OpenSSL library (or with
|
||||
* modified versions of OpenSSL that use the same license as OpenSSL),
|
||||
* and distribute linked combinations including the two.
|
||||
*
|
||||
* You must obey the GNU Lesser General Public License in all respects for
|
||||
* all of the code used other than OpenSSL.
|
||||
*
|
||||
* psycopg2 is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
|
||||
* License for more details.
|
||||
*/
|
||||
|
||||
#ifndef PSYCOPG_COLUMN_H
|
||||
#define PSYCOPG_COLUMN_H 1
|
||||
|
||||
extern HIDDEN PyTypeObject columnType;
|
||||
|
||||
typedef struct {
|
||||
PyObject_HEAD
|
||||
|
||||
PyObject *name;
|
||||
PyObject *type_code;
|
||||
PyObject *display_size;
|
||||
PyObject *internal_size;
|
||||
PyObject *precision;
|
||||
PyObject *scale;
|
||||
PyObject *null_ok;
|
||||
|
||||
/* Extensions to the DBAPI */
|
||||
PyObject *table_oid;
|
||||
PyObject *table_column;
|
||||
|
||||
} columnObject;
|
||||
|
||||
#endif /* PSYCOPG_COLUMN_H */
|
||||
420
psycopg/column_type.c
Normal file
420
psycopg/column_type.c
Normal file
@ -0,0 +1,420 @@
|
||||
/* column_type.c - python interface to cursor.description objects
|
||||
*
|
||||
* Copyright (C) 2018-2019 Daniele Varrazzo <daniele.varrazzo@gmail.com>
|
||||
* Copyright (C) 2020-2021 The Psycopg Team
|
||||
*
|
||||
* This file is part of psycopg.
|
||||
*
|
||||
* psycopg2 is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give
|
||||
* permission to link this program with the OpenSSL library (or with
|
||||
* modified versions of OpenSSL that use the same license as OpenSSL),
|
||||
* and distribute linked combinations including the two.
|
||||
*
|
||||
* You must obey the GNU Lesser General Public License in all respects for
|
||||
* all of the code used other than OpenSSL.
|
||||
*
|
||||
* psycopg2 is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
|
||||
* License for more details.
|
||||
*/
|
||||
|
||||
#define PSYCOPG_MODULE
|
||||
#include "psycopg/psycopg.h"
|
||||
|
||||
#include "psycopg/column.h"
|
||||
|
||||
|
||||
static const char column_doc[] =
|
||||
"Description of a column returned by a query.\n\n"
|
||||
"The DBAPI demands this object to be a 7-items sequence. This object\n"
|
||||
"respects this interface, but adds names for the exposed attributes\n"
|
||||
"and adds attribute not requested by the DBAPI.";
|
||||
|
||||
static const char name_doc[] =
|
||||
"The name of the column returned.";
|
||||
|
||||
static const char type_code_doc[] =
|
||||
"The PostgreSQL OID of the column.\n\n"
|
||||
"You can use the pg_type system table to get more informations about the\n"
|
||||
"type. This is the value used by Psycopg to decide what Python type use\n"
|
||||
"to represent the value";
|
||||
|
||||
static const char display_size_doc[] =
|
||||
"The actual length of the column in bytes.\n\n"
|
||||
"Obtaining this value is computationally intensive, so it is always None";
|
||||
|
||||
static const char internal_size_doc[] =
|
||||
"The size in bytes of the column associated to this column on the server.\n\n"
|
||||
"Set to a negative value for variable-size types.";
|
||||
|
||||
static const char precision_doc[] =
|
||||
"Total number of significant digits in columns of type NUMERIC.\n\n"
|
||||
"None for other types.";
|
||||
|
||||
static const char scale_doc[] =
|
||||
"Count of decimal digits in the fractional part in columns of type NUMERIC.\n\n"
|
||||
"None for other types.";
|
||||
|
||||
static const char null_ok_doc[] =
|
||||
"Always none.";
|
||||
|
||||
static const char table_oid_doc[] =
|
||||
"The OID of the table from which the column was fetched.\n\n"
|
||||
"None if not available";
|
||||
|
||||
static const char table_column_doc[] =
|
||||
"The number (within its table) of the column making up the result\n\n"
|
||||
"None if not available. Note that PostgreSQL column numbers start at 1";
|
||||
|
||||
|
||||
static PyMemberDef column_members[] = {
|
||||
{ "name", T_OBJECT, offsetof(columnObject, name), READONLY, (char *)name_doc },
|
||||
{ "type_code", T_OBJECT, offsetof(columnObject, type_code), READONLY, (char *)type_code_doc },
|
||||
{ "display_size", T_OBJECT, offsetof(columnObject, display_size), READONLY, (char *)display_size_doc },
|
||||
{ "internal_size", T_OBJECT, offsetof(columnObject, internal_size), READONLY, (char *)internal_size_doc },
|
||||
{ "precision", T_OBJECT, offsetof(columnObject, precision), READONLY, (char *)precision_doc },
|
||||
{ "scale", T_OBJECT, offsetof(columnObject, scale), READONLY, (char *)scale_doc },
|
||||
{ "null_ok", T_OBJECT, offsetof(columnObject, null_ok), READONLY, (char *)null_ok_doc },
|
||||
{ "table_oid", T_OBJECT, offsetof(columnObject, table_oid), READONLY, (char *)table_oid_doc },
|
||||
{ "table_column", T_OBJECT, offsetof(columnObject, table_column), READONLY, (char *)table_column_doc },
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
|
||||
static PyObject *
|
||||
column_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
|
||||
{
|
||||
return type->tp_alloc(type, 0);
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
column_init(columnObject *self, PyObject *args, PyObject *kwargs)
|
||||
{
|
||||
PyObject *name = NULL;
|
||||
PyObject *type_code = NULL;
|
||||
PyObject *display_size = NULL;
|
||||
PyObject *internal_size = NULL;
|
||||
PyObject *precision = NULL;
|
||||
PyObject *scale = NULL;
|
||||
PyObject *null_ok = NULL;
|
||||
PyObject *table_oid = NULL;
|
||||
PyObject *table_column = NULL;
|
||||
|
||||
static char *kwlist[] = {
|
||||
"name", "type_code", "display_size", "internal_size",
|
||||
"precision", "scale", "null_ok", "table_oid", "table_column", NULL};
|
||||
|
||||
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|OOOOOOOOO", kwlist,
|
||||
&name, &type_code, &display_size, &internal_size, &precision,
|
||||
&scale, &null_ok, &table_oid, &table_column)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
Py_XINCREF(name); self->name = name;
|
||||
Py_XINCREF(type_code); self->type_code = type_code;
|
||||
Py_XINCREF(display_size); self->display_size = display_size;
|
||||
Py_XINCREF(internal_size); self->internal_size = internal_size;
|
||||
Py_XINCREF(precision); self->precision = precision;
|
||||
Py_XINCREF(scale); self->scale = scale;
|
||||
Py_XINCREF(null_ok); self->null_ok = null_ok;
|
||||
Py_XINCREF(table_oid); self->table_oid = table_oid;
|
||||
Py_XINCREF(table_column); self->table_column = table_column;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
column_dealloc(columnObject *self)
|
||||
{
|
||||
Py_CLEAR(self->name);
|
||||
Py_CLEAR(self->type_code);
|
||||
Py_CLEAR(self->display_size);
|
||||
Py_CLEAR(self->internal_size);
|
||||
Py_CLEAR(self->precision);
|
||||
Py_CLEAR(self->scale);
|
||||
Py_CLEAR(self->null_ok);
|
||||
Py_CLEAR(self->table_oid);
|
||||
Py_CLEAR(self->table_column);
|
||||
|
||||
Py_TYPE(self)->tp_free((PyObject *)self);
|
||||
}
|
||||
|
||||
|
||||
static PyObject*
|
||||
column_repr(columnObject *self)
|
||||
{
|
||||
PyObject *rv = NULL;
|
||||
PyObject *format = NULL;
|
||||
PyObject *args = NULL;
|
||||
PyObject *tmp;
|
||||
|
||||
if (!(format = Text_FromUTF8("Column(name=%r, type_code=%r)"))) {
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (!(args = PyTuple_New(2))) { goto exit; }
|
||||
|
||||
tmp = self->name ? self->name : Py_None;
|
||||
Py_INCREF(tmp);
|
||||
PyTuple_SET_ITEM(args, 0, tmp);
|
||||
|
||||
tmp = self->type_code ? self->type_code : Py_None;
|
||||
Py_INCREF(tmp);
|
||||
PyTuple_SET_ITEM(args, 1, tmp);
|
||||
|
||||
rv = Text_Format(format, args);
|
||||
|
||||
exit:
|
||||
Py_XDECREF(args);
|
||||
Py_XDECREF(format);
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
|
||||
static PyObject *
|
||||
column_richcompare(columnObject *self, PyObject *other, int op)
|
||||
{
|
||||
PyObject *rv = NULL;
|
||||
PyObject *tself = NULL;
|
||||
|
||||
if (!(tself = PyObject_CallFunctionObjArgs(
|
||||
(PyObject *)&PyTuple_Type, (PyObject *)self, NULL))) {
|
||||
goto exit;
|
||||
}
|
||||
|
||||
rv = PyObject_RichCompare(tself, other, op);
|
||||
|
||||
exit:
|
||||
Py_XDECREF(tself);
|
||||
return rv;
|
||||
}
|
||||
|
||||
|
||||
/* column description can be accessed as a 7 items tuple for DBAPI compatibility */
|
||||
|
||||
static Py_ssize_t
|
||||
column_len(columnObject *self)
|
||||
{
|
||||
return 7;
|
||||
}
|
||||
|
||||
|
||||
static PyObject *
|
||||
column_getitem(columnObject *self, Py_ssize_t item)
|
||||
{
|
||||
PyObject *rv = NULL;
|
||||
|
||||
if (item < 0)
|
||||
item += 7;
|
||||
|
||||
switch (item) {
|
||||
case 0:
|
||||
rv = self->name;
|
||||
break;
|
||||
case 1:
|
||||
rv = self->type_code;
|
||||
break;
|
||||
case 2:
|
||||
rv = self->display_size;
|
||||
break;
|
||||
case 3:
|
||||
rv = self->internal_size;
|
||||
break;
|
||||
case 4:
|
||||
rv = self->precision;
|
||||
break;
|
||||
case 5:
|
||||
rv = self->scale;
|
||||
break;
|
||||
case 6:
|
||||
rv = self->null_ok;
|
||||
break;
|
||||
default:
|
||||
PyErr_SetString(PyExc_IndexError, "index out of range");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!rv) {
|
||||
rv = Py_None;
|
||||
}
|
||||
|
||||
Py_INCREF(rv);
|
||||
return rv;
|
||||
}
|
||||
|
||||
|
||||
static PyObject*
|
||||
column_subscript(columnObject* self, PyObject* item)
|
||||
{
|
||||
PyObject *t = NULL;
|
||||
PyObject *rv = NULL;
|
||||
|
||||
/* t = tuple(self) */
|
||||
if (!(t = PyObject_CallFunctionObjArgs(
|
||||
(PyObject *)&PyTuple_Type, (PyObject *)self, NULL))) {
|
||||
goto exit;
|
||||
}
|
||||
|
||||
/* rv = t[item] */
|
||||
rv = PyObject_GetItem(t, item);
|
||||
|
||||
exit:
|
||||
Py_XDECREF(t);
|
||||
return rv;
|
||||
}
|
||||
|
||||
static PyMappingMethods column_mapping = {
|
||||
(lenfunc)column_len, /* mp_length */
|
||||
(binaryfunc)column_subscript, /* mp_subscript */
|
||||
0 /* mp_ass_subscript */
|
||||
};
|
||||
|
||||
static PySequenceMethods column_sequence = {
|
||||
(lenfunc)column_len, /* sq_length */
|
||||
0, /* sq_concat */
|
||||
0, /* sq_repeat */
|
||||
(ssizeargfunc)column_getitem, /* sq_item */
|
||||
0, /* sq_slice */
|
||||
0, /* sq_ass_item */
|
||||
0, /* sq_ass_slice */
|
||||
0, /* sq_contains */
|
||||
0, /* sq_inplace_concat */
|
||||
0, /* sq_inplace_repeat */
|
||||
};
|
||||
|
||||
|
||||
static PyObject *
|
||||
column_getstate(columnObject *self, PyObject *dummy)
|
||||
{
|
||||
return PyObject_CallFunctionObjArgs(
|
||||
(PyObject *)&PyTuple_Type, (PyObject *)self, NULL);
|
||||
}
|
||||
|
||||
|
||||
PyObject *
|
||||
column_setstate(columnObject *self, PyObject *state)
|
||||
{
|
||||
Py_ssize_t size;
|
||||
PyObject *rv = NULL;
|
||||
|
||||
if (state == Py_None) {
|
||||
goto exit;
|
||||
}
|
||||
if (!PyTuple_Check(state)) {
|
||||
PyErr_SetString(PyExc_TypeError, "state is not a tuple");
|
||||
goto error;
|
||||
}
|
||||
|
||||
size = PyTuple_GET_SIZE(state);
|
||||
|
||||
if (size > 0) {
|
||||
Py_CLEAR(self->name);
|
||||
self->name = PyTuple_GET_ITEM(state, 0);
|
||||
Py_INCREF(self->name);
|
||||
}
|
||||
if (size > 1) {
|
||||
Py_CLEAR(self->type_code);
|
||||
self->type_code = PyTuple_GET_ITEM(state, 1);
|
||||
Py_INCREF(self->type_code);
|
||||
}
|
||||
if (size > 2) {
|
||||
Py_CLEAR(self->display_size);
|
||||
self->display_size = PyTuple_GET_ITEM(state, 2);
|
||||
Py_INCREF(self->display_size);
|
||||
}
|
||||
if (size > 3) {
|
||||
Py_CLEAR(self->internal_size);
|
||||
self->internal_size = PyTuple_GET_ITEM(state, 3);
|
||||
Py_INCREF(self->internal_size);
|
||||
}
|
||||
if (size > 4) {
|
||||
Py_CLEAR(self->precision);
|
||||
self->precision = PyTuple_GET_ITEM(state, 4);
|
||||
Py_INCREF(self->precision);
|
||||
}
|
||||
if (size > 5) {
|
||||
Py_CLEAR(self->scale);
|
||||
self->scale = PyTuple_GET_ITEM(state, 5);
|
||||
Py_INCREF(self->scale);
|
||||
}
|
||||
if (size > 6) {
|
||||
Py_CLEAR(self->null_ok);
|
||||
self->null_ok = PyTuple_GET_ITEM(state, 6);
|
||||
Py_INCREF(self->null_ok);
|
||||
}
|
||||
if (size > 7) {
|
||||
Py_CLEAR(self->table_oid);
|
||||
self->table_oid = PyTuple_GET_ITEM(state, 7);
|
||||
Py_INCREF(self->table_oid);
|
||||
}
|
||||
if (size > 8) {
|
||||
Py_CLEAR(self->table_column);
|
||||
self->table_column = PyTuple_GET_ITEM(state, 8);
|
||||
Py_INCREF(self->table_column);
|
||||
}
|
||||
|
||||
exit:
|
||||
rv = Py_None;
|
||||
Py_INCREF(rv);
|
||||
|
||||
error:
|
||||
return rv;
|
||||
}
|
||||
|
||||
|
||||
static PyMethodDef column_methods[] = {
|
||||
/* Make Column picklable. */
|
||||
{"__getstate__", (PyCFunction)column_getstate, METH_NOARGS },
|
||||
{"__setstate__", (PyCFunction)column_setstate, METH_O },
|
||||
{NULL}
|
||||
};
|
||||
|
||||
|
||||
PyTypeObject columnType = {
|
||||
PyVarObject_HEAD_INIT(NULL, 0)
|
||||
"psycopg2.extensions.Column",
|
||||
sizeof(columnObject), 0,
|
||||
(destructor)column_dealloc, /* tp_dealloc */
|
||||
0, /*tp_print*/
|
||||
0, /*tp_getattr*/
|
||||
0, /*tp_setattr*/
|
||||
0, /*tp_compare*/
|
||||
(reprfunc)column_repr, /*tp_repr*/
|
||||
0, /*tp_as_number*/
|
||||
&column_sequence, /*tp_as_sequence*/
|
||||
&column_mapping, /*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*/
|
||||
column_doc, /*tp_doc*/
|
||||
0, /*tp_traverse*/
|
||||
0, /*tp_clear*/
|
||||
(richcmpfunc)column_richcompare, /*tp_richcompare*/
|
||||
0, /*tp_weaklistoffset*/
|
||||
0, /*tp_iter*/
|
||||
0, /*tp_iternext*/
|
||||
column_methods, /*tp_methods*/
|
||||
column_members, /*tp_members*/
|
||||
0, /*tp_getset*/
|
||||
0, /*tp_base*/
|
||||
0, /*tp_dict*/
|
||||
0, /*tp_descr_get*/
|
||||
0, /*tp_descr_set*/
|
||||
0, /*tp_dictoffset*/
|
||||
(initproc)column_init, /*tp_init*/
|
||||
0, /*tp_alloc*/
|
||||
column_new, /*tp_new*/
|
||||
};
|
||||
216
psycopg/config.h
Normal file
216
psycopg/config.h
Normal file
@ -0,0 +1,216 @@
|
||||
/* config.h - general config and Dprintf macro
|
||||
*
|
||||
* Copyright (C) 2003-2019 Federico Di Gregorio <fog@debian.org>
|
||||
* Copyright (C) 2020-2021 The Psycopg Team
|
||||
*
|
||||
* This file is part of psycopg.
|
||||
*
|
||||
* psycopg2 is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give
|
||||
* permission to link this program with the OpenSSL library (or with
|
||||
* modified versions of OpenSSL that use the same license as OpenSSL),
|
||||
* and distribute linked combinations including the two.
|
||||
*
|
||||
* You must obey the GNU Lesser General Public License in all respects for
|
||||
* all of the code used other than OpenSSL.
|
||||
*
|
||||
* psycopg2 is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
|
||||
* License for more details.
|
||||
*/
|
||||
|
||||
#ifndef PSYCOPG_CONFIG_H
|
||||
#define PSYCOPG_CONFIG_H 1
|
||||
|
||||
/* GCC 4.0 and later have support for specifying symbol visibility */
|
||||
#if __GNUC__ >= 4 && !defined(__MINGW32__)
|
||||
# define HIDDEN __attribute__((visibility("hidden")))
|
||||
#else
|
||||
# define HIDDEN
|
||||
#endif
|
||||
|
||||
/* support for getpid() */
|
||||
#if defined( __GNUC__)
|
||||
#define CONN_CHECK_PID
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
#ifdef _WIN32
|
||||
/* Windows doesn't seem affected by bug #829: just make it compile. */
|
||||
#define pid_t int
|
||||
#endif
|
||||
|
||||
|
||||
/* debug printf-like function */
|
||||
#ifdef PSYCOPG_DEBUG
|
||||
extern HIDDEN int psycopg_debug_enabled;
|
||||
#endif
|
||||
|
||||
#if defined( __GNUC__) && !defined(__APPLE__)
|
||||
#ifdef PSYCOPG_DEBUG
|
||||
#define Dprintf(fmt, args...) \
|
||||
if (!psycopg_debug_enabled) ; else \
|
||||
fprintf(stderr, "[%d] " fmt "\n", (int) getpid() , ## args)
|
||||
#else
|
||||
#define Dprintf(fmt, args...)
|
||||
#endif
|
||||
#else /* !__GNUC__ or __APPLE__ */
|
||||
#ifdef PSYCOPG_DEBUG
|
||||
#include <stdarg.h>
|
||||
#ifdef _WIN32
|
||||
#include <process.h>
|
||||
#define getpid _getpid
|
||||
#endif
|
||||
static void Dprintf(const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
if (!psycopg_debug_enabled)
|
||||
return;
|
||||
printf("[%d] ", (int) getpid());
|
||||
va_start(ap, fmt);
|
||||
vprintf(fmt, ap);
|
||||
va_end(ap);
|
||||
printf("\n");
|
||||
}
|
||||
#else
|
||||
static void Dprintf(const char *fmt, ...) {}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/* pthreads work-arounds for mutilated operating systems */
|
||||
#if defined(_WIN32) || defined(__BEOS__)
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
/* A Python extension should be linked to only one C runtime: the same one as
|
||||
* the Python interpreter itself. Straightforwardly using the strdup function
|
||||
* causes MinGW to implicitly link to the msvcrt.dll, which is not appropriate
|
||||
* for any Python version later than 2.3.
|
||||
* Microsoft C runtimes for Windows 98 and later make a _strdup function
|
||||
* available, which replaces the "normal" strdup. If we replace all psycopg
|
||||
* calls to strdup with calls to _strdup, MinGW no longer implicitly links to
|
||||
* the obsolete C runtime. */
|
||||
#define strdup _strdup
|
||||
|
||||
#include <winsock2.h>
|
||||
#define pthread_mutex_t HANDLE
|
||||
#define pthread_condvar_t HANDLE
|
||||
#define pthread_mutex_lock(object) WaitForSingleObject(*(object), INFINITE)
|
||||
#define pthread_mutex_unlock(object) ReleaseMutex(*(object))
|
||||
#define pthread_mutex_destroy(ref) (CloseHandle(*(ref)))
|
||||
/* convert pthread mutex to native mutex */
|
||||
static int pthread_mutex_init(pthread_mutex_t *mutex, void* fake)
|
||||
{
|
||||
*mutex = CreateMutex(NULL, FALSE, NULL);
|
||||
return 0;
|
||||
}
|
||||
#endif /* _WIN32 */
|
||||
|
||||
#ifdef __BEOS__
|
||||
#include <OS.h>
|
||||
#define pthread_mutex_t sem_id
|
||||
#define pthread_mutex_lock(object) acquire_sem(object)
|
||||
#define pthread_mutex_unlock(object) release_sem(object)
|
||||
#define pthread_mutex_destroy(ref) delete_sem(*ref)
|
||||
static int pthread_mutex_init(pthread_mutex_t *mutex, void* fake)
|
||||
{
|
||||
*mutex = create_sem(1, "psycopg_mutex");
|
||||
if (*mutex < B_OK)
|
||||
return *mutex;
|
||||
return 0;
|
||||
}
|
||||
#endif /* __BEOS__ */
|
||||
|
||||
#else /* pthread is available */
|
||||
#include <pthread.h>
|
||||
#endif
|
||||
|
||||
/* to work around the fact that Windows does not have a gmtime_r function, or
|
||||
a proper gmtime function */
|
||||
#ifdef _WIN32
|
||||
#define gmtime_r(t, tm) (gmtime(t)?memcpy((tm), gmtime(t), sizeof(*(tm))):NULL)
|
||||
#define localtime_r(t, tm) (localtime(t)?memcpy((tm), localtime(t), sizeof(*(tm))):NULL)
|
||||
|
||||
/* remove the inline keyword, since it doesn't work unless C++ file */
|
||||
#define inline
|
||||
|
||||
/* Hmmm, MSVC <2015 doesn't have a isnan/isinf function, but has _isnan function */
|
||||
#if defined (_MSC_VER)
|
||||
#if !defined(isnan)
|
||||
#define isnan(x) (_isnan(x))
|
||||
/* The following line was hacked together from simliar code by Bjorn Reese
|
||||
* in libxml2 code */
|
||||
#define isinf(x) ((_fpclass(x) == _FPCLASS_PINF) ? 1 \
|
||||
: ((_fpclass(x) == _FPCLASS_NINF) ? -1 : 0))
|
||||
#endif
|
||||
#define strcasecmp(x, y) lstrcmpi(x, y)
|
||||
|
||||
typedef __int8 int8_t;
|
||||
typedef __int16 int16_t;
|
||||
typedef __int32 int32_t;
|
||||
typedef __int64 int64_t;
|
||||
typedef unsigned __int8 uint8_t;
|
||||
typedef unsigned __int16 uint16_t;
|
||||
typedef unsigned __int32 uint32_t;
|
||||
typedef unsigned __int64 uint64_t;
|
||||
#endif
|
||||
|
||||
#include "win32_support.h"
|
||||
#endif
|
||||
|
||||
/* what's this, we have no round function either? */
|
||||
#if (defined(_WIN32) && !defined(__GNUC__)) \
|
||||
|| (defined(sun) || defined(__sun__)) \
|
||||
&& (defined(__SunOS_5_8) || defined(__SunOS_5_9))
|
||||
|
||||
/* round has been added in the standard library with MSVC 2015 */
|
||||
#if _MSC_VER < 1900
|
||||
static double round(double num)
|
||||
{
|
||||
return (num >= 0) ? floor(num + 0.5) : ceil(num - 0.5);
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/* resolve missing isinf() function for Solaris */
|
||||
#if defined (__SVR4) && defined (__sun)
|
||||
#include <ieeefp.h>
|
||||
#define isinf(x) (!finite((x)) && (x)==(x))
|
||||
#endif
|
||||
|
||||
/* decorators for the gcc cpychecker plugin */
|
||||
#if defined(WITH_CPYCHECKER_RETURNS_BORROWED_REF_ATTRIBUTE)
|
||||
#define BORROWED \
|
||||
__attribute__((cpychecker_returns_borrowed_ref))
|
||||
#else
|
||||
#define BORROWED
|
||||
#endif
|
||||
|
||||
#if defined(WITH_CPYCHECKER_STEALS_REFERENCE_TO_ARG_ATTRIBUTE)
|
||||
#define STEALS(n) \
|
||||
__attribute__((cpychecker_steals_reference_to_arg(n)))
|
||||
#else
|
||||
#define STEALS(n)
|
||||
#endif
|
||||
|
||||
#if defined(WITH_CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION_ATTRIBUTE)
|
||||
#define RAISES_NEG \
|
||||
__attribute__((cpychecker_negative_result_sets_exception))
|
||||
#else
|
||||
#define RAISES_NEG
|
||||
#endif
|
||||
|
||||
#if defined(WITH_CPYCHECKER_SETS_EXCEPTION_ATTRIBUTE)
|
||||
#define RAISES \
|
||||
__attribute__((cpychecker_sets_exception))
|
||||
#else
|
||||
#define RAISES
|
||||
#endif
|
||||
|
||||
#endif /* !defined(PSYCOPG_CONFIG_H) */
|
||||
229
psycopg/connection.h
Normal file
229
psycopg/connection.h
Normal file
@ -0,0 +1,229 @@
|
||||
/* connection.h - definition for the psycopg connection type
|
||||
*
|
||||
* Copyright (C) 2003-2019 Federico Di Gregorio <fog@debian.org>
|
||||
* Copyright (C) 2020-2021 The Psycopg Team
|
||||
*
|
||||
* This file is part of psycopg.
|
||||
*
|
||||
* psycopg2 is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give
|
||||
* permission to link this program with the OpenSSL library (or with
|
||||
* modified versions of OpenSSL that use the same license as OpenSSL),
|
||||
* and distribute linked combinations including the two.
|
||||
*
|
||||
* You must obey the GNU Lesser General Public License in all respects for
|
||||
* all of the code used other than OpenSSL.
|
||||
*
|
||||
* psycopg2 is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
|
||||
* License for more details.
|
||||
*/
|
||||
|
||||
#ifndef PSYCOPG_CONNECTION_H
|
||||
#define PSYCOPG_CONNECTION_H 1
|
||||
|
||||
#include "psycopg/xid.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* isolation levels */
|
||||
#define ISOLATION_LEVEL_AUTOCOMMIT 0
|
||||
#define ISOLATION_LEVEL_READ_UNCOMMITTED 4
|
||||
#define ISOLATION_LEVEL_READ_COMMITTED 1
|
||||
#define ISOLATION_LEVEL_REPEATABLE_READ 2
|
||||
#define ISOLATION_LEVEL_SERIALIZABLE 3
|
||||
#define ISOLATION_LEVEL_DEFAULT 5
|
||||
|
||||
/* 3-state values on/off/default */
|
||||
#define STATE_OFF 0
|
||||
#define STATE_ON 1
|
||||
#define STATE_DEFAULT 2
|
||||
|
||||
/* connection status */
|
||||
#define CONN_STATUS_SETUP 0
|
||||
#define CONN_STATUS_READY 1
|
||||
#define CONN_STATUS_BEGIN 2
|
||||
#define CONN_STATUS_PREPARED 5
|
||||
/* async connection building statuses */
|
||||
#define CONN_STATUS_CONNECTING 20
|
||||
#define CONN_STATUS_DATESTYLE 21
|
||||
|
||||
/* async query execution status */
|
||||
#define ASYNC_DONE 0
|
||||
#define ASYNC_READ 1
|
||||
#define ASYNC_WRITE 2
|
||||
|
||||
/* polling result */
|
||||
#define PSYCO_POLL_OK 0
|
||||
#define PSYCO_POLL_READ 1
|
||||
#define PSYCO_POLL_WRITE 2
|
||||
#define PSYCO_POLL_ERROR 3
|
||||
|
||||
/* Hard limit on the notices stored by the Python connection */
|
||||
#define CONN_NOTICES_LIMIT 50
|
||||
|
||||
/* we need the initial date style to be ISO, for typecasters; if the user
|
||||
later change it, she must know what she's doing... these are the queries we
|
||||
need to issue */
|
||||
#define psyco_datestyle "SET DATESTYLE TO 'ISO'"
|
||||
|
||||
extern HIDDEN PyTypeObject connectionType;
|
||||
|
||||
struct connectionObject_notice {
|
||||
struct connectionObject_notice *next;
|
||||
char *message;
|
||||
};
|
||||
|
||||
/* the typedef is forward-declared in psycopg.h */
|
||||
struct connectionObject {
|
||||
PyObject_HEAD
|
||||
|
||||
pthread_mutex_t lock; /* the global connection lock */
|
||||
|
||||
char *dsn; /* data source name */
|
||||
char *error; /* temporarily stored error before raising */
|
||||
char *encoding; /* current backend encoding */
|
||||
|
||||
long int closed; /* 1 means connection has been closed;
|
||||
2 that something horrible happened */
|
||||
long int mark; /* number of commits/rollbacks done so far */
|
||||
int status; /* status of the connection */
|
||||
xidObject *tpc_xid; /* Transaction ID in two-phase commit */
|
||||
|
||||
long int async; /* 1 means the connection is async */
|
||||
int protocol; /* protocol version */
|
||||
int server_version; /* server version */
|
||||
|
||||
PGconn *pgconn; /* the postgresql connection */
|
||||
PGcancel *cancel; /* the cancellation structure */
|
||||
|
||||
/* Weakref to the object executing an asynchronous query. The object
|
||||
* is a cursor for async connections, but it may be something else
|
||||
* for a green connection. If NULL, the connection is idle. */
|
||||
PyObject *async_cursor;
|
||||
int async_status; /* asynchronous execution status */
|
||||
PGresult *pgres; /* temporary result across async calls */
|
||||
|
||||
/* notice processing */
|
||||
PyObject *notice_list;
|
||||
struct connectionObject_notice *notice_pending;
|
||||
struct connectionObject_notice *last_notice;
|
||||
|
||||
/* notifies */
|
||||
PyObject *notifies;
|
||||
|
||||
/* per-connection typecasters */
|
||||
PyObject *string_types; /* a set of typecasters for string types */
|
||||
PyObject *binary_types; /* a set of typecasters for binary types */
|
||||
|
||||
int equote; /* use E''-style quotes for escaped strings */
|
||||
PyObject *weakreflist; /* list of weak references */
|
||||
|
||||
int autocommit;
|
||||
|
||||
PyObject *cursor_factory; /* default cursor factory from cursor() */
|
||||
|
||||
/* Optional pointer to a decoding C function, e.g. PyUnicode_DecodeUTF8 */
|
||||
PyObject *(*cdecoder)(const char *, Py_ssize_t, const char *);
|
||||
|
||||
/* Pointers to python encoding/decoding functions, e.g.
|
||||
* codecs.getdecoder('utf8') */
|
||||
PyObject *pyencoder; /* python codec encoding function */
|
||||
PyObject *pydecoder; /* python codec decoding function */
|
||||
|
||||
/* Values for the transactions characteristics */
|
||||
int isolevel;
|
||||
int readonly;
|
||||
int deferrable;
|
||||
|
||||
/* the pid this connection was created into */
|
||||
pid_t procpid;
|
||||
|
||||
/* inside a with block */
|
||||
int entered;
|
||||
};
|
||||
|
||||
/* map isolation level values into a numeric const */
|
||||
typedef struct {
|
||||
char *name;
|
||||
int value;
|
||||
} IsolationLevel;
|
||||
|
||||
/* C-callable functions in connection_int.c and connection_ext.c */
|
||||
HIDDEN PyObject *conn_text_from_chars(connectionObject *pgconn, const char *str);
|
||||
HIDDEN PyObject *conn_encode(connectionObject *self, PyObject *b);
|
||||
HIDDEN PyObject *conn_decode(connectionObject *self, const char *str, Py_ssize_t len);
|
||||
HIDDEN int conn_get_standard_conforming_strings(PGconn *pgconn);
|
||||
HIDDEN PyObject *conn_pgenc_to_pyenc(const char *encoding, char **clean_encoding);
|
||||
HIDDEN int conn_get_protocol_version(PGconn *pgconn);
|
||||
HIDDEN int conn_get_server_version(PGconn *pgconn);
|
||||
HIDDEN void conn_notice_process(connectionObject *self);
|
||||
HIDDEN void conn_notice_clean(connectionObject *self);
|
||||
HIDDEN void conn_notifies_process(connectionObject *self);
|
||||
RAISES_NEG HIDDEN int conn_setup(connectionObject *self);
|
||||
HIDDEN int conn_connect(connectionObject *self, const char *dsn, long int async);
|
||||
HIDDEN char *conn_obscure_password(const char *dsn);
|
||||
HIDDEN void conn_close(connectionObject *self);
|
||||
HIDDEN void conn_close_locked(connectionObject *self);
|
||||
RAISES_NEG HIDDEN int conn_commit(connectionObject *self);
|
||||
RAISES_NEG HIDDEN int conn_rollback(connectionObject *self);
|
||||
RAISES_NEG HIDDEN int conn_set_session(connectionObject *self, int autocommit,
|
||||
int isolevel, int readonly, int deferrable);
|
||||
RAISES_NEG HIDDEN int conn_set_client_encoding(connectionObject *self, const char *enc);
|
||||
HIDDEN int conn_poll(connectionObject *self);
|
||||
RAISES_NEG HIDDEN int conn_tpc_begin(connectionObject *self, xidObject *xid);
|
||||
RAISES_NEG HIDDEN int conn_tpc_command(connectionObject *self,
|
||||
const char *cmd, xidObject *xid);
|
||||
HIDDEN PyObject *conn_tpc_recover(connectionObject *self);
|
||||
HIDDEN void conn_set_result(connectionObject *self, PGresult *pgres);
|
||||
HIDDEN void conn_set_error(connectionObject *self, const char *msg);
|
||||
|
||||
/* exception-raising macros */
|
||||
#define EXC_IF_CONN_CLOSED(self) if ((self)->closed > 0) { \
|
||||
PyErr_SetString(InterfaceError, "connection already closed"); \
|
||||
return NULL; }
|
||||
|
||||
#define EXC_IF_CONN_ASYNC(self, cmd) if ((self)->async == 1) { \
|
||||
PyErr_SetString(ProgrammingError, #cmd " cannot be used " \
|
||||
"in asynchronous mode"); \
|
||||
return NULL; }
|
||||
|
||||
#define EXC_IF_IN_TRANSACTION(self, cmd) \
|
||||
if (self->status != CONN_STATUS_READY) { \
|
||||
PyErr_Format(ProgrammingError, \
|
||||
"%s cannot be used inside a transaction", #cmd); \
|
||||
return NULL; \
|
||||
}
|
||||
|
||||
#define EXC_IF_TPC_NOT_SUPPORTED(self) \
|
||||
if ((self)->server_version < 80100) { \
|
||||
PyErr_Format(NotSupportedError, \
|
||||
"server version %d: " \
|
||||
"two-phase transactions not supported", \
|
||||
(self)->server_version); \
|
||||
return NULL; \
|
||||
}
|
||||
|
||||
#define EXC_IF_TPC_BEGIN(self, cmd) if ((self)->tpc_xid) { \
|
||||
PyErr_Format(ProgrammingError, "%s cannot be used " \
|
||||
"during a two-phase transaction", #cmd); \
|
||||
return NULL; }
|
||||
|
||||
#define EXC_IF_TPC_PREPARED(self, cmd) \
|
||||
if ((self)->status == CONN_STATUS_PREPARED) { \
|
||||
PyErr_Format(ProgrammingError, "%s cannot be used " \
|
||||
"with a prepared two-phase transaction", #cmd); \
|
||||
return NULL; }
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* !defined(PSYCOPG_CONNECTION_H) */
|
||||
1553
psycopg/connection_int.c
Normal file
1553
psycopg/connection_int.c
Normal file
File diff suppressed because it is too large
Load Diff
1517
psycopg/connection_type.c
Normal file
1517
psycopg/connection_type.c
Normal file
File diff suppressed because it is too large
Load Diff
41
psycopg/conninfo.h
Normal file
41
psycopg/conninfo.h
Normal file
@ -0,0 +1,41 @@
|
||||
/* connection.h - definition for the psycopg ConnectionInfo type
|
||||
*
|
||||
* Copyright (C) 2018-2019 Daniele Varrazzo <daniele.varrazzo@gmail.com>
|
||||
* Copyright (C) 2020-2021 The Psycopg Team
|
||||
*
|
||||
* This file is part of psycopg.
|
||||
*
|
||||
* psycopg2 is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give
|
||||
* permission to link this program with the OpenSSL library (or with
|
||||
* modified versions of OpenSSL that use the same license as OpenSSL),
|
||||
* and distribute linked combinations including the two.
|
||||
*
|
||||
* You must obey the GNU Lesser General Public License in all respects for
|
||||
* all of the code used other than OpenSSL.
|
||||
*
|
||||
* psycopg2 is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
|
||||
* License for more details.
|
||||
*/
|
||||
|
||||
#ifndef PSYCOPG_CONNINFO_H
|
||||
#define PSYCOPG_CONNINFO_H 1
|
||||
|
||||
#include "psycopg/connection.h"
|
||||
|
||||
extern HIDDEN PyTypeObject connInfoType;
|
||||
|
||||
typedef struct {
|
||||
PyObject_HEAD
|
||||
|
||||
connectionObject *conn;
|
||||
|
||||
} connInfoObject;
|
||||
|
||||
#endif /* PSYCOPG_CONNINFO_H */
|
||||
648
psycopg/conninfo_type.c
Normal file
648
psycopg/conninfo_type.c
Normal file
@ -0,0 +1,648 @@
|
||||
/* conninfo_type.c - present information about the libpq connection
|
||||
*
|
||||
* Copyright (C) 2018-2019 Daniele Varrazzo <daniele.varrazzo@gmail.com>
|
||||
* Copyright (C) 2020-2021 The Psycopg Team
|
||||
*
|
||||
* This file is part of psycopg.
|
||||
*
|
||||
* psycopg2 is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give
|
||||
* permission to link this program with the OpenSSL library (or with
|
||||
* modified versions of OpenSSL that use the same license as OpenSSL),
|
||||
* and distribute linked combinations including the two.
|
||||
*
|
||||
* You must obey the GNU Lesser General Public License in all respects for
|
||||
* all of the code used other than OpenSSL.
|
||||
*
|
||||
* psycopg2 is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
|
||||
* License for more details.
|
||||
*/
|
||||
|
||||
#define PSYCOPG_MODULE
|
||||
#include "psycopg/psycopg.h"
|
||||
|
||||
#include "psycopg/conninfo.h"
|
||||
|
||||
|
||||
static const char connInfoType_doc[] =
|
||||
"Details about the native PostgreSQL database connection.\n"
|
||||
"\n"
|
||||
"This class exposes several `informative functions`__ about the status\n"
|
||||
"of the libpq connection.\n"
|
||||
"\n"
|
||||
"Objects of this class are exposed as the `connection.info` attribute.\n"
|
||||
"\n"
|
||||
".. __: https://www.postgresql.org/docs/current/static/libpq-status.html";
|
||||
|
||||
|
||||
static const char dbname_doc[] =
|
||||
"The database name of the connection.\n"
|
||||
"\n"
|
||||
".. seealso:: libpq docs for `PQdb()`__ for details.\n"
|
||||
".. __: https://www.postgresql.org/docs/current/static/libpq-status.html"
|
||||
"#LIBPQ-PQDB";
|
||||
|
||||
static PyObject *
|
||||
dbname_get(connInfoObject *self)
|
||||
{
|
||||
const char *val;
|
||||
|
||||
val = PQdb(self->conn->pgconn);
|
||||
if (!val) {
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
return conn_text_from_chars(self->conn, val);
|
||||
}
|
||||
|
||||
|
||||
static const char user_doc[] =
|
||||
"The user name of the connection.\n"
|
||||
"\n"
|
||||
".. seealso:: libpq docs for `PQuser()`__ for details.\n"
|
||||
".. __: https://www.postgresql.org/docs/current/static/libpq-status.html"
|
||||
"#LIBPQ-PQUSER";
|
||||
|
||||
static PyObject *
|
||||
user_get(connInfoObject *self)
|
||||
{
|
||||
const char *val;
|
||||
|
||||
val = PQuser(self->conn->pgconn);
|
||||
if (!val) {
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
return conn_text_from_chars(self->conn, val);
|
||||
}
|
||||
|
||||
|
||||
static const char password_doc[] =
|
||||
"The password of the connection.\n"
|
||||
"\n"
|
||||
".. seealso:: libpq docs for `PQpass()`__ for details.\n"
|
||||
".. __: https://www.postgresql.org/docs/current/static/libpq-status.html"
|
||||
"#LIBPQ-PQPASS";
|
||||
|
||||
static PyObject *
|
||||
password_get(connInfoObject *self)
|
||||
{
|
||||
const char *val;
|
||||
|
||||
val = PQpass(self->conn->pgconn);
|
||||
if (!val) {
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
return conn_text_from_chars(self->conn, val);
|
||||
}
|
||||
|
||||
|
||||
static const char host_doc[] =
|
||||
"The server host name of the connection.\n"
|
||||
"\n"
|
||||
"This can be a host name, an IP address, or a directory path if the\n"
|
||||
"connection is via Unix socket. (The path case can be distinguished\n"
|
||||
"because it will always be an absolute path, beginning with ``/``.)\n"
|
||||
"\n"
|
||||
".. seealso:: libpq docs for `PQhost()`__ for details.\n"
|
||||
".. __: https://www.postgresql.org/docs/current/static/libpq-status.html"
|
||||
"#LIBPQ-PQHOST";
|
||||
|
||||
static PyObject *
|
||||
host_get(connInfoObject *self)
|
||||
{
|
||||
const char *val;
|
||||
|
||||
val = PQhost(self->conn->pgconn);
|
||||
if (!val) {
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
return conn_text_from_chars(self->conn, val);
|
||||
}
|
||||
|
||||
|
||||
static const char port_doc[] =
|
||||
"The port of the connection.\n"
|
||||
"\n"
|
||||
":type: `!int`\n"
|
||||
"\n"
|
||||
".. seealso:: libpq docs for `PQport()`__ for details.\n"
|
||||
".. __: https://www.postgresql.org/docs/current/static/libpq-status.html"
|
||||
"#LIBPQ-PQPORT";
|
||||
|
||||
static PyObject *
|
||||
port_get(connInfoObject *self)
|
||||
{
|
||||
const char *val;
|
||||
|
||||
val = PQport(self->conn->pgconn);
|
||||
if (!val || !val[0]) {
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
return PyInt_FromString((char *)val, NULL, 10);
|
||||
}
|
||||
|
||||
|
||||
static const char options_doc[] =
|
||||
"The command-line options passed in the connection request.\n"
|
||||
"\n"
|
||||
".. seealso:: libpq docs for `PQoptions()`__ for details.\n"
|
||||
".. __: https://www.postgresql.org/docs/current/static/libpq-status.html"
|
||||
"#LIBPQ-PQOPTIONS";
|
||||
|
||||
static PyObject *
|
||||
options_get(connInfoObject *self)
|
||||
{
|
||||
const char *val;
|
||||
|
||||
val = PQoptions(self->conn->pgconn);
|
||||
if (!val) {
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
return conn_text_from_chars(self->conn, val);
|
||||
}
|
||||
|
||||
|
||||
static const char dsn_parameters_doc[] =
|
||||
"The effective connection parameters.\n"
|
||||
"\n"
|
||||
":type: `!dict`\n"
|
||||
"\n"
|
||||
"The results include values which weren't explicitly set by the connection\n"
|
||||
"string, such as defaults, environment variables, etc.\n"
|
||||
"The *password* parameter is removed from the results.\n"
|
||||
"\n"
|
||||
".. seealso:: libpq docs for `PQconninfo()`__ for details.\n"
|
||||
".. __: https://www.postgresql.org/docs/current/libpq-connect.html"
|
||||
"#LIBPQ-PQCONNINFO";
|
||||
|
||||
static PyObject *
|
||||
dsn_parameters_get(connInfoObject *self)
|
||||
{
|
||||
#if PG_VERSION_NUM >= 90300
|
||||
PyObject *res = NULL;
|
||||
PQconninfoOption *options = NULL;
|
||||
|
||||
EXC_IF_CONN_CLOSED(self->conn);
|
||||
|
||||
if (!(options = PQconninfo(self->conn->pgconn))) {
|
||||
PyErr_NoMemory();
|
||||
goto exit;
|
||||
}
|
||||
|
||||
res = psyco_dict_from_conninfo_options(options, /* include_password = */ 0);
|
||||
|
||||
exit:
|
||||
PQconninfoFree(options);
|
||||
|
||||
return res;
|
||||
#else
|
||||
PyErr_SetString(NotSupportedError, "PQconninfo not available in libpq < 9.3");
|
||||
return NULL;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
static const char status_doc[] =
|
||||
"The status of the connection.\n"
|
||||
"\n"
|
||||
":type: `!int`\n"
|
||||
"\n"
|
||||
".. seealso:: libpq docs for `PQstatus()`__ for details.\n"
|
||||
".. __: https://www.postgresql.org/docs/current/static/libpq-status.html"
|
||||
"#LIBPQ-PQSTATUS";
|
||||
|
||||
static PyObject *
|
||||
status_get(connInfoObject *self)
|
||||
{
|
||||
ConnStatusType val;
|
||||
|
||||
val = PQstatus(self->conn->pgconn);
|
||||
return PyInt_FromLong((long)val);
|
||||
}
|
||||
|
||||
|
||||
static const char transaction_status_doc[] =
|
||||
"The current in-transaction status of the connection.\n"
|
||||
"\n"
|
||||
"Symbolic constants for the values are defined in the module\n"
|
||||
"`psycopg2.extensions`: see :ref:`transaction-status-constants` for the\n"
|
||||
"available values.\n"
|
||||
"\n"
|
||||
":type: `!int`\n"
|
||||
"\n"
|
||||
".. seealso:: libpq docs for `PQtransactionStatus()`__ for details.\n"
|
||||
".. __: https://www.postgresql.org/docs/current/static/libpq-status.html"
|
||||
"#LIBPQ-PQTRANSACTIONSTATUS";
|
||||
|
||||
static PyObject *
|
||||
transaction_status_get(connInfoObject *self)
|
||||
{
|
||||
PGTransactionStatusType val;
|
||||
|
||||
val = PQtransactionStatus(self->conn->pgconn);
|
||||
return PyInt_FromLong((long)val);
|
||||
}
|
||||
|
||||
|
||||
static const char parameter_status_doc[] =
|
||||
"Looks up a current parameter setting of the server.\n"
|
||||
"\n"
|
||||
":param name: The name of the parameter to return.\n"
|
||||
":type name: `!str`\n"
|
||||
":return: The parameter value, `!None` if the parameter is unknown.\n"
|
||||
":rtype: `!str`\n"
|
||||
"\n"
|
||||
".. seealso:: libpq docs for `PQparameterStatus()`__ for details.\n"
|
||||
".. __: https://www.postgresql.org/docs/current/static/libpq-status.html"
|
||||
"#LIBPQ-PQPARAMETERSTATUS";
|
||||
|
||||
static PyObject *
|
||||
parameter_status(connInfoObject *self, PyObject *args, PyObject *kwargs)
|
||||
{
|
||||
static char *kwlist[] = {"name", NULL};
|
||||
const char *name;
|
||||
const char *val;
|
||||
|
||||
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s", kwlist, &name)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
val = PQparameterStatus(self->conn->pgconn, name);
|
||||
|
||||
if (!val) {
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
else {
|
||||
return conn_text_from_chars(self->conn, val);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static const char protocol_version_doc[] =
|
||||
"The frontend/backend protocol being used.\n"
|
||||
"\n"
|
||||
":type: `!int`\n"
|
||||
"\n"
|
||||
".. seealso:: libpq docs for `PQprotocolVersion()`__ for details.\n"
|
||||
".. __: https://www.postgresql.org/docs/current/static/libpq-status.html"
|
||||
"#LIBPQ-PQPROTOCOLVERSION";
|
||||
|
||||
static PyObject *
|
||||
protocol_version_get(connInfoObject *self)
|
||||
{
|
||||
int val;
|
||||
|
||||
val = PQprotocolVersion(self->conn->pgconn);
|
||||
return PyInt_FromLong((long)val);
|
||||
}
|
||||
|
||||
|
||||
static const char server_version_doc[] =
|
||||
"Returns an integer representing the server version.\n"
|
||||
"\n"
|
||||
":type: `!int`\n"
|
||||
"\n"
|
||||
".. seealso:: libpq docs for `PQserverVersion()`__ for details.\n"
|
||||
".. __: https://www.postgresql.org/docs/current/static/libpq-status.html"
|
||||
"#LIBPQ-PQSERVERVERSION";
|
||||
|
||||
static PyObject *
|
||||
server_version_get(connInfoObject *self)
|
||||
{
|
||||
int val;
|
||||
|
||||
val = PQserverVersion(self->conn->pgconn);
|
||||
return PyInt_FromLong((long)val);
|
||||
}
|
||||
|
||||
|
||||
static const char error_message_doc[] =
|
||||
"The error message most recently generated by an operation on the connection.\n"
|
||||
"\n"
|
||||
"`!None` if there is no current message.\n"
|
||||
"\n"
|
||||
".. seealso:: libpq docs for `PQerrorMessage()`__ for details.\n"
|
||||
".. __: https://www.postgresql.org/docs/current/static/libpq-status.html"
|
||||
"#LIBPQ-PQERRORMESSAGE";
|
||||
|
||||
static PyObject *
|
||||
error_message_get(connInfoObject *self)
|
||||
{
|
||||
const char *val;
|
||||
|
||||
val = PQerrorMessage(self->conn->pgconn);
|
||||
if (!val || !val[0]) {
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
return conn_text_from_chars(self->conn, val);
|
||||
}
|
||||
|
||||
|
||||
static const char socket_doc[] =
|
||||
"The file descriptor number of the connection socket to the server.\n"
|
||||
"\n"
|
||||
":type: `!int`\n"
|
||||
"\n"
|
||||
".. seealso:: libpq docs for `PQsocket()`__ for details.\n"
|
||||
".. __: https://www.postgresql.org/docs/current/static/libpq-status.html"
|
||||
"#LIBPQ-PQSOCKET";
|
||||
|
||||
static PyObject *
|
||||
socket_get(connInfoObject *self)
|
||||
{
|
||||
int val;
|
||||
|
||||
val = PQsocket(self->conn->pgconn);
|
||||
return PyInt_FromLong((long)val);
|
||||
}
|
||||
|
||||
|
||||
static const char backend_pid_doc[] =
|
||||
"The process ID (PID) of the backend process you connected to.\n"
|
||||
"\n"
|
||||
":type: `!int`\n"
|
||||
"\n"
|
||||
".. seealso:: libpq docs for `PQbackendPID()`__ for details.\n"
|
||||
".. __: https://www.postgresql.org/docs/current/static/libpq-status.html"
|
||||
"#LIBPQ-PQBACKENDPID";
|
||||
|
||||
static PyObject *
|
||||
backend_pid_get(connInfoObject *self)
|
||||
{
|
||||
int val;
|
||||
|
||||
val = PQbackendPID(self->conn->pgconn);
|
||||
return PyInt_FromLong((long)val);
|
||||
}
|
||||
|
||||
|
||||
static const char needs_password_doc[] =
|
||||
"The connection authentication method required a password, but none was available.\n"
|
||||
"\n"
|
||||
":type: `!bool`\n"
|
||||
"\n"
|
||||
".. seealso:: libpq docs for `PQconnectionNeedsPassword()`__ for details.\n"
|
||||
".. __: https://www.postgresql.org/docs/current/static/libpq-status.html"
|
||||
"#LIBPQ-PQCONNECTIONNEEDSPASSWORD";
|
||||
|
||||
static PyObject *
|
||||
needs_password_get(connInfoObject *self)
|
||||
{
|
||||
return PyBool_FromLong(PQconnectionNeedsPassword(self->conn->pgconn));
|
||||
}
|
||||
|
||||
|
||||
static const char used_password_doc[] =
|
||||
"The connection authentication method used a password.\n"
|
||||
"\n"
|
||||
":type: `!bool`\n"
|
||||
"\n"
|
||||
".. seealso:: libpq docs for `PQconnectionUsedPassword()`__ for details.\n"
|
||||
".. __: https://www.postgresql.org/docs/current/static/libpq-status.html"
|
||||
"#LIBPQ-PQCONNECTIONUSEDPASSWORD";
|
||||
|
||||
static PyObject *
|
||||
used_password_get(connInfoObject *self)
|
||||
{
|
||||
return PyBool_FromLong(PQconnectionUsedPassword(self->conn->pgconn));
|
||||
}
|
||||
|
||||
|
||||
static const char ssl_in_use_doc[] =
|
||||
"`!True` if the connection uses SSL, `!False` if not.\n"
|
||||
"\n"
|
||||
"Only available if psycopg was built with libpq >= 9.5; raise\n"
|
||||
"`~psycopg2.NotSupportedError` otherwise.\n"
|
||||
"\n"
|
||||
":type: `!bool`\n"
|
||||
"\n"
|
||||
".. seealso:: libpq docs for `PQsslInUse()`__ for details.\n"
|
||||
".. __: https://www.postgresql.org/docs/current/static/libpq-status.html"
|
||||
"#LIBPQ-PQSSLINUSE";
|
||||
|
||||
static PyObject *
|
||||
ssl_in_use_get(connInfoObject *self)
|
||||
{
|
||||
PyObject *rv = NULL;
|
||||
|
||||
#if PG_VERSION_NUM >= 90500
|
||||
rv = PyBool_FromLong(PQsslInUse(self->conn->pgconn));
|
||||
#else
|
||||
PyErr_SetString(NotSupportedError,
|
||||
"'ssl_in_use' not available in libpq < 9.5");
|
||||
#endif
|
||||
return rv;
|
||||
}
|
||||
|
||||
|
||||
static const char ssl_attribute_doc[] =
|
||||
"Returns SSL-related information about the connection.\n"
|
||||
"\n"
|
||||
":param name: The name of the attribute to return.\n"
|
||||
":type name: `!str`\n"
|
||||
":return: The attribute value, `!None` if unknown.\n"
|
||||
":rtype: `!str`\n"
|
||||
"\n"
|
||||
"Only available if psycopg was built with libpq >= 9.5; raise\n"
|
||||
"`~psycopg2.NotSupportedError` otherwise.\n"
|
||||
"\n"
|
||||
"Valid names are available in `ssl_attribute_names`.\n"
|
||||
"\n"
|
||||
".. seealso:: libpq docs for `PQsslAttribute()`__ for details.\n"
|
||||
".. __: https://www.postgresql.org/docs/current/static/libpq-status.html"
|
||||
"#LIBPQ-PQSSLATTRIBUTE";
|
||||
|
||||
static PyObject *
|
||||
ssl_attribute(connInfoObject *self, PyObject *args, PyObject *kwargs)
|
||||
{
|
||||
static char *kwlist[] = {"name", NULL};
|
||||
const char *name;
|
||||
|
||||
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s", kwlist, &name)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#if PG_VERSION_NUM >= 90500
|
||||
{
|
||||
const char *val;
|
||||
|
||||
val = PQsslAttribute(self->conn->pgconn, name);
|
||||
|
||||
if (!val) {
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
else {
|
||||
return conn_text_from_chars(self->conn, val);
|
||||
}
|
||||
}
|
||||
#else
|
||||
PyErr_SetString(NotSupportedError,
|
||||
"'ssl_attribute()' not available in libpq < 9.5");
|
||||
return NULL;
|
||||
#endif
|
||||
}
|
||||
|
||||
static const char ssl_attribute_names_doc[] =
|
||||
"The list of the SSL attribute names available.\n"
|
||||
"\n"
|
||||
":type: `!list` of `!str`\n"
|
||||
"\n"
|
||||
"Only available if psycopg was built with libpq >= 9.5; raise\n"
|
||||
"`~psycopg2.NotSupportedError` otherwise.\n"
|
||||
"\n"
|
||||
".. seealso:: libpq docs for `PQsslAttributeNames()`__ for details.\n"
|
||||
".. __: https://www.postgresql.org/docs/current/static/libpq-status.html"
|
||||
"#LIBPQ-PQSSLATTRIBUTENAMES";
|
||||
|
||||
static PyObject *
|
||||
ssl_attribute_names_get(connInfoObject *self)
|
||||
{
|
||||
#if PG_VERSION_NUM >= 90500
|
||||
const char* const* names;
|
||||
int i;
|
||||
PyObject *l = NULL, *s = NULL, *rv = NULL;
|
||||
|
||||
names = PQsslAttributeNames(self->conn->pgconn);
|
||||
if (!(l = PyList_New(0))) { goto exit; }
|
||||
|
||||
for (i = 0; names[i]; i++) {
|
||||
if (!(s = conn_text_from_chars(self->conn, names[i]))) { goto exit; }
|
||||
if (0 != PyList_Append(l, s)) { goto exit; }
|
||||
Py_CLEAR(s);
|
||||
}
|
||||
|
||||
rv = l;
|
||||
l = NULL;
|
||||
|
||||
exit:
|
||||
Py_XDECREF(l);
|
||||
Py_XDECREF(s);
|
||||
return rv;
|
||||
#else
|
||||
PyErr_SetString(NotSupportedError,
|
||||
"'ssl_attribute_names not available in libpq < 9.5");
|
||||
return NULL;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
static struct PyGetSetDef connInfoObject_getsets[] = {
|
||||
{ "dbname", (getter)dbname_get, NULL, (char *)dbname_doc },
|
||||
{ "user", (getter)user_get, NULL, (char *)user_doc },
|
||||
{ "password", (getter)password_get, NULL, (char *)password_doc },
|
||||
{ "host", (getter)host_get, NULL, (char *)host_doc },
|
||||
{ "port", (getter)port_get, NULL, (char *)port_doc },
|
||||
{ "options", (getter)options_get, NULL, (char *)options_doc },
|
||||
{ "dsn_parameters", (getter)dsn_parameters_get, NULL,
|
||||
(char *)dsn_parameters_doc },
|
||||
{ "status", (getter)status_get, NULL, (char *)status_doc },
|
||||
{ "transaction_status", (getter)transaction_status_get, NULL,
|
||||
(char *)transaction_status_doc },
|
||||
{ "protocol_version", (getter)protocol_version_get, NULL,
|
||||
(char *)protocol_version_doc },
|
||||
{ "server_version", (getter)server_version_get, NULL,
|
||||
(char *)server_version_doc },
|
||||
{ "error_message", (getter)error_message_get, NULL,
|
||||
(char *)error_message_doc },
|
||||
{ "socket", (getter)socket_get, NULL, (char *)socket_doc },
|
||||
{ "backend_pid", (getter)backend_pid_get, NULL, (char *)backend_pid_doc },
|
||||
{ "used_password", (getter)used_password_get, NULL,
|
||||
(char *)used_password_doc },
|
||||
{ "needs_password", (getter)needs_password_get, NULL,
|
||||
(char *)needs_password_doc },
|
||||
{ "ssl_in_use", (getter)ssl_in_use_get, NULL,
|
||||
(char *)ssl_in_use_doc },
|
||||
{ "ssl_attribute_names", (getter)ssl_attribute_names_get, NULL,
|
||||
(char *)ssl_attribute_names_doc },
|
||||
{NULL}
|
||||
};
|
||||
|
||||
static struct PyMethodDef connInfoObject_methods[] = {
|
||||
{"ssl_attribute", (PyCFunction)ssl_attribute,
|
||||
METH_VARARGS|METH_KEYWORDS, ssl_attribute_doc},
|
||||
{"parameter_status", (PyCFunction)parameter_status,
|
||||
METH_VARARGS|METH_KEYWORDS, parameter_status_doc},
|
||||
{NULL}
|
||||
};
|
||||
|
||||
/* initialization and finalization methods */
|
||||
|
||||
static PyObject *
|
||||
conninfo_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
|
||||
{
|
||||
return type->tp_alloc(type, 0);
|
||||
}
|
||||
|
||||
static int
|
||||
conninfo_init(connInfoObject *self, PyObject *args, PyObject *kwds)
|
||||
{
|
||||
PyObject *conn = NULL;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "O", &conn))
|
||||
return -1;
|
||||
|
||||
if (!PyObject_TypeCheck(conn, &connectionType)) {
|
||||
PyErr_SetString(PyExc_TypeError,
|
||||
"The argument must be a psycopg2 connection");
|
||||
return -1;
|
||||
}
|
||||
|
||||
Py_INCREF(conn);
|
||||
self->conn = (connectionObject *)conn;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
conninfo_dealloc(connInfoObject* self)
|
||||
{
|
||||
Py_CLEAR(self->conn);
|
||||
Py_TYPE(self)->tp_free((PyObject *)self);
|
||||
}
|
||||
|
||||
|
||||
/* object type */
|
||||
|
||||
PyTypeObject connInfoType = {
|
||||
PyVarObject_HEAD_INIT(NULL, 0)
|
||||
"psycopg2.extensions.ConnectionInfo",
|
||||
sizeof(connInfoObject), 0,
|
||||
(destructor)conninfo_dealloc, /*tp_dealloc*/
|
||||
0, /*tp_print*/
|
||||
0, /*tp_getattr*/
|
||||
0, /*tp_setattr*/
|
||||
0, /*tp_compare*/
|
||||
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*/
|
||||
connInfoType_doc, /*tp_doc*/
|
||||
0, /*tp_traverse*/
|
||||
0, /*tp_clear*/
|
||||
0, /*tp_richcompare*/
|
||||
0, /*tp_weaklistoffset*/
|
||||
0, /*tp_iter*/
|
||||
0, /*tp_iternext*/
|
||||
connInfoObject_methods, /*tp_methods*/
|
||||
0, /*tp_members*/
|
||||
connInfoObject_getsets, /*tp_getset*/
|
||||
0, /*tp_base*/
|
||||
0, /*tp_dict*/
|
||||
0, /*tp_descr_get*/
|
||||
0, /*tp_descr_set*/
|
||||
0, /*tp_dictoffset*/
|
||||
(initproc)conninfo_init, /*tp_init*/
|
||||
0, /*tp_alloc*/
|
||||
conninfo_new, /*tp_new*/
|
||||
};
|
||||
147
psycopg/cursor.h
Normal file
147
psycopg/cursor.h
Normal file
@ -0,0 +1,147 @@
|
||||
/* cursor.h - definition for the psycopg cursor type
|
||||
*
|
||||
* Copyright (C) 2003-2019 Federico Di Gregorio <fog@debian.org>
|
||||
* Copyright (C) 2020-2021 The Psycopg Team
|
||||
*
|
||||
* This file is part of psycopg.
|
||||
*
|
||||
* psycopg2 is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give
|
||||
* permission to link this program with the OpenSSL library (or with
|
||||
* modified versions of OpenSSL that use the same license as OpenSSL),
|
||||
* and distribute linked combinations including the two.
|
||||
*
|
||||
* You must obey the GNU Lesser General Public License in all respects for
|
||||
* all of the code used other than OpenSSL.
|
||||
*
|
||||
* psycopg2 is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
|
||||
* License for more details.
|
||||
*/
|
||||
|
||||
#ifndef PSYCOPG_CURSOR_H
|
||||
#define PSYCOPG_CURSOR_H 1
|
||||
|
||||
#include "psycopg/connection.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
extern HIDDEN PyTypeObject cursorType;
|
||||
|
||||
/* the typedef is forward-declared in psycopg.h */
|
||||
struct cursorObject {
|
||||
PyObject_HEAD
|
||||
|
||||
connectionObject *conn; /* connection owning the cursor */
|
||||
|
||||
int closed:1; /* 1 if the cursor is closed */
|
||||
int notuples:1; /* 1 if the command was not a SELECT query */
|
||||
int withhold:1; /* 1 if the cursor is named and uses WITH HOLD */
|
||||
|
||||
int scrollable; /* 1 if the cursor is named and SCROLLABLE,
|
||||
0 if not scrollable
|
||||
-1 if undefined (PG may decide scrollable or not)
|
||||
*/
|
||||
|
||||
long int rowcount; /* number of rows affected by last execute */
|
||||
long int columns; /* number of columns fetched from the db */
|
||||
long int arraysize; /* how many rows should fetchmany() return */
|
||||
long int itersize; /* how many rows should iter(cur) fetch in named cursors */
|
||||
long int row; /* the row counter for fetch*() operations */
|
||||
long int mark; /* transaction marker, copied from conn */
|
||||
|
||||
PyObject *description; /* read-only attribute: sequence of 7-item
|
||||
sequences.*/
|
||||
|
||||
/* postgres connection stuff */
|
||||
PGresult *pgres; /* result of last query */
|
||||
PyObject *pgstatus; /* last message from the server after an execute */
|
||||
Oid lastoid; /* last oid from an insert or InvalidOid */
|
||||
|
||||
PyObject *casts; /* an array (tuple) of typecast functions */
|
||||
PyObject *caster; /* the current typecaster object */
|
||||
|
||||
PyObject *copyfile; /* file-like used during COPY TO/FROM ops */
|
||||
Py_ssize_t copysize; /* size of the copy buffer during COPY TO/FROM ops */
|
||||
#define DEFAULT_COPYSIZE 16384
|
||||
#define DEFAULT_COPYBUFF 8192
|
||||
|
||||
PyObject *tuple_factory; /* factory for result tuples */
|
||||
PyObject *tzinfo_factory; /* factory for tzinfo objects */
|
||||
|
||||
PyObject *query; /* last query executed */
|
||||
|
||||
char *qattr; /* quoting attr, used when quoting strings */
|
||||
char *notice; /* a notice from the backend */
|
||||
char *name; /* this cursor name */
|
||||
char *qname; /* this cursor name, quoted */
|
||||
|
||||
PyObject *string_types; /* a set of typecasters for string types */
|
||||
PyObject *binary_types; /* a set of typecasters for binary types */
|
||||
|
||||
PyObject *weakreflist; /* list of weak references */
|
||||
|
||||
};
|
||||
|
||||
|
||||
/* C-callable functions in cursor_int.c and cursor_type.c */
|
||||
BORROWED HIDDEN PyObject *curs_get_cast(cursorObject *self, PyObject *oid);
|
||||
HIDDEN void curs_reset(cursorObject *self);
|
||||
RAISES_NEG HIDDEN int curs_withhold_set(cursorObject *self, PyObject *pyvalue);
|
||||
RAISES_NEG HIDDEN int curs_scrollable_set(cursorObject *self, PyObject *pyvalue);
|
||||
HIDDEN PyObject *curs_validate_sql_basic(cursorObject *self, PyObject *sql);
|
||||
HIDDEN void curs_set_result(cursorObject *self, PGresult *pgres);
|
||||
|
||||
/* exception-raising macros */
|
||||
#define EXC_IF_CURS_CLOSED(self) \
|
||||
do { \
|
||||
if (!(self)->conn) { \
|
||||
PyErr_SetString(InterfaceError, "the cursor has no connection"); \
|
||||
return NULL; } \
|
||||
if ((self)->closed || (self)->conn->closed) { \
|
||||
PyErr_SetString(InterfaceError, "cursor already closed"); \
|
||||
return NULL; } \
|
||||
} while (0)
|
||||
|
||||
#define EXC_IF_NO_TUPLES(self) \
|
||||
do \
|
||||
if ((self)->notuples && (self)->name == NULL) { \
|
||||
PyErr_SetString(ProgrammingError, "no results to fetch"); \
|
||||
return NULL; } \
|
||||
while (0)
|
||||
|
||||
#define EXC_IF_NO_MARK(self) \
|
||||
do \
|
||||
if ((self)->mark != (self)->conn->mark && (self)->withhold == 0) { \
|
||||
PyErr_SetString(ProgrammingError, "named cursor isn't valid anymore"); \
|
||||
return NULL; } \
|
||||
while (0)
|
||||
|
||||
#define EXC_IF_CURS_ASYNC(self, cmd) \
|
||||
do \
|
||||
if ((self)->conn->async == 1) { \
|
||||
PyErr_SetString(ProgrammingError, \
|
||||
#cmd " cannot be used in asynchronous mode"); \
|
||||
return NULL; } \
|
||||
while (0)
|
||||
|
||||
#define EXC_IF_ASYNC_IN_PROGRESS(self, cmd) \
|
||||
do \
|
||||
if ((self)->conn->async_cursor != NULL) { \
|
||||
PyErr_SetString(ProgrammingError, \
|
||||
#cmd " cannot be used while an asynchronous query is underway"); \
|
||||
return NULL; } \
|
||||
while (0)
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* !defined(PSYCOPG_CURSOR_H) */
|
||||
171
psycopg/cursor_int.c
Normal file
171
psycopg/cursor_int.c
Normal file
@ -0,0 +1,171 @@
|
||||
/* cursor_int.c - code used by the cursor object
|
||||
*
|
||||
* Copyright (C) 2003-2019 Federico Di Gregorio <fog@debian.org>
|
||||
* Copyright (C) 2020-2021 The Psycopg Team
|
||||
*
|
||||
* This file is part of psycopg.
|
||||
*
|
||||
* psycopg2 is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give
|
||||
* permission to link this program with the OpenSSL library (or with
|
||||
* modified versions of OpenSSL that use the same license as OpenSSL),
|
||||
* and distribute linked combinations including the two.
|
||||
*
|
||||
* You must obey the GNU Lesser General Public License in all respects for
|
||||
* all of the code used other than OpenSSL.
|
||||
*
|
||||
* psycopg2 is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
|
||||
* License for more details.
|
||||
*/
|
||||
|
||||
#define PSYCOPG_MODULE
|
||||
#include "psycopg/psycopg.h"
|
||||
|
||||
#include "psycopg/cursor.h"
|
||||
#include "psycopg/pqpath.h"
|
||||
#include "psycopg/typecast.h"
|
||||
|
||||
/* curs_get_cast - return the type caster for an oid.
|
||||
*
|
||||
* Return the most specific type caster, from cursor to connection to global.
|
||||
* If no type caster is found, return the default one.
|
||||
*
|
||||
* Return a borrowed reference.
|
||||
*/
|
||||
|
||||
BORROWED PyObject *
|
||||
curs_get_cast(cursorObject *self, PyObject *oid)
|
||||
{
|
||||
PyObject *cast;
|
||||
|
||||
/* cursor lookup */
|
||||
if (self->string_types != NULL && self->string_types != Py_None) {
|
||||
cast = PyDict_GetItem(self->string_types, oid);
|
||||
Dprintf("curs_get_cast: per-cursor dict: %p", cast);
|
||||
if (cast) { return cast; }
|
||||
}
|
||||
|
||||
/* connection lookup */
|
||||
cast = PyDict_GetItem(self->conn->string_types, oid);
|
||||
Dprintf("curs_get_cast: per-connection dict: %p", cast);
|
||||
if (cast) { return cast; }
|
||||
|
||||
/* global lookup */
|
||||
cast = PyDict_GetItem(psyco_types, oid);
|
||||
Dprintf("curs_get_cast: global dict: %p", cast);
|
||||
if (cast) { return cast; }
|
||||
|
||||
/* fallback */
|
||||
return psyco_default_cast;
|
||||
}
|
||||
|
||||
#include <string.h>
|
||||
|
||||
|
||||
/* curs_reset - reset the cursor to a clean state */
|
||||
|
||||
void
|
||||
curs_reset(cursorObject *self)
|
||||
{
|
||||
/* initialize some variables to default values */
|
||||
self->notuples = 1;
|
||||
self->rowcount = -1;
|
||||
self->row = 0;
|
||||
|
||||
Py_CLEAR(self->description);
|
||||
Py_CLEAR(self->casts);
|
||||
}
|
||||
|
||||
|
||||
/* Return 1 if `obj` is a `psycopg2.sql.Composable` instance, else 0
|
||||
* Set an exception and return -1 in case of error.
|
||||
*/
|
||||
RAISES_NEG static int
|
||||
_curs_is_composible(PyObject *obj)
|
||||
{
|
||||
int rv = -1;
|
||||
PyObject *m = NULL;
|
||||
PyObject *comp = NULL;
|
||||
|
||||
if (!(m = PyImport_ImportModule("psycopg2.sql"))) { goto exit; }
|
||||
if (!(comp = PyObject_GetAttrString(m, "Composable"))) { goto exit; }
|
||||
rv = PyObject_IsInstance(obj, comp);
|
||||
|
||||
exit:
|
||||
Py_XDECREF(comp);
|
||||
Py_XDECREF(m);
|
||||
return rv;
|
||||
|
||||
}
|
||||
|
||||
/* Performs very basic validation on an incoming SQL string.
|
||||
* Returns a new reference to a str instance on success; NULL on failure,
|
||||
* after having set an exception.
|
||||
*/
|
||||
PyObject *
|
||||
curs_validate_sql_basic(cursorObject *self, PyObject *sql)
|
||||
{
|
||||
PyObject *rv = NULL;
|
||||
PyObject *comp = NULL;
|
||||
int iscomp;
|
||||
|
||||
if (!sql || !PyObject_IsTrue(sql)) {
|
||||
psyco_set_error(ProgrammingError, self,
|
||||
"can't execute an empty query");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (Bytes_Check(sql)) {
|
||||
/* Necessary for ref-count symmetry with the unicode case: */
|
||||
Py_INCREF(sql);
|
||||
rv = sql;
|
||||
}
|
||||
else if (PyUnicode_Check(sql)) {
|
||||
if (!(rv = conn_encode(self->conn, sql))) { goto exit; }
|
||||
}
|
||||
else if (0 != (iscomp = _curs_is_composible(sql))) {
|
||||
if (iscomp < 0) { goto exit; }
|
||||
if (!(comp = PyObject_CallMethod(sql, "as_string", "O", self->conn))) {
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (Bytes_Check(comp)) {
|
||||
rv = comp;
|
||||
comp = NULL;
|
||||
}
|
||||
else if (PyUnicode_Check(comp)) {
|
||||
if (!(rv = conn_encode(self->conn, comp))) { goto exit; }
|
||||
}
|
||||
else {
|
||||
PyErr_Format(PyExc_TypeError,
|
||||
"as_string() should return a string: got %s instead",
|
||||
Py_TYPE(comp)->tp_name);
|
||||
goto exit;
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* the is not unicode or string, raise an error */
|
||||
PyErr_Format(PyExc_TypeError,
|
||||
"argument 1 must be a string or unicode object: got %s instead",
|
||||
Py_TYPE(sql)->tp_name);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
exit:
|
||||
Py_XDECREF(comp);
|
||||
return rv;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
curs_set_result(cursorObject *self, PGresult *pgres)
|
||||
{
|
||||
PQclear(self->pgres);
|
||||
self->pgres = pgres;
|
||||
}
|
||||
2126
psycopg/cursor_type.c
Normal file
2126
psycopg/cursor_type.c
Normal file
File diff suppressed because it is too large
Load Diff
41
psycopg/diagnostics.h
Normal file
41
psycopg/diagnostics.h
Normal file
@ -0,0 +1,41 @@
|
||||
/* diagnostics.c - definition for the psycopg Diagnostics type
|
||||
*
|
||||
* Copyright (C) 2013-2019 Matthew Woodcraft <matthew@woodcraft.me.uk>
|
||||
* Copyright (C) 2020-2021 The Psycopg Team
|
||||
*
|
||||
* This file is part of psycopg.
|
||||
*
|
||||
* psycopg2 is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give
|
||||
* permission to link this program with the OpenSSL library (or with
|
||||
* modified versions of OpenSSL that use the same license as OpenSSL),
|
||||
* and distribute linked combinations including the two.
|
||||
*
|
||||
* You must obey the GNU Lesser General Public License in all respects for
|
||||
* all of the code used other than OpenSSL.
|
||||
*
|
||||
* psycopg2 is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
|
||||
* License for more details.
|
||||
*/
|
||||
|
||||
#ifndef PSYCOPG_DIAGNOSTICS_H
|
||||
#define PSYCOPG_DIAGNOSTICS_H 1
|
||||
|
||||
#include "psycopg/error.h"
|
||||
|
||||
extern HIDDEN PyTypeObject diagnosticsType;
|
||||
|
||||
typedef struct {
|
||||
PyObject_HEAD
|
||||
|
||||
errorObject *err; /* exception to retrieve the diagnostics from */
|
||||
|
||||
} diagnosticsObject;
|
||||
|
||||
#endif /* PSYCOPG_DIAGNOSTICS_H */
|
||||
208
psycopg/diagnostics_type.c
Normal file
208
psycopg/diagnostics_type.c
Normal file
@ -0,0 +1,208 @@
|
||||
/* diagnostics.c - present information from libpq error responses
|
||||
*
|
||||
* Copyright (C) 2013-2019 Matthew Woodcraft <matthew@woodcraft.me.uk>
|
||||
* Copyright (C) 2020-2021 The Psycopg Team
|
||||
*
|
||||
* This file is part of psycopg.
|
||||
*
|
||||
* psycopg2 is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give
|
||||
* permission to link this program with the OpenSSL library (or with
|
||||
* modified versions of OpenSSL that use the same license as OpenSSL),
|
||||
* and distribute linked combinations including the two.
|
||||
*
|
||||
* You must obey the GNU Lesser General Public License in all respects for
|
||||
* all of the code used other than OpenSSL.
|
||||
*
|
||||
* psycopg2 is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
|
||||
* License for more details.
|
||||
*/
|
||||
|
||||
#define PSYCOPG_MODULE
|
||||
#include "psycopg/psycopg.h"
|
||||
|
||||
#include "psycopg/diagnostics.h"
|
||||
#include "psycopg/error.h"
|
||||
|
||||
|
||||
/* These constants are defined in src/include/postgres_ext.h but some may not
|
||||
* be available with the libpq we currently support at compile time. */
|
||||
|
||||
/* Available from PG 9.3 */
|
||||
#ifndef PG_DIAG_SCHEMA_NAME
|
||||
#define PG_DIAG_SCHEMA_NAME 's'
|
||||
#endif
|
||||
#ifndef PG_DIAG_TABLE_NAME
|
||||
#define PG_DIAG_TABLE_NAME 't'
|
||||
#endif
|
||||
#ifndef PG_DIAG_COLUMN_NAME
|
||||
#define PG_DIAG_COLUMN_NAME 'c'
|
||||
#endif
|
||||
#ifndef PG_DIAG_DATATYPE_NAME
|
||||
#define PG_DIAG_DATATYPE_NAME 'd'
|
||||
#endif
|
||||
#ifndef PG_DIAG_CONSTRAINT_NAME
|
||||
#define PG_DIAG_CONSTRAINT_NAME 'n'
|
||||
#endif
|
||||
|
||||
/* Available from PG 9.6 */
|
||||
#ifndef PG_DIAG_SEVERITY_NONLOCALIZED
|
||||
#define PG_DIAG_SEVERITY_NONLOCALIZED 'V'
|
||||
#endif
|
||||
|
||||
|
||||
/* Retrieve an error string from the exception's cursor.
|
||||
*
|
||||
* If the cursor or its result isn't available, return None.
|
||||
*/
|
||||
static PyObject *
|
||||
diagnostics_get_field(diagnosticsObject *self, void *closure)
|
||||
{
|
||||
const char *errortext;
|
||||
|
||||
if (!self->err->pgres) {
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
errortext = PQresultErrorField(self->err->pgres, (int)(Py_intptr_t)closure);
|
||||
return error_text_from_chars(self->err, errortext);
|
||||
}
|
||||
|
||||
|
||||
/* object calculated member list */
|
||||
static struct PyGetSetDef diagnosticsObject_getsets[] = {
|
||||
{ "severity", (getter)diagnostics_get_field, NULL,
|
||||
NULL, (void*) PG_DIAG_SEVERITY },
|
||||
{ "severity_nonlocalized", (getter)diagnostics_get_field, NULL,
|
||||
NULL, (void*) PG_DIAG_SEVERITY_NONLOCALIZED },
|
||||
{ "sqlstate", (getter)diagnostics_get_field, NULL,
|
||||
NULL, (void*) PG_DIAG_SQLSTATE },
|
||||
{ "message_primary", (getter)diagnostics_get_field, NULL,
|
||||
NULL, (void*) PG_DIAG_MESSAGE_PRIMARY },
|
||||
{ "message_detail", (getter)diagnostics_get_field, NULL,
|
||||
NULL, (void*) PG_DIAG_MESSAGE_DETAIL },
|
||||
{ "message_hint", (getter)diagnostics_get_field, NULL,
|
||||
NULL, (void*) PG_DIAG_MESSAGE_HINT },
|
||||
{ "statement_position", (getter)diagnostics_get_field, NULL,
|
||||
NULL, (void*) PG_DIAG_STATEMENT_POSITION },
|
||||
{ "internal_position", (getter)diagnostics_get_field, NULL,
|
||||
NULL, (void*) PG_DIAG_INTERNAL_POSITION },
|
||||
{ "internal_query", (getter)diagnostics_get_field, NULL,
|
||||
NULL, (void*) PG_DIAG_INTERNAL_QUERY },
|
||||
{ "context", (getter)diagnostics_get_field, NULL,
|
||||
NULL, (void*) PG_DIAG_CONTEXT },
|
||||
{ "schema_name", (getter)diagnostics_get_field, NULL,
|
||||
NULL, (void*) PG_DIAG_SCHEMA_NAME },
|
||||
{ "table_name", (getter)diagnostics_get_field, NULL,
|
||||
NULL, (void*) PG_DIAG_TABLE_NAME },
|
||||
{ "column_name", (getter)diagnostics_get_field, NULL,
|
||||
NULL, (void*) PG_DIAG_COLUMN_NAME },
|
||||
{ "datatype_name", (getter)diagnostics_get_field, NULL,
|
||||
NULL, (void*) PG_DIAG_DATATYPE_NAME },
|
||||
{ "constraint_name", (getter)diagnostics_get_field, NULL,
|
||||
NULL, (void*) PG_DIAG_CONSTRAINT_NAME },
|
||||
{ "source_file", (getter)diagnostics_get_field, NULL,
|
||||
NULL, (void*) PG_DIAG_SOURCE_FILE },
|
||||
{ "source_line", (getter)diagnostics_get_field, NULL,
|
||||
NULL, (void*) PG_DIAG_SOURCE_LINE },
|
||||
{ "source_function", (getter)diagnostics_get_field, NULL,
|
||||
NULL, (void*) PG_DIAG_SOURCE_FUNCTION },
|
||||
{NULL}
|
||||
};
|
||||
|
||||
/* initialization and finalization methods */
|
||||
|
||||
static PyObject *
|
||||
diagnostics_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
|
||||
{
|
||||
return type->tp_alloc(type, 0);
|
||||
}
|
||||
|
||||
static int
|
||||
diagnostics_init(diagnosticsObject *self, PyObject *args, PyObject *kwds)
|
||||
{
|
||||
PyObject *err = NULL;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "O", &err))
|
||||
return -1;
|
||||
|
||||
if (!PyObject_TypeCheck(err, &errorType)) {
|
||||
PyErr_SetString(PyExc_TypeError,
|
||||
"The argument must be a psycopg2.Error");
|
||||
return -1;
|
||||
}
|
||||
|
||||
Py_INCREF(err);
|
||||
self->err = (errorObject *)err;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
diagnostics_dealloc(diagnosticsObject* self)
|
||||
{
|
||||
Py_CLEAR(self->err);
|
||||
Py_TYPE(self)->tp_free((PyObject *)self);
|
||||
}
|
||||
|
||||
|
||||
/* object type */
|
||||
|
||||
static const char diagnosticsType_doc[] =
|
||||
"Details from a database error report.\n\n"
|
||||
"The object is returned by the `~psycopg2.Error.diag` attribute of the\n"
|
||||
"`!Error` object.\n"
|
||||
"All the information available from the |PQresultErrorField|_ function\n"
|
||||
"are exposed as attributes by the object, e.g. the `!severity` attribute\n"
|
||||
"returns the `!PG_DIAG_SEVERITY` code. "
|
||||
"Please refer to the `PostgreSQL documentation`__ for the meaning of all"
|
||||
" the attributes.\n\n"
|
||||
".. |PQresultErrorField| replace:: `!PQresultErrorField()`\n"
|
||||
".. _PQresultErrorField: https://www.postgresql.org/docs/current/static/"
|
||||
"libpq-exec.html#LIBPQ-PQRESULTERRORFIELD\n"
|
||||
".. __: PQresultErrorField_\n";
|
||||
|
||||
PyTypeObject diagnosticsType = {
|
||||
PyVarObject_HEAD_INIT(NULL, 0)
|
||||
"psycopg2.extensions.Diagnostics",
|
||||
sizeof(diagnosticsObject), 0,
|
||||
(destructor)diagnostics_dealloc, /*tp_dealloc*/
|
||||
0, /*tp_print*/
|
||||
0, /*tp_getattr*/
|
||||
0, /*tp_setattr*/
|
||||
0, /*tp_compare*/
|
||||
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*/
|
||||
diagnosticsType_doc, /*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*/
|
||||
diagnosticsObject_getsets, /*tp_getset*/
|
||||
0, /*tp_base*/
|
||||
0, /*tp_dict*/
|
||||
0, /*tp_descr_get*/
|
||||
0, /*tp_descr_set*/
|
||||
0, /*tp_dictoffset*/
|
||||
(initproc)diagnostics_init, /*tp_init*/
|
||||
0, /*tp_alloc*/
|
||||
diagnostics_new, /*tp_new*/
|
||||
};
|
||||
46
psycopg/error.h
Normal file
46
psycopg/error.h
Normal file
@ -0,0 +1,46 @@
|
||||
/* error.h - definition for the psycopg base Error type
|
||||
*
|
||||
* Copyright (C) 2013-2019 Daniele Varrazzo <daniele.varrazzo@gmail.com>
|
||||
* Copyright (C) 2020-2021 The Psycopg Team
|
||||
*
|
||||
* This file is part of psycopg.
|
||||
*
|
||||
* psycopg2 is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give
|
||||
* permission to link this program with the OpenSSL library (or with
|
||||
* modified versions of OpenSSL that use the same license as OpenSSL),
|
||||
* and distribute linked combinations including the two.
|
||||
*
|
||||
* You must obey the GNU Lesser General Public License in all respects for
|
||||
* all of the code used other than OpenSSL.
|
||||
*
|
||||
* psycopg2 is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
|
||||
* License for more details.
|
||||
*/
|
||||
|
||||
#ifndef PSYCOPG_ERROR_H
|
||||
#define PSYCOPG_ERROR_H 1
|
||||
|
||||
extern HIDDEN PyTypeObject errorType;
|
||||
|
||||
typedef struct {
|
||||
PyBaseExceptionObject exc;
|
||||
|
||||
PyObject *pgerror;
|
||||
PyObject *pgcode;
|
||||
cursorObject *cursor;
|
||||
PyObject *pydecoder;
|
||||
PGresult *pgres;
|
||||
} errorObject;
|
||||
|
||||
HIDDEN PyObject *error_text_from_chars(errorObject *self, const char *str);
|
||||
HIDDEN BORROWED PyObject *exception_from_sqlstate(const char *sqlstate);
|
||||
HIDDEN BORROWED PyObject *base_exception_from_sqlstate(const char *sqlstate);
|
||||
|
||||
#endif /* PSYCOPG_ERROR_H */
|
||||
376
psycopg/error_type.c
Normal file
376
psycopg/error_type.c
Normal file
@ -0,0 +1,376 @@
|
||||
/* error_type.c - python interface to the Error objects
|
||||
*
|
||||
* Copyright (C) 2013-2019 Daniele Varrazzo <daniele.varrazzo@gmail.com>
|
||||
* Copyright (C) 2020-2021 The Psycopg Team
|
||||
*
|
||||
* This file is part of psycopg.
|
||||
*
|
||||
* psycopg2 is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give
|
||||
* permission to link this program with the OpenSSL library (or with
|
||||
* modified versions of OpenSSL that use the same license as OpenSSL),
|
||||
* and distribute linked combinations including the two.
|
||||
*
|
||||
* You must obey the GNU Lesser General Public License in all respects for
|
||||
* all of the code used other than OpenSSL.
|
||||
*
|
||||
* psycopg2 is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
|
||||
* License for more details.
|
||||
*/
|
||||
|
||||
#define PSYCOPG_MODULE
|
||||
#include "psycopg/psycopg.h"
|
||||
|
||||
#include "psycopg/error.h"
|
||||
#include "psycopg/diagnostics.h"
|
||||
#include "psycopg/pqpath.h"
|
||||
|
||||
|
||||
PyObject *
|
||||
error_text_from_chars(errorObject *self, const char *str)
|
||||
{
|
||||
return psyco_text_from_chars_safe(str, -1, self->pydecoder);
|
||||
}
|
||||
|
||||
|
||||
/* Return the Python exception corresponding to an SQLSTATE error
|
||||
* code. A list of error codes can be found at:
|
||||
* https://www.postgresql.org/docs/current/static/errcodes-appendix.html
|
||||
*/
|
||||
BORROWED PyObject *
|
||||
exception_from_sqlstate(const char *sqlstate)
|
||||
{
|
||||
PyObject *exc;
|
||||
|
||||
/* First look up an exception of the proper class */
|
||||
exc = PyDict_GetItemString(sqlstate_errors, sqlstate);
|
||||
if (exc) {
|
||||
return exc;
|
||||
}
|
||||
else {
|
||||
PyErr_Clear();
|
||||
return base_exception_from_sqlstate(sqlstate);
|
||||
}
|
||||
}
|
||||
|
||||
BORROWED PyObject *
|
||||
base_exception_from_sqlstate(const char *sqlstate)
|
||||
{
|
||||
switch (sqlstate[0]) {
|
||||
case '0':
|
||||
switch (sqlstate[1]) {
|
||||
case '8': /* Class 08 - Connection Exception */
|
||||
return OperationalError;
|
||||
case 'A': /* Class 0A - Feature Not Supported */
|
||||
return NotSupportedError;
|
||||
}
|
||||
break;
|
||||
case '2':
|
||||
switch (sqlstate[1]) {
|
||||
case '0': /* Class 20 - Case Not Found */
|
||||
case '1': /* Class 21 - Cardinality Violation */
|
||||
return ProgrammingError;
|
||||
case '2': /* Class 22 - Data Exception */
|
||||
return DataError;
|
||||
case '3': /* Class 23 - Integrity Constraint Violation */
|
||||
return IntegrityError;
|
||||
case '4': /* Class 24 - Invalid Cursor State */
|
||||
case '5': /* Class 25 - Invalid Transaction State */
|
||||
return InternalError;
|
||||
case '6': /* Class 26 - Invalid SQL Statement Name */
|
||||
case '7': /* Class 27 - Triggered Data Change Violation */
|
||||
case '8': /* Class 28 - Invalid Authorization Specification */
|
||||
return OperationalError;
|
||||
case 'B': /* Class 2B - Dependent Privilege Descriptors Still Exist */
|
||||
case 'D': /* Class 2D - Invalid Transaction Termination */
|
||||
case 'F': /* Class 2F - SQL Routine Exception */
|
||||
return InternalError;
|
||||
}
|
||||
break;
|
||||
case '3':
|
||||
switch (sqlstate[1]) {
|
||||
case '4': /* Class 34 - Invalid Cursor Name */
|
||||
return OperationalError;
|
||||
case '8': /* Class 38 - External Routine Exception */
|
||||
case '9': /* Class 39 - External Routine Invocation Exception */
|
||||
case 'B': /* Class 3B - Savepoint Exception */
|
||||
return InternalError;
|
||||
case 'D': /* Class 3D - Invalid Catalog Name */
|
||||
case 'F': /* Class 3F - Invalid Schema Name */
|
||||
return ProgrammingError;
|
||||
}
|
||||
break;
|
||||
case '4':
|
||||
switch (sqlstate[1]) {
|
||||
case '0': /* Class 40 - Transaction Rollback */
|
||||
return TransactionRollbackError;
|
||||
case '2': /* Class 42 - Syntax Error or Access Rule Violation */
|
||||
case '4': /* Class 44 - WITH CHECK OPTION Violation */
|
||||
return ProgrammingError;
|
||||
}
|
||||
break;
|
||||
case '5':
|
||||
/* Class 53 - Insufficient Resources
|
||||
Class 54 - Program Limit Exceeded
|
||||
Class 55 - Object Not In Prerequisite State
|
||||
Class 57 - Operator Intervention
|
||||
Class 58 - System Error (errors external to PostgreSQL itself) */
|
||||
if (!strcmp(sqlstate, "57014"))
|
||||
return QueryCanceledError;
|
||||
else
|
||||
return OperationalError;
|
||||
case 'F': /* Class F0 - Configuration File Error */
|
||||
return InternalError;
|
||||
case 'H': /* Class HV - Foreign Data Wrapper Error (SQL/MED) */
|
||||
return OperationalError;
|
||||
case 'P': /* Class P0 - PL/pgSQL Error */
|
||||
return InternalError;
|
||||
case 'X': /* Class XX - Internal Error */
|
||||
return InternalError;
|
||||
}
|
||||
/* return DatabaseError as a fallback */
|
||||
return DatabaseError;
|
||||
}
|
||||
|
||||
|
||||
static const char pgerror_doc[] =
|
||||
"The error message returned by the backend, if available, else None";
|
||||
|
||||
static const char pgcode_doc[] =
|
||||
"The error code returned by the backend, if available, else None";
|
||||
|
||||
static const char cursor_doc[] =
|
||||
"The cursor that raised the exception, if available, else None";
|
||||
|
||||
static const char diag_doc[] =
|
||||
"A Diagnostics object to get further information about the error";
|
||||
|
||||
static PyMemberDef error_members[] = {
|
||||
{ "pgerror", T_OBJECT, offsetof(errorObject, pgerror),
|
||||
READONLY, (char *)pgerror_doc },
|
||||
{ "pgcode", T_OBJECT, offsetof(errorObject, pgcode),
|
||||
READONLY, (char *)pgcode_doc },
|
||||
{ "cursor", T_OBJECT, offsetof(errorObject, cursor),
|
||||
READONLY, (char *)cursor_doc },
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
static PyObject *
|
||||
error_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
|
||||
{
|
||||
return ((PyTypeObject *)PyExc_StandardError)->tp_new(
|
||||
type, args, kwargs);
|
||||
}
|
||||
|
||||
static int
|
||||
error_init(errorObject *self, PyObject *args, PyObject *kwargs)
|
||||
{
|
||||
if (((PyTypeObject *)PyExc_StandardError)->tp_init(
|
||||
(PyObject *)self, args, kwargs) < 0) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
error_traverse(errorObject *self, visitproc visit, void *arg)
|
||||
{
|
||||
Py_VISIT(self->pgerror);
|
||||
Py_VISIT(self->pgcode);
|
||||
Py_VISIT(self->cursor);
|
||||
Py_VISIT(self->pydecoder);
|
||||
|
||||
return ((PyTypeObject *)PyExc_StandardError)->tp_traverse(
|
||||
(PyObject *)self, visit, arg);
|
||||
}
|
||||
|
||||
static int
|
||||
error_clear(errorObject *self)
|
||||
{
|
||||
Py_CLEAR(self->pgerror);
|
||||
Py_CLEAR(self->pgcode);
|
||||
Py_CLEAR(self->cursor);
|
||||
Py_CLEAR(self->pydecoder);
|
||||
|
||||
return ((PyTypeObject *)PyExc_StandardError)->tp_clear((PyObject *)self);
|
||||
}
|
||||
|
||||
static void
|
||||
error_dealloc(errorObject *self)
|
||||
{
|
||||
PyObject_GC_UnTrack((PyObject *)self);
|
||||
error_clear(self);
|
||||
CLEARPGRES(self->pgres);
|
||||
|
||||
Py_TYPE(self)->tp_free((PyObject *)self);
|
||||
}
|
||||
|
||||
|
||||
static PyObject *
|
||||
error_get_diag(errorObject *self, void *closure)
|
||||
{
|
||||
return PyObject_CallFunctionObjArgs(
|
||||
(PyObject *)&diagnosticsType, (PyObject *)self, NULL);
|
||||
}
|
||||
|
||||
static struct PyGetSetDef error_getsets[] = {
|
||||
{ "diag", (getter)error_get_diag, NULL, (char *)diag_doc },
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
|
||||
/* Error.__reduce__
|
||||
*
|
||||
* The method is required to make exceptions picklable: set the cursor
|
||||
* attribute to None. Only working from Py 2.5: previous versions
|
||||
* would require implementing __getstate__, and as of 2012 it's a little
|
||||
* bit too late to care. */
|
||||
static PyObject *
|
||||
error_reduce(errorObject *self, PyObject *dummy)
|
||||
{
|
||||
PyObject *meth = NULL;
|
||||
PyObject *tuple = NULL;
|
||||
PyObject *dict = NULL;
|
||||
PyObject *rv = NULL;
|
||||
|
||||
if (!(meth = PyObject_GetAttrString(PyExc_StandardError, "__reduce__"))) {
|
||||
goto error;
|
||||
}
|
||||
if (!(tuple = PyObject_CallFunctionObjArgs(meth, self, NULL))) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* tuple is (type, args): convert it to (type, args, dict)
|
||||
* with our extra items in the dict.
|
||||
*
|
||||
* If these checks fail, we can still return a valid object. Pickle
|
||||
* will likely fail downstream, but there's nothing else we can do here */
|
||||
if (!PyTuple_Check(tuple)) { goto exit; }
|
||||
if (2 != PyTuple_GET_SIZE(tuple)) { goto exit; }
|
||||
|
||||
if (!(dict = PyDict_New())) { goto error; }
|
||||
if (self->pgerror) {
|
||||
if (0 != PyDict_SetItemString(dict, "pgerror", self->pgerror)) {
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
if (self->pgcode) {
|
||||
if (0 != PyDict_SetItemString(dict, "pgcode", self->pgcode)) {
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
PyObject *newtuple;
|
||||
if (!(newtuple = PyTuple_Pack(3,
|
||||
PyTuple_GET_ITEM(tuple, 0),
|
||||
PyTuple_GET_ITEM(tuple, 1),
|
||||
dict))) {
|
||||
goto error;
|
||||
}
|
||||
Py_DECREF(tuple);
|
||||
tuple = newtuple;
|
||||
}
|
||||
|
||||
exit:
|
||||
rv = tuple;
|
||||
tuple = NULL;
|
||||
|
||||
error:
|
||||
Py_XDECREF(dict);
|
||||
Py_XDECREF(tuple);
|
||||
Py_XDECREF(meth);
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
PyObject *
|
||||
error_setstate(errorObject *self, PyObject *state)
|
||||
{
|
||||
PyObject *rv = NULL;
|
||||
|
||||
/* we don't call the StandartError's setstate as it would try to load the
|
||||
* dict content as attributes */
|
||||
|
||||
if (state == Py_None) {
|
||||
goto exit;
|
||||
}
|
||||
if (!PyDict_Check(state)) {
|
||||
PyErr_SetString(PyExc_TypeError, "state is not a dictionary");
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* load the dict content in the structure */
|
||||
Py_CLEAR(self->pgerror);
|
||||
self->pgerror = PyDict_GetItemString(state, "pgerror");
|
||||
Py_XINCREF(self->pgerror);
|
||||
|
||||
Py_CLEAR(self->pgcode);
|
||||
self->pgcode = PyDict_GetItemString(state, "pgcode");
|
||||
Py_XINCREF(self->pgcode);
|
||||
|
||||
Py_CLEAR(self->cursor);
|
||||
/* We never expect a cursor in the state as it's not picklable.
|
||||
* at most there could be a None here, coming from Psycopg < 2.5 */
|
||||
|
||||
exit:
|
||||
rv = Py_None;
|
||||
Py_INCREF(rv);
|
||||
|
||||
error:
|
||||
return rv;
|
||||
}
|
||||
|
||||
static PyMethodDef error_methods[] = {
|
||||
/* Make Error and all its subclasses picklable. */
|
||||
{"__reduce__", (PyCFunction)error_reduce, METH_NOARGS },
|
||||
{"__setstate__", (PyCFunction)error_setstate, METH_O },
|
||||
{NULL}
|
||||
};
|
||||
|
||||
|
||||
PyTypeObject errorType = {
|
||||
PyVarObject_HEAD_INIT(NULL, 0)
|
||||
"psycopg2.Error",
|
||||
sizeof(errorObject), 0,
|
||||
(destructor)error_dealloc, /* tp_dealloc */
|
||||
0, /*tp_print*/
|
||||
0, /*tp_getattr*/
|
||||
0, /*tp_setattr*/
|
||||
0, /*tp_compare*/
|
||||
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|Py_TPFLAGS_HAVE_GC, /*tp_flags*/
|
||||
Error_doc, /*tp_doc*/
|
||||
(traverseproc)error_traverse, /*tp_traverse*/
|
||||
(inquiry)error_clear, /*tp_clear*/
|
||||
0, /*tp_richcompare*/
|
||||
0, /*tp_weaklistoffset*/
|
||||
0, /*tp_iter*/
|
||||
0, /*tp_iternext*/
|
||||
error_methods, /*tp_methods*/
|
||||
error_members, /*tp_members*/
|
||||
error_getsets, /*tp_getset*/
|
||||
0, /*tp_base Will be set to StandardError in module init */
|
||||
0, /*tp_dict*/
|
||||
0, /*tp_descr_get*/
|
||||
0, /*tp_descr_set*/
|
||||
0, /*tp_dictoffset*/
|
||||
(initproc)error_init, /*tp_init*/
|
||||
0, /*tp_alloc*/
|
||||
error_new, /*tp_new*/
|
||||
};
|
||||
210
psycopg/green.c
Normal file
210
psycopg/green.c
Normal file
@ -0,0 +1,210 @@
|
||||
/* green.c - cooperation with coroutine libraries.
|
||||
*
|
||||
* Copyright (C) 2010-2019 Daniele Varrazzo <daniele.varrazzo@gmail.com>
|
||||
* Copyright (C) 2020-2021 The Psycopg Team
|
||||
*
|
||||
* This file is part of psycopg.
|
||||
*
|
||||
* psycopg2 is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give
|
||||
* permission to link this program with the OpenSSL library (or with
|
||||
* modified versions of OpenSSL that use the same license as OpenSSL),
|
||||
* and distribute linked combinations including the two.
|
||||
*
|
||||
* You must obey the GNU Lesser General Public License in all respects for
|
||||
* all of the code used other than OpenSSL.
|
||||
*
|
||||
* psycopg2 is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
|
||||
* License for more details.
|
||||
*/
|
||||
|
||||
#define PSYCOPG_MODULE
|
||||
#include "psycopg/psycopg.h"
|
||||
|
||||
#include "psycopg/green.h"
|
||||
#include "psycopg/connection.h"
|
||||
#include "psycopg/pqpath.h"
|
||||
|
||||
|
||||
HIDDEN PyObject *wait_callback = NULL;
|
||||
|
||||
static PyObject *have_wait_callback(void);
|
||||
static void green_panic(connectionObject *conn);
|
||||
|
||||
/* Register a callback function to block waiting for data.
|
||||
*
|
||||
* The function is exported by the _psycopg module.
|
||||
*/
|
||||
PyObject *
|
||||
psyco_set_wait_callback(PyObject *self, PyObject *obj)
|
||||
{
|
||||
Py_XDECREF(wait_callback);
|
||||
|
||||
if (obj != Py_None) {
|
||||
wait_callback = obj;
|
||||
Py_INCREF(obj);
|
||||
}
|
||||
else {
|
||||
wait_callback = NULL;
|
||||
}
|
||||
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
|
||||
/* Return the currently registered wait callback function.
|
||||
*
|
||||
* The function is exported by the _psycopg module.
|
||||
*/
|
||||
PyObject *
|
||||
psyco_get_wait_callback(PyObject *self, PyObject *obj)
|
||||
{
|
||||
PyObject *ret;
|
||||
|
||||
ret = wait_callback;
|
||||
if (!ret) {
|
||||
ret = Py_None;
|
||||
}
|
||||
|
||||
Py_INCREF(ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
/* Return nonzero if a wait callback should be called. */
|
||||
int
|
||||
psyco_green()
|
||||
{
|
||||
return (NULL != wait_callback);
|
||||
}
|
||||
|
||||
/* Return the wait callback if available.
|
||||
*
|
||||
* If not available, set a Python exception and return.
|
||||
*
|
||||
* The function returns a new reference: decref after use.
|
||||
*/
|
||||
static PyObject *
|
||||
have_wait_callback()
|
||||
{
|
||||
PyObject *cb;
|
||||
|
||||
cb = wait_callback;
|
||||
if (!cb) {
|
||||
PyErr_SetString(OperationalError, "wait callback not available");
|
||||
return NULL;
|
||||
}
|
||||
Py_INCREF(cb);
|
||||
return cb;
|
||||
}
|
||||
|
||||
/* Block waiting for data available in an async connection.
|
||||
*
|
||||
* This function assumes `wait_callback` to be available:
|
||||
* raise `InterfaceError` if it is not. Use `psyco_green()` to check if
|
||||
* the function is to be called.
|
||||
*
|
||||
* Return 0 on success, else nonzero and set a Python exception.
|
||||
*/
|
||||
int
|
||||
psyco_wait(connectionObject *conn)
|
||||
{
|
||||
PyObject *rv;
|
||||
PyObject *cb;
|
||||
|
||||
Dprintf("psyco_wait");
|
||||
if (!(cb = have_wait_callback())) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
rv = PyObject_CallFunctionObjArgs(cb, conn, NULL);
|
||||
Py_DECREF(cb);
|
||||
|
||||
if (NULL != rv) {
|
||||
Py_DECREF(rv);
|
||||
return 0;
|
||||
} else {
|
||||
Dprintf("psyco_wait: error in wait callback");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Replacement for PQexec using the user-provided wait function.
|
||||
*
|
||||
* The function should be called helding the connection lock, and
|
||||
* the GIL because some Python code is expected to be called.
|
||||
*
|
||||
* If PGresult is NULL, there may have been either a libpq error
|
||||
* or an exception raised by Python code: before raising an exception
|
||||
* check if there is already one using `PyErr_Occurred()` */
|
||||
PGresult *
|
||||
psyco_exec_green(connectionObject *conn, const char *command)
|
||||
{
|
||||
PGresult *result = NULL;
|
||||
|
||||
/* Check that there is a single concurrently executing query */
|
||||
if (conn->async_cursor) {
|
||||
PyErr_SetString(ProgrammingError,
|
||||
"a single async query can be executed on the same connection");
|
||||
goto end;
|
||||
}
|
||||
/* we don't care about which cursor is executing the query, and
|
||||
* it may also be that no cursor is involved at all and this is
|
||||
* an internal query. So just store anything in the async_cursor,
|
||||
* respecting the code expecting it to be a weakref */
|
||||
if (!(conn->async_cursor = PyWeakref_NewRef((PyObject*)conn, NULL))) {
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Send the query asynchronously */
|
||||
if (0 == pq_send_query(conn, command)) {
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Enter the poll loop with a write. When writing is finished the poll
|
||||
implementation will set the status to ASYNC_READ without exiting the
|
||||
loop. If read is finished the status is finally set to ASYNC_DONE.
|
||||
*/
|
||||
conn->async_status = ASYNC_WRITE;
|
||||
|
||||
if (0 != psyco_wait(conn)) {
|
||||
green_panic(conn);
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* the result is now in the connection: take its ownership */
|
||||
result = conn->pgres;
|
||||
conn->pgres = NULL;
|
||||
|
||||
end:
|
||||
CLEARPGRES(conn->pgres);
|
||||
conn->async_status = ASYNC_DONE;
|
||||
Py_CLEAR(conn->async_cursor);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/* There has been a communication error during query execution. It may have
|
||||
* happened e.g. for a network error or an error in the callback, and we
|
||||
* cannot tell the two apart.
|
||||
* Trying to PQcancel or PQgetResult to put the connection back into a working
|
||||
* state doesn't work nice (issue #113): the program blocks and the
|
||||
* interpreter won't even respond to SIGINT. PQreset could work async, but the
|
||||
* python program would have then a connection made but not configured where
|
||||
* it is probably not designed to handled. So for the moment we do the kindest
|
||||
* thing we can: we close the connection. A long-running program should
|
||||
* already have a way to discard broken connections; a short-lived one would
|
||||
* benefit of working ctrl-c.
|
||||
*/
|
||||
static void
|
||||
green_panic(connectionObject *conn)
|
||||
{
|
||||
Dprintf("green_panic: closing the connection");
|
||||
conn_close_locked(conn);
|
||||
}
|
||||
76
psycopg/green.h
Normal file
76
psycopg/green.h
Normal file
@ -0,0 +1,76 @@
|
||||
/* green.c - cooperation with coroutine libraries.
|
||||
*
|
||||
* Copyright (C) 2010-2019 Daniele Varrazzo <daniele.varrazzo@gmail.com>
|
||||
* Copyright (C) 2020-2021 The Psycopg Team
|
||||
*
|
||||
* This file is part of psycopg.
|
||||
*
|
||||
* psycopg2 is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give
|
||||
* permission to link this program with the OpenSSL library (or with
|
||||
* modified versions of OpenSSL that use the same license as OpenSSL),
|
||||
* and distribute linked combinations including the two.
|
||||
*
|
||||
* You must obey the GNU Lesser General Public License in all respects for
|
||||
* all of the code used other than OpenSSL.
|
||||
*
|
||||
* psycopg2 is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
|
||||
* License for more details.
|
||||
*/
|
||||
|
||||
#ifndef PSYCOPG_GREEN_H
|
||||
#define PSYCOPG_GREEN_H 1
|
||||
|
||||
#include <libpq-fe.h>
|
||||
#include "psycopg/connection.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define psyco_set_wait_callback_doc \
|
||||
"Register a callback function to block waiting for data.\n" \
|
||||
"\n" \
|
||||
"The callback should have signature :samp:`fun({conn})` and\n" \
|
||||
"is called to wait for data available whenever a blocking function from the\n" \
|
||||
"libpq is called. Use `!set_wait_callback(None)` to revert to the\n" \
|
||||
"original behaviour (i.e. using blocking libpq functions).\n" \
|
||||
"\n" \
|
||||
"The function is an hook to allow coroutine-based libraries (such as\n" \
|
||||
"Eventlet_ or gevent_) to switch when Psycopg is blocked, allowing\n" \
|
||||
"other coroutines to run concurrently.\n" \
|
||||
"\n" \
|
||||
"See `~psycopg2.extras.wait_select()` for an example of a wait callback\n" \
|
||||
"implementation.\n" \
|
||||
"\n" \
|
||||
".. _Eventlet: https://eventlet.net/\n" \
|
||||
".. _gevent: http://www.gevent.org/\n"
|
||||
HIDDEN PyObject *psyco_set_wait_callback(PyObject *self, PyObject *obj);
|
||||
|
||||
#define psyco_get_wait_callback_doc \
|
||||
"Return the currently registered wait callback.\n" \
|
||||
"\n" \
|
||||
"Return `!None` if no callback is currently registered.\n"
|
||||
HIDDEN PyObject *psyco_get_wait_callback(PyObject *self, PyObject *obj);
|
||||
|
||||
HIDDEN int psyco_green(void);
|
||||
HIDDEN int psyco_wait(connectionObject *conn);
|
||||
HIDDEN PGresult *psyco_exec_green(connectionObject *conn, const char *command);
|
||||
|
||||
#define EXC_IF_GREEN(cmd) \
|
||||
if (psyco_green()) { \
|
||||
PyErr_SetString(ProgrammingError, #cmd " cannot be used " \
|
||||
"with an asynchronous callback."); \
|
||||
return NULL; }
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* !defined(PSYCOPG_GREEN_H) */
|
||||
105
psycopg/libpq_support.c
Normal file
105
psycopg/libpq_support.c
Normal file
@ -0,0 +1,105 @@
|
||||
/* libpq_support.c - functions not provided by libpq, but which are
|
||||
* required for advanced communication with the server, such as
|
||||
* streaming replication
|
||||
*
|
||||
* Copyright (C) 2003-2019 Federico Di Gregorio <fog@debian.org>
|
||||
* Copyright (C) 2020-2021 The Psycopg Team
|
||||
*
|
||||
* This file is part of psycopg.
|
||||
*
|
||||
* psycopg2 is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give
|
||||
* permission to link this program with the OpenSSL library (or with
|
||||
* modified versions of OpenSSL that use the same license as OpenSSL),
|
||||
* and distribute linked combinations including the two.
|
||||
*
|
||||
* You must obey the GNU Lesser General Public License in all respects for
|
||||
* all of the code used other than OpenSSL.
|
||||
*
|
||||
* psycopg2 is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
|
||||
* License for more details.
|
||||
*/
|
||||
|
||||
#define PSYCOPG_MODULE
|
||||
#include "psycopg/psycopg.h"
|
||||
|
||||
#include "psycopg/libpq_support.h"
|
||||
|
||||
/* htonl(), ntohl() */
|
||||
#ifdef _WIN32
|
||||
#include <winsock2.h>
|
||||
/* gettimeofday() */
|
||||
#include "psycopg/win32_support.h"
|
||||
#else
|
||||
#include <arpa/inet.h>
|
||||
#endif
|
||||
|
||||
/* support routines taken from pg_basebackup/streamutil.c */
|
||||
|
||||
/*
|
||||
* Frontend version of GetCurrentTimestamp(), since we are not linked with
|
||||
* backend code. The protocol always uses integer timestamps, regardless of
|
||||
* server setting.
|
||||
*/
|
||||
int64_t
|
||||
feGetCurrentTimestamp(void)
|
||||
{
|
||||
int64_t result;
|
||||
struct timeval tp;
|
||||
|
||||
gettimeofday(&tp, NULL);
|
||||
|
||||
result = (int64_t) tp.tv_sec -
|
||||
((POSTGRES_EPOCH_JDATE - UNIX_EPOCH_JDATE) * SECS_PER_DAY);
|
||||
|
||||
result = (result * USECS_PER_SEC) + tp.tv_usec;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* Converts an int64 to network byte order.
|
||||
*/
|
||||
void
|
||||
fe_sendint64(int64_t i, char *buf)
|
||||
{
|
||||
uint32_t n32;
|
||||
|
||||
/* High order half first, since we're doing MSB-first */
|
||||
n32 = (uint32_t) (i >> 32);
|
||||
n32 = htonl(n32);
|
||||
memcpy(&buf[0], &n32, 4);
|
||||
|
||||
/* Now the low order half */
|
||||
n32 = (uint32_t) i;
|
||||
n32 = htonl(n32);
|
||||
memcpy(&buf[4], &n32, 4);
|
||||
}
|
||||
|
||||
/*
|
||||
* Converts an int64 from network byte order to native format.
|
||||
*/
|
||||
int64_t
|
||||
fe_recvint64(char *buf)
|
||||
{
|
||||
int64_t result;
|
||||
uint32_t h32;
|
||||
uint32_t l32;
|
||||
|
||||
memcpy(&h32, buf, 4);
|
||||
memcpy(&l32, buf + 4, 4);
|
||||
h32 = ntohl(h32);
|
||||
l32 = ntohl(l32);
|
||||
|
||||
result = h32;
|
||||
result <<= 32;
|
||||
result |= l32;
|
||||
|
||||
return result;
|
||||
}
|
||||
49
psycopg/libpq_support.h
Normal file
49
psycopg/libpq_support.h
Normal file
@ -0,0 +1,49 @@
|
||||
/* libpq_support.h - definitions for libpq_support.c
|
||||
*
|
||||
* Copyright (C) 2003-2019 Federico Di Gregorio <fog@debian.org>
|
||||
* Copyright (C) 2020-2021 The Psycopg Team
|
||||
*
|
||||
* This file is part of psycopg.
|
||||
*
|
||||
* psycopg2 is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give
|
||||
* permission to link this program with the OpenSSL library (or with
|
||||
* modified versions of OpenSSL that use the same license as OpenSSL),
|
||||
* and distribute linked combinations including the two.
|
||||
*
|
||||
* You must obey the GNU Lesser General Public License in all respects for
|
||||
* all of the code used other than OpenSSL.
|
||||
*
|
||||
* psycopg2 is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
|
||||
* License for more details.
|
||||
*/
|
||||
#ifndef PSYCOPG_LIBPQ_SUPPORT_H
|
||||
#define PSYCOPG_LIBPQ_SUPPORT_H 1
|
||||
|
||||
#include "psycopg/config.h"
|
||||
|
||||
/* type and constant definitions from internal postgres include */
|
||||
typedef uint64_t XLogRecPtr;
|
||||
|
||||
/* have to use lowercase %x, as PyString_FromFormat can't do %X */
|
||||
#define XLOGFMTSTR "%x/%x"
|
||||
#define XLOGFMTARGS(x) ((uint32_t)((x) >> 32)), ((uint32_t)((x) & 0xFFFFFFFF))
|
||||
|
||||
/* Julian-date equivalents of Day 0 in Unix and Postgres reckoning */
|
||||
#define UNIX_EPOCH_JDATE 2440588 /* == date2j(1970, 1, 1) */
|
||||
#define POSTGRES_EPOCH_JDATE 2451545 /* == date2j(2000, 1, 1) */
|
||||
|
||||
#define SECS_PER_DAY 86400
|
||||
#define USECS_PER_SEC 1000000LL
|
||||
|
||||
HIDDEN int64_t feGetCurrentTimestamp(void);
|
||||
HIDDEN void fe_sendint64(int64_t i, char *buf);
|
||||
HIDDEN int64_t fe_recvint64(char *buf);
|
||||
|
||||
#endif /* !defined(PSYCOPG_LIBPQ_SUPPORT_H) */
|
||||
102
psycopg/lobject.h
Normal file
102
psycopg/lobject.h
Normal file
@ -0,0 +1,102 @@
|
||||
/* lobject.h - definition for the psycopg lobject type
|
||||
*
|
||||
* Copyright (C) 2006-2019 Federico Di Gregorio <fog@debian.org>
|
||||
* Copyright (C) 2020-2021 The Psycopg Team
|
||||
*
|
||||
* This file is part of psycopg.
|
||||
*
|
||||
* psycopg2 is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give
|
||||
* permission to link this program with the OpenSSL library (or with
|
||||
* modified versions of OpenSSL that use the same license as OpenSSL),
|
||||
* and distribute linked combinations including the two.
|
||||
*
|
||||
* You must obey the GNU Lesser General Public License in all respects for
|
||||
* all of the code used other than OpenSSL.
|
||||
*
|
||||
* psycopg2 is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
|
||||
* License for more details.
|
||||
*/
|
||||
|
||||
#ifndef PSYCOPG_LOBJECT_H
|
||||
#define PSYCOPG_LOBJECT_H 1
|
||||
|
||||
#include <libpq/libpq-fs.h>
|
||||
|
||||
#include "psycopg/connection.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
extern HIDDEN PyTypeObject lobjectType;
|
||||
|
||||
typedef struct {
|
||||
PyObject HEAD;
|
||||
|
||||
connectionObject *conn; /* connection owning the lobject */
|
||||
long int mark; /* copied from conn->mark */
|
||||
|
||||
char *smode; /* string mode if lobject was opened */
|
||||
int mode; /* numeric version of smode */
|
||||
|
||||
int fd; /* the file descriptor for file-like ops */
|
||||
Oid oid; /* the oid for this lobject */
|
||||
} lobjectObject;
|
||||
|
||||
/* functions exported from lobject_int.c */
|
||||
|
||||
RAISES_NEG HIDDEN int lobject_open(lobjectObject *self, connectionObject *conn,
|
||||
Oid oid, const char *smode, Oid new_oid,
|
||||
const char *new_file);
|
||||
RAISES_NEG HIDDEN int lobject_unlink(lobjectObject *self);
|
||||
RAISES_NEG HIDDEN int lobject_export(lobjectObject *self, const char *filename);
|
||||
|
||||
RAISES_NEG HIDDEN Py_ssize_t lobject_read(lobjectObject *self, char *buf, size_t len);
|
||||
RAISES_NEG HIDDEN Py_ssize_t lobject_write(lobjectObject *self, const char *buf,
|
||||
size_t len);
|
||||
RAISES_NEG HIDDEN Py_ssize_t lobject_seek(lobjectObject *self, Py_ssize_t pos, int whence);
|
||||
RAISES_NEG HIDDEN Py_ssize_t lobject_tell(lobjectObject *self);
|
||||
RAISES_NEG HIDDEN int lobject_truncate(lobjectObject *self, size_t len);
|
||||
RAISES_NEG HIDDEN int lobject_close(lobjectObject *self);
|
||||
|
||||
#define lobject_is_closed(self) \
|
||||
((self)->fd < 0 || !(self)->conn || (self)->conn->closed)
|
||||
|
||||
/* exception-raising macros */
|
||||
|
||||
#define EXC_IF_LOBJ_CLOSED(self) \
|
||||
if (lobject_is_closed(self)) { \
|
||||
PyErr_SetString(InterfaceError, "lobject already closed"); \
|
||||
return NULL; }
|
||||
|
||||
#define EXC_IF_LOBJ_LEVEL0(self) \
|
||||
if (self->conn->autocommit) { \
|
||||
psyco_set_error(ProgrammingError, NULL, \
|
||||
"can't use a lobject outside of transactions"); \
|
||||
return NULL; \
|
||||
}
|
||||
#define EXC_IF_LOBJ_UNMARKED(self) \
|
||||
if (self->conn->mark != self->mark) { \
|
||||
psyco_set_error(ProgrammingError, NULL, \
|
||||
"lobject isn't valid anymore"); \
|
||||
return NULL; \
|
||||
}
|
||||
|
||||
/* Values for the lobject mode */
|
||||
#define LOBJECT_READ 1
|
||||
#define LOBJECT_WRITE 2
|
||||
#define LOBJECT_BINARY 4
|
||||
#define LOBJECT_TEXT 8
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* !defined(PSYCOPG_LOBJECT_H) */
|
||||
486
psycopg/lobject_int.c
Normal file
486
psycopg/lobject_int.c
Normal file
@ -0,0 +1,486 @@
|
||||
/* lobject_int.c - code used by the lobject object
|
||||
*
|
||||
* Copyright (C) 2006-2019 Federico Di Gregorio <fog@debian.org>
|
||||
* Copyright (C) 2020-2021 The Psycopg Team
|
||||
*
|
||||
* This file is part of psycopg.
|
||||
*
|
||||
* psycopg2 is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give
|
||||
* permission to link this program with the OpenSSL library (or with
|
||||
* modified versions of OpenSSL that use the same license as OpenSSL),
|
||||
* and distribute linked combinations including the two.
|
||||
*
|
||||
* You must obey the GNU Lesser General Public License in all respects for
|
||||
* all of the code used other than OpenSSL.
|
||||
*
|
||||
* psycopg2 is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
|
||||
* License for more details.
|
||||
*/
|
||||
|
||||
#define PSYCOPG_MODULE
|
||||
#include "psycopg/psycopg.h"
|
||||
|
||||
#include "psycopg/lobject.h"
|
||||
#include "psycopg/connection.h"
|
||||
#include "psycopg/pqpath.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
static void
|
||||
collect_error(connectionObject *conn)
|
||||
{
|
||||
conn_set_error(conn, PQerrorMessage(conn->pgconn));
|
||||
}
|
||||
|
||||
|
||||
/* Check if the mode passed to the large object is valid.
|
||||
* In case of success return a value >= 0
|
||||
* On error return a value < 0 and set an exception.
|
||||
*
|
||||
* Valid mode are [r|w|rw|n][t|b]
|
||||
*/
|
||||
RAISES_NEG static int
|
||||
_lobject_parse_mode(const char *mode)
|
||||
{
|
||||
int rv = 0;
|
||||
size_t pos = 0;
|
||||
|
||||
if (0 == strncmp("rw", mode, 2)) {
|
||||
rv |= LOBJECT_READ | LOBJECT_WRITE;
|
||||
pos += 2;
|
||||
}
|
||||
else {
|
||||
switch (mode[0]) {
|
||||
case 'r':
|
||||
rv |= LOBJECT_READ;
|
||||
pos += 1;
|
||||
break;
|
||||
case 'w':
|
||||
rv |= LOBJECT_WRITE;
|
||||
pos += 1;
|
||||
break;
|
||||
case 'n':
|
||||
pos += 1;
|
||||
break;
|
||||
default:
|
||||
rv |= LOBJECT_READ;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
switch (mode[pos]) {
|
||||
case 't':
|
||||
rv |= LOBJECT_TEXT;
|
||||
pos += 1;
|
||||
break;
|
||||
case 'b':
|
||||
rv |= LOBJECT_BINARY;
|
||||
pos += 1;
|
||||
break;
|
||||
default:
|
||||
rv |= LOBJECT_TEXT;
|
||||
break;
|
||||
}
|
||||
|
||||
if (pos != strlen(mode)) {
|
||||
PyErr_Format(PyExc_ValueError,
|
||||
"bad mode for lobject: '%s'", mode);
|
||||
rv = -1;
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
|
||||
/* Return a string representing the lobject mode.
|
||||
*
|
||||
* The return value is a new string allocated on the Python heap.
|
||||
*
|
||||
* The function must be called holding the GIL.
|
||||
*/
|
||||
static char *
|
||||
_lobject_unparse_mode(int mode)
|
||||
{
|
||||
char *buf;
|
||||
char *c;
|
||||
|
||||
/* the longest is 'rwt' */
|
||||
if (!(c = buf = PyMem_Malloc(4))) {
|
||||
PyErr_NoMemory();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (mode & LOBJECT_READ) { *c++ = 'r'; }
|
||||
if (mode & LOBJECT_WRITE) { *c++ = 'w'; }
|
||||
|
||||
if (buf == c) {
|
||||
/* neither read nor write */
|
||||
*c++ = 'n';
|
||||
}
|
||||
else {
|
||||
if (mode & LOBJECT_TEXT) {
|
||||
*c++ = 't';
|
||||
}
|
||||
else {
|
||||
*c++ = 'b';
|
||||
}
|
||||
}
|
||||
*c = '\0';
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
/* lobject_open - create a new/open an existing lo */
|
||||
|
||||
RAISES_NEG int
|
||||
lobject_open(lobjectObject *self, connectionObject *conn,
|
||||
Oid oid, const char *smode, Oid new_oid, const char *new_file)
|
||||
{
|
||||
int retvalue = -1;
|
||||
int pgmode = 0;
|
||||
int mode;
|
||||
|
||||
if (0 > (mode = _lobject_parse_mode(smode))) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
Py_BEGIN_ALLOW_THREADS;
|
||||
pthread_mutex_lock(&(self->conn->lock));
|
||||
|
||||
retvalue = pq_begin_locked(self->conn, &_save);
|
||||
if (retvalue < 0)
|
||||
goto end;
|
||||
|
||||
/* if the oid is InvalidOid we create a new lob before opening it
|
||||
or we import a file from the FS, depending on the value of
|
||||
new_file */
|
||||
if (oid == InvalidOid) {
|
||||
if (new_file)
|
||||
self->oid = lo_import(self->conn->pgconn, new_file);
|
||||
else {
|
||||
/* Use lo_creat when possible to be more middleware-friendly.
|
||||
See ticket #88. */
|
||||
if (new_oid != InvalidOid)
|
||||
self->oid = lo_create(self->conn->pgconn, new_oid);
|
||||
else
|
||||
self->oid = lo_creat(self->conn->pgconn, INV_READ | INV_WRITE);
|
||||
}
|
||||
|
||||
Dprintf("lobject_open: large object created with oid = %u",
|
||||
self->oid);
|
||||
|
||||
if (self->oid == InvalidOid) {
|
||||
collect_error(self->conn);
|
||||
retvalue = -1;
|
||||
goto end;
|
||||
}
|
||||
|
||||
mode = (mode & ~LOBJECT_READ) | LOBJECT_WRITE;
|
||||
}
|
||||
else {
|
||||
self->oid = oid;
|
||||
}
|
||||
|
||||
/* if the oid is a real one we try to open with the given mode */
|
||||
if (mode & LOBJECT_READ) { pgmode |= INV_READ; }
|
||||
if (mode & LOBJECT_WRITE) { pgmode |= INV_WRITE; }
|
||||
if (pgmode) {
|
||||
self->fd = lo_open(self->conn->pgconn, self->oid, pgmode);
|
||||
Dprintf("lobject_open: large object opened with mode = %i fd = %d",
|
||||
pgmode, self->fd);
|
||||
|
||||
if (self->fd == -1) {
|
||||
collect_error(self->conn);
|
||||
retvalue = -1;
|
||||
goto end;
|
||||
}
|
||||
}
|
||||
|
||||
/* set the mode for future reference */
|
||||
self->mode = mode;
|
||||
Py_BLOCK_THREADS;
|
||||
self->smode = _lobject_unparse_mode(mode);
|
||||
Py_UNBLOCK_THREADS;
|
||||
if (NULL == self->smode) {
|
||||
retvalue = 1; /* exception already set */
|
||||
goto end;
|
||||
}
|
||||
|
||||
retvalue = 0;
|
||||
|
||||
end:
|
||||
pthread_mutex_unlock(&(self->conn->lock));
|
||||
Py_END_ALLOW_THREADS;
|
||||
|
||||
if (retvalue < 0)
|
||||
pq_complete_error(self->conn);
|
||||
/* if retvalue > 0, an exception is already set */
|
||||
|
||||
return retvalue;
|
||||
}
|
||||
|
||||
/* lobject_close - close an existing lo */
|
||||
|
||||
RAISES_NEG static int
|
||||
lobject_close_locked(lobjectObject *self)
|
||||
{
|
||||
int retvalue;
|
||||
|
||||
Dprintf("lobject_close_locked: conn->closed %ld", self->conn->closed);
|
||||
switch (self->conn->closed) {
|
||||
case 0:
|
||||
/* Connection is open, go ahead */
|
||||
break;
|
||||
case 1:
|
||||
/* Connection is closed, return a success */
|
||||
return 0;
|
||||
break;
|
||||
default:
|
||||
conn_set_error(self->conn, "the connection is broken");
|
||||
return -1;
|
||||
break;
|
||||
}
|
||||
|
||||
if (self->conn->autocommit ||
|
||||
self->conn->mark != self->mark ||
|
||||
self->fd == -1)
|
||||
return 0;
|
||||
|
||||
retvalue = lo_close(self->conn->pgconn, self->fd);
|
||||
self->fd = -1;
|
||||
if (retvalue < 0)
|
||||
collect_error(self->conn);
|
||||
|
||||
return retvalue;
|
||||
}
|
||||
|
||||
RAISES_NEG int
|
||||
lobject_close(lobjectObject *self)
|
||||
{
|
||||
int retvalue;
|
||||
|
||||
Py_BEGIN_ALLOW_THREADS;
|
||||
pthread_mutex_lock(&(self->conn->lock));
|
||||
|
||||
retvalue = lobject_close_locked(self);
|
||||
|
||||
pthread_mutex_unlock(&(self->conn->lock));
|
||||
Py_END_ALLOW_THREADS;
|
||||
|
||||
if (retvalue < 0)
|
||||
pq_complete_error(self->conn);
|
||||
return retvalue;
|
||||
}
|
||||
|
||||
/* lobject_unlink - remove an lo from database */
|
||||
|
||||
RAISES_NEG int
|
||||
lobject_unlink(lobjectObject *self)
|
||||
{
|
||||
int retvalue = -1;
|
||||
|
||||
Py_BEGIN_ALLOW_THREADS;
|
||||
pthread_mutex_lock(&(self->conn->lock));
|
||||
|
||||
retvalue = pq_begin_locked(self->conn, &_save);
|
||||
if (retvalue < 0)
|
||||
goto end;
|
||||
|
||||
/* first we make sure the lobject is closed and then we unlink */
|
||||
retvalue = lobject_close_locked(self);
|
||||
if (retvalue < 0)
|
||||
goto end;
|
||||
|
||||
retvalue = lo_unlink(self->conn->pgconn, self->oid);
|
||||
if (retvalue < 0)
|
||||
collect_error(self->conn);
|
||||
|
||||
end:
|
||||
pthread_mutex_unlock(&(self->conn->lock));
|
||||
Py_END_ALLOW_THREADS;
|
||||
|
||||
if (retvalue < 0)
|
||||
pq_complete_error(self->conn);
|
||||
return retvalue;
|
||||
}
|
||||
|
||||
/* lobject_write - write bytes to a lo */
|
||||
|
||||
RAISES_NEG Py_ssize_t
|
||||
lobject_write(lobjectObject *self, const char *buf, size_t len)
|
||||
{
|
||||
Py_ssize_t written;
|
||||
|
||||
Dprintf("lobject_writing: fd = %d, len = " FORMAT_CODE_SIZE_T,
|
||||
self->fd, len);
|
||||
|
||||
Py_BEGIN_ALLOW_THREADS;
|
||||
pthread_mutex_lock(&(self->conn->lock));
|
||||
|
||||
written = lo_write(self->conn->pgconn, self->fd, buf, len);
|
||||
if (written < 0)
|
||||
collect_error(self->conn);
|
||||
|
||||
pthread_mutex_unlock(&(self->conn->lock));
|
||||
Py_END_ALLOW_THREADS;
|
||||
|
||||
if (written < 0)
|
||||
pq_complete_error(self->conn);
|
||||
return written;
|
||||
}
|
||||
|
||||
/* lobject_read - read bytes from a lo */
|
||||
|
||||
RAISES_NEG Py_ssize_t
|
||||
lobject_read(lobjectObject *self, char *buf, size_t len)
|
||||
{
|
||||
Py_ssize_t n_read;
|
||||
|
||||
Py_BEGIN_ALLOW_THREADS;
|
||||
pthread_mutex_lock(&(self->conn->lock));
|
||||
|
||||
n_read = lo_read(self->conn->pgconn, self->fd, buf, len);
|
||||
if (n_read < 0)
|
||||
collect_error(self->conn);
|
||||
|
||||
pthread_mutex_unlock(&(self->conn->lock));
|
||||
Py_END_ALLOW_THREADS;
|
||||
|
||||
if (n_read < 0)
|
||||
pq_complete_error(self->conn);
|
||||
return n_read;
|
||||
}
|
||||
|
||||
/* lobject_seek - move the current position in the lo */
|
||||
|
||||
RAISES_NEG Py_ssize_t
|
||||
lobject_seek(lobjectObject *self, Py_ssize_t pos, int whence)
|
||||
{
|
||||
Py_ssize_t where;
|
||||
|
||||
Dprintf("lobject_seek: fd = %d, pos = " FORMAT_CODE_PY_SSIZE_T ", whence = %d",
|
||||
self->fd, pos, whence);
|
||||
|
||||
Py_BEGIN_ALLOW_THREADS;
|
||||
pthread_mutex_lock(&(self->conn->lock));
|
||||
|
||||
#ifdef HAVE_LO64
|
||||
if (self->conn->server_version < 90300) {
|
||||
where = (Py_ssize_t)lo_lseek(self->conn->pgconn, self->fd, (int)pos, whence);
|
||||
} else {
|
||||
where = (Py_ssize_t)lo_lseek64(self->conn->pgconn, self->fd, pos, whence);
|
||||
}
|
||||
#else
|
||||
where = (Py_ssize_t)lo_lseek(self->conn->pgconn, self->fd, (int)pos, whence);
|
||||
#endif
|
||||
Dprintf("lobject_seek: where = " FORMAT_CODE_PY_SSIZE_T, where);
|
||||
if (where < 0)
|
||||
collect_error(self->conn);
|
||||
|
||||
pthread_mutex_unlock(&(self->conn->lock));
|
||||
Py_END_ALLOW_THREADS;
|
||||
|
||||
if (where < 0)
|
||||
pq_complete_error(self->conn);
|
||||
return where;
|
||||
}
|
||||
|
||||
/* lobject_tell - tell the current position in the lo */
|
||||
|
||||
RAISES_NEG Py_ssize_t
|
||||
lobject_tell(lobjectObject *self)
|
||||
{
|
||||
Py_ssize_t where;
|
||||
|
||||
Dprintf("lobject_tell: fd = %d", self->fd);
|
||||
|
||||
Py_BEGIN_ALLOW_THREADS;
|
||||
pthread_mutex_lock(&(self->conn->lock));
|
||||
|
||||
#ifdef HAVE_LO64
|
||||
if (self->conn->server_version < 90300) {
|
||||
where = (Py_ssize_t)lo_tell(self->conn->pgconn, self->fd);
|
||||
} else {
|
||||
where = (Py_ssize_t)lo_tell64(self->conn->pgconn, self->fd);
|
||||
}
|
||||
#else
|
||||
where = (Py_ssize_t)lo_tell(self->conn->pgconn, self->fd);
|
||||
#endif
|
||||
Dprintf("lobject_tell: where = " FORMAT_CODE_PY_SSIZE_T, where);
|
||||
if (where < 0)
|
||||
collect_error(self->conn);
|
||||
|
||||
pthread_mutex_unlock(&(self->conn->lock));
|
||||
Py_END_ALLOW_THREADS;
|
||||
|
||||
if (where < 0)
|
||||
pq_complete_error(self->conn);
|
||||
return where;
|
||||
}
|
||||
|
||||
/* lobject_export - export to a local file */
|
||||
|
||||
RAISES_NEG int
|
||||
lobject_export(lobjectObject *self, const char *filename)
|
||||
{
|
||||
int retvalue;
|
||||
|
||||
Py_BEGIN_ALLOW_THREADS;
|
||||
pthread_mutex_lock(&(self->conn->lock));
|
||||
|
||||
retvalue = pq_begin_locked(self->conn, &_save);
|
||||
if (retvalue < 0)
|
||||
goto end;
|
||||
|
||||
retvalue = lo_export(self->conn->pgconn, self->oid, filename);
|
||||
if (retvalue < 0)
|
||||
collect_error(self->conn);
|
||||
|
||||
end:
|
||||
pthread_mutex_unlock(&(self->conn->lock));
|
||||
Py_END_ALLOW_THREADS;
|
||||
|
||||
if (retvalue < 0)
|
||||
pq_complete_error(self->conn);
|
||||
return retvalue;
|
||||
}
|
||||
|
||||
RAISES_NEG int
|
||||
lobject_truncate(lobjectObject *self, size_t len)
|
||||
{
|
||||
int retvalue;
|
||||
|
||||
Dprintf("lobject_truncate: fd = %d, len = " FORMAT_CODE_SIZE_T,
|
||||
self->fd, len);
|
||||
|
||||
Py_BEGIN_ALLOW_THREADS;
|
||||
pthread_mutex_lock(&(self->conn->lock));
|
||||
|
||||
#ifdef HAVE_LO64
|
||||
if (self->conn->server_version < 90300) {
|
||||
retvalue = lo_truncate(self->conn->pgconn, self->fd, len);
|
||||
} else {
|
||||
retvalue = lo_truncate64(self->conn->pgconn, self->fd, len);
|
||||
}
|
||||
#else
|
||||
retvalue = lo_truncate(self->conn->pgconn, self->fd, len);
|
||||
#endif
|
||||
Dprintf("lobject_truncate: result = %d", retvalue);
|
||||
if (retvalue < 0)
|
||||
collect_error(self->conn);
|
||||
|
||||
pthread_mutex_unlock(&(self->conn->lock));
|
||||
Py_END_ALLOW_THREADS;
|
||||
|
||||
if (retvalue < 0)
|
||||
pq_complete_error(self->conn);
|
||||
return retvalue;
|
||||
|
||||
}
|
||||
471
psycopg/lobject_type.c
Normal file
471
psycopg/lobject_type.c
Normal file
@ -0,0 +1,471 @@
|
||||
/* lobject_type.c - python interface to lobject objects
|
||||
*
|
||||
* Copyright (C) 2006-2019 Federico Di Gregorio <fog@debian.org>
|
||||
* Copyright (C) 2020-2021 The Psycopg Team
|
||||
*
|
||||
* This file is part of psycopg.
|
||||
*
|
||||
* psycopg2 is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give
|
||||
* permission to link this program with the OpenSSL library (or with
|
||||
* modified versions of OpenSSL that use the same license as OpenSSL),
|
||||
* and distribute linked combinations including the two.
|
||||
*
|
||||
* You must obey the GNU Lesser General Public License in all respects for
|
||||
* all of the code used other than OpenSSL.
|
||||
*
|
||||
* psycopg2 is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
|
||||
* License for more details.
|
||||
*/
|
||||
|
||||
#define PSYCOPG_MODULE
|
||||
#include "psycopg/psycopg.h"
|
||||
|
||||
#include "psycopg/lobject.h"
|
||||
#include "psycopg/connection.h"
|
||||
#include "psycopg/microprotocols.h"
|
||||
#include "psycopg/microprotocols_proto.h"
|
||||
#include "psycopg/pqpath.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
|
||||
/** public methods **/
|
||||
|
||||
/* close method - close the lobject */
|
||||
|
||||
#define psyco_lobj_close_doc \
|
||||
"close() -- Close the lobject."
|
||||
|
||||
static PyObject *
|
||||
psyco_lobj_close(lobjectObject *self, PyObject *args)
|
||||
{
|
||||
/* file-like objects can be closed multiple times and remember that
|
||||
closing the current transaction is equivalent to close all the
|
||||
opened large objects */
|
||||
if (!lobject_is_closed(self)
|
||||
&& !self->conn->autocommit
|
||||
&& self->conn->mark == self->mark)
|
||||
{
|
||||
Dprintf("psyco_lobj_close: closing lobject at %p", self);
|
||||
if (lobject_close(self) < 0)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
/* write method - write data to the lobject */
|
||||
|
||||
#define psyco_lobj_write_doc \
|
||||
"write(str) -- Write a string to the large object."
|
||||
|
||||
static PyObject *
|
||||
psyco_lobj_write(lobjectObject *self, PyObject *args)
|
||||
{
|
||||
char *buffer;
|
||||
Py_ssize_t len;
|
||||
Py_ssize_t res;
|
||||
PyObject *obj;
|
||||
PyObject *data = NULL;
|
||||
PyObject *rv = NULL;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "O", &obj)) return NULL;
|
||||
|
||||
EXC_IF_LOBJ_CLOSED(self);
|
||||
EXC_IF_LOBJ_LEVEL0(self);
|
||||
EXC_IF_LOBJ_UNMARKED(self);
|
||||
|
||||
if (Bytes_Check(obj)) {
|
||||
Py_INCREF(obj);
|
||||
data = obj;
|
||||
}
|
||||
else if (PyUnicode_Check(obj)) {
|
||||
if (!(data = conn_encode(self->conn, obj))) { goto exit; }
|
||||
}
|
||||
else {
|
||||
PyErr_Format(PyExc_TypeError,
|
||||
"lobject.write requires a string; got %s instead",
|
||||
Py_TYPE(obj)->tp_name);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (-1 == Bytes_AsStringAndSize(data, &buffer, &len)) {
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (0 > (res = lobject_write(self, buffer, (size_t)len))) {
|
||||
goto exit;
|
||||
}
|
||||
|
||||
rv = PyInt_FromSsize_t((Py_ssize_t)res);
|
||||
|
||||
exit:
|
||||
Py_XDECREF(data);
|
||||
return rv;
|
||||
}
|
||||
|
||||
/* read method - read data from the lobject */
|
||||
|
||||
#define psyco_lobj_read_doc \
|
||||
"read(size=-1) -- Read at most size bytes or to the end of the large object."
|
||||
|
||||
static PyObject *
|
||||
psyco_lobj_read(lobjectObject *self, PyObject *args)
|
||||
{
|
||||
PyObject *res;
|
||||
Py_ssize_t where, end;
|
||||
Py_ssize_t size = -1;
|
||||
char *buffer;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "|n", &size)) return NULL;
|
||||
|
||||
EXC_IF_LOBJ_CLOSED(self);
|
||||
EXC_IF_LOBJ_LEVEL0(self);
|
||||
EXC_IF_LOBJ_UNMARKED(self);
|
||||
|
||||
if (size < 0) {
|
||||
if ((where = lobject_tell(self)) < 0) return NULL;
|
||||
if ((end = lobject_seek(self, 0, SEEK_END)) < 0) return NULL;
|
||||
if (lobject_seek(self, where, SEEK_SET) < 0) return NULL;
|
||||
size = end - where;
|
||||
}
|
||||
|
||||
if ((buffer = PyMem_Malloc(size)) == NULL) {
|
||||
PyErr_NoMemory();
|
||||
return NULL;
|
||||
}
|
||||
if ((size = lobject_read(self, buffer, size)) < 0) {
|
||||
PyMem_Free(buffer);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (self->mode & LOBJECT_BINARY) {
|
||||
res = Bytes_FromStringAndSize(buffer, size);
|
||||
} else {
|
||||
res = conn_decode(self->conn, buffer, size);
|
||||
}
|
||||
PyMem_Free(buffer);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/* seek method - seek in the lobject */
|
||||
|
||||
#define psyco_lobj_seek_doc \
|
||||
"seek(offset, whence=0) -- Set the lobject's current position."
|
||||
|
||||
static PyObject *
|
||||
psyco_lobj_seek(lobjectObject *self, PyObject *args)
|
||||
{
|
||||
Py_ssize_t offset, pos=0;
|
||||
int whence=0;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "n|i", &offset, &whence))
|
||||
return NULL;
|
||||
|
||||
EXC_IF_LOBJ_CLOSED(self);
|
||||
EXC_IF_LOBJ_LEVEL0(self);
|
||||
EXC_IF_LOBJ_UNMARKED(self);
|
||||
|
||||
#ifdef HAVE_LO64
|
||||
if ((offset < INT_MIN || offset > INT_MAX)
|
||||
&& self->conn->server_version < 90300) {
|
||||
PyErr_Format(NotSupportedError,
|
||||
"offset out of range (%ld): server version %d "
|
||||
"does not support the lobject 64 API",
|
||||
offset, self->conn->server_version);
|
||||
return NULL;
|
||||
}
|
||||
#else
|
||||
if (offset < INT_MIN || offset > INT_MAX) {
|
||||
PyErr_Format(InterfaceError,
|
||||
"offset out of range (" FORMAT_CODE_PY_SSIZE_T "): "
|
||||
"this psycopg version was not built with lobject 64 API support",
|
||||
offset);
|
||||
return NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
if ((pos = lobject_seek(self, offset, whence)) < 0)
|
||||
return NULL;
|
||||
|
||||
return PyInt_FromSsize_t(pos);
|
||||
}
|
||||
|
||||
/* tell method - tell current position in the lobject */
|
||||
|
||||
#define psyco_lobj_tell_doc \
|
||||
"tell() -- Return the lobject's current position."
|
||||
|
||||
static PyObject *
|
||||
psyco_lobj_tell(lobjectObject *self, PyObject *args)
|
||||
{
|
||||
Py_ssize_t pos;
|
||||
|
||||
EXC_IF_LOBJ_CLOSED(self);
|
||||
EXC_IF_LOBJ_LEVEL0(self);
|
||||
EXC_IF_LOBJ_UNMARKED(self);
|
||||
|
||||
if ((pos = lobject_tell(self)) < 0)
|
||||
return NULL;
|
||||
|
||||
return PyInt_FromSsize_t(pos);
|
||||
}
|
||||
|
||||
/* unlink method - unlink (destroy) the lobject */
|
||||
|
||||
#define psyco_lobj_unlink_doc \
|
||||
"unlink() -- Close and then remove the lobject."
|
||||
|
||||
static PyObject *
|
||||
psyco_lobj_unlink(lobjectObject *self, PyObject *args)
|
||||
{
|
||||
if (lobject_unlink(self) < 0)
|
||||
return NULL;
|
||||
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
/* export method - export lobject's content to given file */
|
||||
|
||||
#define psyco_lobj_export_doc \
|
||||
"export(filename) -- Export large object to given file."
|
||||
|
||||
static PyObject *
|
||||
psyco_lobj_export(lobjectObject *self, PyObject *args)
|
||||
{
|
||||
const char *filename;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "s", &filename))
|
||||
return NULL;
|
||||
|
||||
EXC_IF_LOBJ_LEVEL0(self);
|
||||
|
||||
if (lobject_export(self, filename) < 0)
|
||||
return NULL;
|
||||
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
|
||||
static PyObject *
|
||||
psyco_lobj_get_closed(lobjectObject *self, void *closure)
|
||||
{
|
||||
return PyBool_FromLong(lobject_is_closed(self));
|
||||
}
|
||||
|
||||
#define psyco_lobj_truncate_doc \
|
||||
"truncate(len=0) -- Truncate large object to given size."
|
||||
|
||||
static PyObject *
|
||||
psyco_lobj_truncate(lobjectObject *self, PyObject *args)
|
||||
{
|
||||
Py_ssize_t len = 0;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "|n", &len))
|
||||
return NULL;
|
||||
|
||||
EXC_IF_LOBJ_CLOSED(self);
|
||||
EXC_IF_LOBJ_LEVEL0(self);
|
||||
EXC_IF_LOBJ_UNMARKED(self);
|
||||
|
||||
#ifdef HAVE_LO64
|
||||
if (len > INT_MAX && self->conn->server_version < 90300) {
|
||||
PyErr_Format(NotSupportedError,
|
||||
"len out of range (" FORMAT_CODE_PY_SSIZE_T "): "
|
||||
"server version %d does not support the lobject 64 API",
|
||||
len, self->conn->server_version);
|
||||
return NULL;
|
||||
}
|
||||
#else
|
||||
if (len > INT_MAX) {
|
||||
PyErr_Format(InterfaceError,
|
||||
"len out of range (" FORMAT_CODE_PY_SSIZE_T "): "
|
||||
"this psycopg version was not built with lobject 64 API support",
|
||||
len);
|
||||
return NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (lobject_truncate(self, len) < 0)
|
||||
return NULL;
|
||||
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
|
||||
/** the lobject object **/
|
||||
|
||||
/* object method list */
|
||||
|
||||
static struct PyMethodDef lobjectObject_methods[] = {
|
||||
{"read", (PyCFunction)psyco_lobj_read,
|
||||
METH_VARARGS, psyco_lobj_read_doc},
|
||||
{"write", (PyCFunction)psyco_lobj_write,
|
||||
METH_VARARGS, psyco_lobj_write_doc},
|
||||
{"seek", (PyCFunction)psyco_lobj_seek,
|
||||
METH_VARARGS, psyco_lobj_seek_doc},
|
||||
{"tell", (PyCFunction)psyco_lobj_tell,
|
||||
METH_NOARGS, psyco_lobj_tell_doc},
|
||||
{"close", (PyCFunction)psyco_lobj_close,
|
||||
METH_NOARGS, psyco_lobj_close_doc},
|
||||
{"unlink",(PyCFunction)psyco_lobj_unlink,
|
||||
METH_NOARGS, psyco_lobj_unlink_doc},
|
||||
{"export",(PyCFunction)psyco_lobj_export,
|
||||
METH_VARARGS, psyco_lobj_export_doc},
|
||||
{"truncate",(PyCFunction)psyco_lobj_truncate,
|
||||
METH_VARARGS, psyco_lobj_truncate_doc},
|
||||
{NULL}
|
||||
};
|
||||
|
||||
/* object member list */
|
||||
|
||||
static struct PyMemberDef lobjectObject_members[] = {
|
||||
{"oid", T_OID, offsetof(lobjectObject, oid), READONLY,
|
||||
"The backend OID associated to this lobject."},
|
||||
{"mode", T_STRING, offsetof(lobjectObject, smode), READONLY,
|
||||
"Open mode."},
|
||||
{NULL}
|
||||
};
|
||||
|
||||
/* object getset list */
|
||||
|
||||
static struct PyGetSetDef lobjectObject_getsets[] = {
|
||||
{"closed", (getter)psyco_lobj_get_closed, NULL,
|
||||
"The if the large object is closed (no file-like methods)."},
|
||||
{NULL}
|
||||
};
|
||||
|
||||
/* initialization and finalization methods */
|
||||
|
||||
static int
|
||||
lobject_setup(lobjectObject *self, connectionObject *conn,
|
||||
Oid oid, const char *smode, Oid new_oid, const char *new_file)
|
||||
{
|
||||
Dprintf("lobject_setup: init lobject object at %p", self);
|
||||
|
||||
if (conn->autocommit) {
|
||||
psyco_set_error(ProgrammingError, NULL,
|
||||
"can't use a lobject outside of transactions");
|
||||
return -1;
|
||||
}
|
||||
|
||||
Py_INCREF((PyObject*)conn);
|
||||
self->conn = conn;
|
||||
self->mark = conn->mark;
|
||||
|
||||
self->fd = -1;
|
||||
self->oid = InvalidOid;
|
||||
|
||||
if (0 != lobject_open(self, conn, oid, smode, new_oid, new_file))
|
||||
return -1;
|
||||
|
||||
Dprintf("lobject_setup: good lobject object at %p, refcnt = "
|
||||
FORMAT_CODE_PY_SSIZE_T, self, Py_REFCNT(self));
|
||||
Dprintf("lobject_setup: oid = %u, fd = %d", self->oid, self->fd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
lobject_dealloc(PyObject* obj)
|
||||
{
|
||||
lobjectObject *self = (lobjectObject *)obj;
|
||||
|
||||
if (self->conn && self->fd != -1) {
|
||||
if (lobject_close(self) < 0)
|
||||
PyErr_Print();
|
||||
}
|
||||
Py_CLEAR(self->conn);
|
||||
PyMem_Free(self->smode);
|
||||
|
||||
Dprintf("lobject_dealloc: deleted lobject object at %p, refcnt = "
|
||||
FORMAT_CODE_PY_SSIZE_T, obj, Py_REFCNT(obj));
|
||||
|
||||
Py_TYPE(obj)->tp_free(obj);
|
||||
}
|
||||
|
||||
static int
|
||||
lobject_init(PyObject *obj, PyObject *args, PyObject *kwds)
|
||||
{
|
||||
Oid oid = InvalidOid, new_oid = InvalidOid;
|
||||
const char *smode = NULL;
|
||||
const char *new_file = NULL;
|
||||
PyObject *conn = NULL;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "O!|IzIz",
|
||||
&connectionType, &conn,
|
||||
&oid, &smode, &new_oid, &new_file))
|
||||
return -1;
|
||||
|
||||
if (!smode)
|
||||
smode = "";
|
||||
|
||||
return lobject_setup((lobjectObject *)obj,
|
||||
(connectionObject *)conn, oid, smode, new_oid, new_file);
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
lobject_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
|
||||
{
|
||||
return type->tp_alloc(type, 0);
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
lobject_repr(lobjectObject *self)
|
||||
{
|
||||
return PyString_FromFormat(
|
||||
"<lobject object at %p; closed: %d>", self, lobject_is_closed(self));
|
||||
}
|
||||
|
||||
|
||||
/* object type */
|
||||
|
||||
#define lobjectType_doc \
|
||||
"A database large object."
|
||||
|
||||
PyTypeObject lobjectType = {
|
||||
PyVarObject_HEAD_INIT(NULL, 0)
|
||||
"psycopg2.extensions.lobject",
|
||||
sizeof(lobjectObject), 0,
|
||||
lobject_dealloc, /*tp_dealloc*/
|
||||
0, /*tp_print*/
|
||||
0, /*tp_getattr*/
|
||||
0, /*tp_setattr*/
|
||||
0, /*tp_compare*/
|
||||
(reprfunc)lobject_repr, /*tp_repr*/
|
||||
0, /*tp_as_number*/
|
||||
0, /*tp_as_sequence*/
|
||||
0, /*tp_as_mapping*/
|
||||
0, /*tp_hash */
|
||||
0, /*tp_call*/
|
||||
(reprfunc)lobject_repr, /*tp_str*/
|
||||
0, /*tp_getattro*/
|
||||
0, /*tp_setattro*/
|
||||
0, /*tp_as_buffer*/
|
||||
Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE|Py_TPFLAGS_HAVE_ITER, /*tp_flags*/
|
||||
lobjectType_doc, /*tp_doc*/
|
||||
0, /*tp_traverse*/
|
||||
0, /*tp_clear*/
|
||||
0, /*tp_richcompare*/
|
||||
0, /*tp_weaklistoffset*/
|
||||
0, /*tp_iter*/
|
||||
0, /*tp_iternext*/
|
||||
lobjectObject_methods, /*tp_methods*/
|
||||
lobjectObject_members, /*tp_members*/
|
||||
lobjectObject_getsets, /*tp_getset*/
|
||||
0, /*tp_base*/
|
||||
0, /*tp_dict*/
|
||||
0, /*tp_descr_get*/
|
||||
0, /*tp_descr_set*/
|
||||
0, /*tp_dictoffset*/
|
||||
lobject_init, /*tp_init*/
|
||||
0, /*tp_alloc*/
|
||||
lobject_new, /*tp_new*/
|
||||
};
|
||||
277
psycopg/microprotocols.c
Normal file
277
psycopg/microprotocols.c
Normal file
@ -0,0 +1,277 @@
|
||||
/* microprotocols.c - minimalist and non-validating protocols implementation
|
||||
*
|
||||
* Copyright (C) 2003-2019 Federico Di Gregorio <fog@debian.org>
|
||||
* Copyright (C) 2020-2021 The Psycopg Team
|
||||
*
|
||||
* This file is part of psycopg.
|
||||
*
|
||||
* psycopg2 is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give
|
||||
* permission to link this program with the OpenSSL library (or with
|
||||
* modified versions of OpenSSL that use the same license as OpenSSL),
|
||||
* and distribute linked combinations including the two.
|
||||
*
|
||||
* You must obey the GNU Lesser General Public License in all respects for
|
||||
* all of the code used other than OpenSSL.
|
||||
*
|
||||
* psycopg2 is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
|
||||
* License for more details.
|
||||
*/
|
||||
|
||||
#define PSYCOPG_MODULE
|
||||
#include "psycopg/psycopg.h"
|
||||
|
||||
#include "psycopg/microprotocols.h"
|
||||
#include "psycopg/microprotocols_proto.h"
|
||||
#include "psycopg/cursor.h"
|
||||
#include "psycopg/connection.h"
|
||||
|
||||
|
||||
/** the adapters registry **/
|
||||
|
||||
PyObject *psyco_adapters;
|
||||
|
||||
/* microprotocols_init - initialize the adapters dictionary */
|
||||
|
||||
RAISES_NEG int
|
||||
microprotocols_init(PyObject *module)
|
||||
{
|
||||
/* create adapters dictionary and put it in module namespace */
|
||||
if (!(psyco_adapters = PyDict_New())) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
Py_INCREF(psyco_adapters);
|
||||
if (0 > PyModule_AddObject(module, "adapters", psyco_adapters)) {
|
||||
Py_DECREF(psyco_adapters);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/* microprotocols_add - add a reverse type-caster to the dictionary
|
||||
*
|
||||
* Return 0 on success, else -1 and set an exception.
|
||||
*/
|
||||
RAISES_NEG int
|
||||
microprotocols_add(PyTypeObject *type, PyObject *proto, PyObject *cast)
|
||||
{
|
||||
PyObject *key = NULL;
|
||||
int rv = -1;
|
||||
|
||||
if (proto == NULL) proto = (PyObject*)&isqlquoteType;
|
||||
|
||||
if (!(key = PyTuple_Pack(2, (PyObject*)type, proto))) { goto exit; }
|
||||
if (0 != PyDict_SetItem(psyco_adapters, key, cast)) { goto exit; }
|
||||
|
||||
rv = 0;
|
||||
|
||||
exit:
|
||||
Py_XDECREF(key);
|
||||
return rv;
|
||||
}
|
||||
|
||||
/* Check if one of `obj` superclasses has an adapter for `proto`.
|
||||
*
|
||||
* If it does, return a *borrowed reference* to the adapter, else to None.
|
||||
*/
|
||||
BORROWED static PyObject *
|
||||
_get_superclass_adapter(PyObject *obj, PyObject *proto)
|
||||
{
|
||||
PyTypeObject *type;
|
||||
PyObject *mro, *st;
|
||||
PyObject *key, *adapter;
|
||||
Py_ssize_t i, ii;
|
||||
|
||||
type = Py_TYPE(obj);
|
||||
if (!(type->tp_mro)) {
|
||||
/* has no mro */
|
||||
return Py_None;
|
||||
}
|
||||
|
||||
/* Walk the mro from the most specific subclass. */
|
||||
mro = type->tp_mro;
|
||||
for (i = 1, ii = PyTuple_GET_SIZE(mro); i < ii; ++i) {
|
||||
st = PyTuple_GET_ITEM(mro, i);
|
||||
if (!(key = PyTuple_Pack(2, st, proto))) { return NULL; }
|
||||
adapter = PyDict_GetItem(psyco_adapters, key);
|
||||
Py_DECREF(key);
|
||||
|
||||
if (adapter) {
|
||||
Dprintf(
|
||||
"microprotocols_adapt: using '%s' adapter to adapt '%s'",
|
||||
((PyTypeObject *)st)->tp_name, type->tp_name);
|
||||
|
||||
/* register this adapter as good for the subclass too,
|
||||
* so that the next time it will be found in the fast path */
|
||||
|
||||
/* Well, no, maybe this is not a good idea.
|
||||
* It would become a leak in case of dynamic
|
||||
* classes generated in a loop (think namedtuples). */
|
||||
|
||||
/* key = PyTuple_Pack(2, (PyObject*)type, proto);
|
||||
* PyDict_SetItem(psyco_adapters, key, adapter);
|
||||
* Py_DECREF(key);
|
||||
*/
|
||||
return adapter;
|
||||
}
|
||||
}
|
||||
return Py_None;
|
||||
}
|
||||
|
||||
|
||||
/* microprotocols_adapt - adapt an object to the built-in protocol */
|
||||
|
||||
PyObject *
|
||||
microprotocols_adapt(PyObject *obj, PyObject *proto, PyObject *alt)
|
||||
{
|
||||
PyObject *adapter, *adapted, *key, *meth;
|
||||
char buffer[256];
|
||||
|
||||
/* we don't check for exact type conformance as specified in PEP 246
|
||||
because the ISQLQuote type is abstract and there is no way to get a
|
||||
quotable object to be its instance */
|
||||
|
||||
Dprintf("microprotocols_adapt: trying to adapt %s",
|
||||
Py_TYPE(obj)->tp_name);
|
||||
|
||||
/* look for an adapter in the registry */
|
||||
if (!(key = PyTuple_Pack(2, Py_TYPE(obj), proto))) { return NULL; }
|
||||
adapter = PyDict_GetItem(psyco_adapters, key);
|
||||
Py_DECREF(key);
|
||||
if (adapter) {
|
||||
adapted = PyObject_CallFunctionObjArgs(adapter, obj, NULL);
|
||||
return adapted;
|
||||
}
|
||||
|
||||
/* try to have the protocol adapt this object*/
|
||||
if ((meth = PyObject_GetAttrString(proto, "__adapt__"))) {
|
||||
adapted = PyObject_CallFunctionObjArgs(meth, obj, NULL);
|
||||
Py_DECREF(meth);
|
||||
if (adapted && adapted != Py_None) return adapted;
|
||||
Py_XDECREF(adapted);
|
||||
if (PyErr_Occurred()) {
|
||||
if (PyErr_ExceptionMatches(PyExc_TypeError)) {
|
||||
PyErr_Clear();
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* proto.__adapt__ not found. */
|
||||
PyErr_Clear();
|
||||
}
|
||||
|
||||
/* then try to have the object adapt itself */
|
||||
if ((meth = PyObject_GetAttrString(obj, "__conform__"))) {
|
||||
adapted = PyObject_CallFunctionObjArgs(meth, proto, NULL);
|
||||
Py_DECREF(meth);
|
||||
if (adapted && adapted != Py_None) return adapted;
|
||||
Py_XDECREF(adapted);
|
||||
if (PyErr_Occurred()) {
|
||||
if (PyErr_ExceptionMatches(PyExc_TypeError)) {
|
||||
PyErr_Clear();
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* obj.__conform__ not found. */
|
||||
PyErr_Clear();
|
||||
}
|
||||
|
||||
/* Finally check if a superclass can be adapted and use the same adapter. */
|
||||
if (!(adapter = _get_superclass_adapter(obj, proto))) {
|
||||
return NULL;
|
||||
}
|
||||
if (Py_None != adapter) {
|
||||
adapted = PyObject_CallFunctionObjArgs(adapter, obj, NULL);
|
||||
return adapted;
|
||||
}
|
||||
|
||||
/* else set the right exception and return NULL */
|
||||
PyOS_snprintf(buffer, 255, "can't adapt type '%s'",
|
||||
Py_TYPE(obj)->tp_name);
|
||||
psyco_set_error(ProgrammingError, NULL, buffer);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* microprotocol_getquoted - utility function that adapt and call getquoted.
|
||||
*
|
||||
* Return a bytes string, NULL on error.
|
||||
*/
|
||||
|
||||
PyObject *
|
||||
microprotocol_getquoted(PyObject *obj, connectionObject *conn)
|
||||
{
|
||||
PyObject *res = NULL;
|
||||
PyObject *prepare = NULL;
|
||||
PyObject *adapted;
|
||||
|
||||
if (!(adapted = microprotocols_adapt(obj, (PyObject*)&isqlquoteType, NULL))) {
|
||||
goto exit;
|
||||
}
|
||||
|
||||
Dprintf("microprotocol_getquoted: adapted to %s",
|
||||
Py_TYPE(adapted)->tp_name);
|
||||
|
||||
/* if requested prepare the object passing it the connection */
|
||||
if (conn) {
|
||||
if ((prepare = PyObject_GetAttrString(adapted, "prepare"))) {
|
||||
res = PyObject_CallFunctionObjArgs(
|
||||
prepare, (PyObject *)conn, NULL);
|
||||
if (res) {
|
||||
Py_DECREF(res);
|
||||
res = NULL;
|
||||
} else {
|
||||
goto exit;
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* adapted.prepare not found */
|
||||
PyErr_Clear();
|
||||
}
|
||||
}
|
||||
|
||||
/* call the getquoted method on adapted (that should exist because we
|
||||
adapted to the right protocol) */
|
||||
res = PyObject_CallMethod(adapted, "getquoted", NULL);
|
||||
|
||||
/* Convert to bytes. */
|
||||
if (res && PyUnicode_CheckExact(res)) {
|
||||
PyObject *b;
|
||||
b = conn_encode(conn, res);
|
||||
Py_DECREF(res);
|
||||
res = b;
|
||||
}
|
||||
|
||||
exit:
|
||||
Py_XDECREF(adapted);
|
||||
Py_XDECREF(prepare);
|
||||
|
||||
/* we return res with one extra reference, the caller shall free it */
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
/** module-level functions **/
|
||||
|
||||
PyObject *
|
||||
psyco_microprotocols_adapt(cursorObject *self, PyObject *args)
|
||||
{
|
||||
PyObject *obj, *alt = NULL;
|
||||
PyObject *proto = (PyObject*)&isqlquoteType;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "O|OO", &obj, &proto, &alt)) return NULL;
|
||||
return microprotocols_adapt(obj, proto, alt);
|
||||
}
|
||||
64
psycopg/microprotocols.h
Normal file
64
psycopg/microprotocols.h
Normal file
@ -0,0 +1,64 @@
|
||||
/* microprotocols.c - definitions for minimalist and non-validating protocols
|
||||
*
|
||||
* Copyright (C) 2003-2019 Federico Di Gregorio <fog@debian.org>
|
||||
* Copyright (C) 2020-2021 The Psycopg Team
|
||||
*
|
||||
* This file is part of psycopg.
|
||||
*
|
||||
* psycopg2 is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give
|
||||
* permission to link this program with the OpenSSL library (or with
|
||||
* modified versions of OpenSSL that use the same license as OpenSSL),
|
||||
* and distribute linked combinations including the two.
|
||||
*
|
||||
* You must obey the GNU Lesser General Public License in all respects for
|
||||
* all of the code used other than OpenSSL.
|
||||
*
|
||||
* psycopg2 is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
|
||||
* License for more details.
|
||||
*/
|
||||
|
||||
#ifndef PSYCOPG_MICROPROTOCOLS_H
|
||||
#define PSYCOPG_MICROPROTOCOLS_H 1
|
||||
|
||||
#include "psycopg/connection.h"
|
||||
#include "psycopg/cursor.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** adapters registry **/
|
||||
|
||||
extern HIDDEN 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 */
|
||||
HIDDEN RAISES_NEG int microprotocols_init(PyObject *dict);
|
||||
HIDDEN RAISES_NEG int microprotocols_add(
|
||||
PyTypeObject *type, PyObject *proto, PyObject *cast);
|
||||
|
||||
HIDDEN PyObject *microprotocols_adapt(
|
||||
PyObject *obj, PyObject *proto, PyObject *alt);
|
||||
HIDDEN PyObject *microprotocol_getquoted(
|
||||
PyObject *obj, connectionObject *conn);
|
||||
|
||||
HIDDEN PyObject *
|
||||
psyco_microprotocols_adapt(cursorObject *self, PyObject *args);
|
||||
#define psyco_microprotocols_adapt_doc \
|
||||
"adapt(obj, protocol, alternate) -> object -- adapt obj to given protocol"
|
||||
|
||||
#endif /* !defined(PSYCOPG_MICROPROTOCOLS_H) */
|
||||
180
psycopg/microprotocols_proto.c
Normal file
180
psycopg/microprotocols_proto.c
Normal file
@ -0,0 +1,180 @@
|
||||
/* microprotocol_proto.c - psycopg protocols
|
||||
*
|
||||
* Copyright (C) 2003-2019 Federico Di Gregorio <fog@debian.org>
|
||||
* Copyright (C) 2020-2021 The Psycopg Team
|
||||
*
|
||||
* This file is part of psycopg.
|
||||
*
|
||||
* psycopg2 is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give
|
||||
* permission to link this program with the OpenSSL library (or with
|
||||
* modified versions of OpenSSL that use the same license as OpenSSL),
|
||||
* and distribute linked combinations including the two.
|
||||
*
|
||||
* You must obey the GNU Lesser General Public License in all respects for
|
||||
* all of the code used other than OpenSSL.
|
||||
*
|
||||
* psycopg2 is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
|
||||
* License for more details.
|
||||
*/
|
||||
|
||||
#define PSYCOPG_MODULE
|
||||
#include "psycopg/psycopg.h"
|
||||
|
||||
#include "psycopg/microprotocols_proto.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
|
||||
/** void protocol implementation **/
|
||||
|
||||
|
||||
/* getquoted - return quoted representation for object */
|
||||
|
||||
#define isqlquote_getquoted_doc \
|
||||
"getquoted() -- return SQL-quoted representation of this object"
|
||||
|
||||
static PyObject *
|
||||
isqlquote_getquoted(isqlquoteObject *self, PyObject *args)
|
||||
{
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
/* getbinary - return quoted representation for object */
|
||||
|
||||
#define isqlquote_getbinary_doc \
|
||||
"getbinary() -- return SQL-quoted binary representation of this object"
|
||||
|
||||
static PyObject *
|
||||
isqlquote_getbinary(isqlquoteObject *self, PyObject *args)
|
||||
{
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
/* getbuffer - return quoted representation for object */
|
||||
|
||||
#define isqlquote_getbuffer_doc \
|
||||
"getbuffer() -- return this object"
|
||||
|
||||
static PyObject *
|
||||
isqlquote_getbuffer(isqlquoteObject *self, PyObject *args)
|
||||
{
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/** the ISQLQuote object **/
|
||||
|
||||
|
||||
/* object method list */
|
||||
|
||||
static struct PyMethodDef isqlquoteObject_methods[] = {
|
||||
{"getquoted", (PyCFunction)isqlquote_getquoted,
|
||||
METH_NOARGS, isqlquote_getquoted_doc},
|
||||
{"getbinary", (PyCFunction)isqlquote_getbinary,
|
||||
METH_NOARGS, isqlquote_getbinary_doc},
|
||||
{"getbuffer", (PyCFunction)isqlquote_getbuffer,
|
||||
METH_NOARGS, isqlquote_getbuffer_doc},
|
||||
{NULL}
|
||||
};
|
||||
|
||||
/* object member list */
|
||||
|
||||
static struct PyMemberDef isqlquoteObject_members[] = {
|
||||
/* DBAPI-2.0 extensions (exception objects) */
|
||||
{"_wrapped", T_OBJECT, offsetof(isqlquoteObject, wrapped), READONLY},
|
||||
{NULL}
|
||||
};
|
||||
|
||||
/* initialization and finalization methods */
|
||||
|
||||
static int
|
||||
isqlquote_setup(isqlquoteObject *self, PyObject *wrapped)
|
||||
{
|
||||
self->wrapped = wrapped;
|
||||
Py_INCREF(wrapped);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
isqlquote_dealloc(PyObject* obj)
|
||||
{
|
||||
isqlquoteObject *self = (isqlquoteObject *)obj;
|
||||
|
||||
Py_XDECREF(self->wrapped);
|
||||
|
||||
Py_TYPE(obj)->tp_free(obj);
|
||||
}
|
||||
|
||||
static int
|
||||
isqlquote_init(PyObject *obj, PyObject *args, PyObject *kwds)
|
||||
{
|
||||
PyObject *wrapped = NULL;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "O", &wrapped))
|
||||
return -1;
|
||||
|
||||
return isqlquote_setup((isqlquoteObject *)obj, wrapped);
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
isqlquote_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
|
||||
{
|
||||
return type->tp_alloc(type, 0);
|
||||
}
|
||||
|
||||
|
||||
/* object type */
|
||||
|
||||
#define isqlquoteType_doc \
|
||||
"Abstract ISQLQuote protocol\n\n" \
|
||||
"An object conform to this protocol should expose a ``getquoted()`` method\n" \
|
||||
"returning the SQL representation of the object.\n\n"
|
||||
|
||||
PyTypeObject isqlquoteType = {
|
||||
PyVarObject_HEAD_INIT(NULL, 0)
|
||||
"psycopg2.extensions.ISQLQuote",
|
||||
sizeof(isqlquoteObject), 0,
|
||||
isqlquote_dealloc, /*tp_dealloc*/
|
||||
0, /*tp_print*/
|
||||
0, /*tp_getattr*/
|
||||
0, /*tp_setattr*/
|
||||
0, /*tp_compare*/
|
||||
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*/
|
||||
isqlquoteType_doc, /*tp_doc*/
|
||||
0, /*tp_traverse*/
|
||||
0, /*tp_clear*/
|
||||
0, /*tp_richcompare*/
|
||||
0, /*tp_weaklistoffset*/
|
||||
0, /*tp_iter*/
|
||||
0, /*tp_iternext*/
|
||||
isqlquoteObject_methods, /*tp_methods*/
|
||||
isqlquoteObject_members, /*tp_members*/
|
||||
0, /*tp_getset*/
|
||||
0, /*tp_base*/
|
||||
0, /*tp_dict*/
|
||||
0, /*tp_descr_get*/
|
||||
0, /*tp_descr_set*/
|
||||
0, /*tp_dictoffset*/
|
||||
isqlquote_init, /*tp_init*/
|
||||
0, /*tp_alloc*/
|
||||
isqlquote_new, /*tp_new*/
|
||||
};
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user