Browse Source

Release v0.9 [No backward compatibility!]

Added prepared SQL statements support (see README)
New `libuuid` dependency (also added missing CMake recipies)
Updated syntax of function `query()` to prinitf-like
More precise UPDATE/INSERT/DELETE status handling
Code clean-up
master
annelin 5 months ago
parent
commit
3ab6883091
8 changed files with 835 additions and 187 deletions
  1. 11
    1
      CMakeLists.txt
  2. 43
    21
      README.md
  3. 493
    0
      cmake/FindOpenSSL.cmake
  4. 116
    0
      cmake/FindUUID.cmake
  5. 6
    2
      pg-tarantool-hg-999.rockspec
  6. 68
    127
      pg_tarantool/driver.c
  7. 92
    34
      pg_tarantool/init.lua
  8. 6
    2
      pg_tarantool/types/init.lua

+ 11
- 1
CMakeLists.txt View File

@@ -22,8 +22,18 @@ find_package(OpenSSL REQUIRED)
include_directories(${OPENSSL_INCLUDE_DIRS})
link_directories(${OPENSSL_LIBRARIES})

# SSL
find_package(OpenSSL REQUIRED)
include_directories(${OPENSSL_INCLUDE_DIRS})
link_directories(${OPENSSL_LIBRARIES})

# UUID
find_package(UUID REQUIRED)
include_directories(${UUID_INCLUDE_DIRS})
link_directories(${UUID_LIBRARIES})

# Set CFLAGS
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DWITH_OPENSSL")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DWITH_OPENSSL -luuid")
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -Wall -Wextra")

if(${CMAKE_SYSTEM_NAME} STREQUAL "Darwin")

+ 43
- 21
README.md View File

@@ -4,8 +4,10 @@

### Prerequisites

* Tarantool 1.10+ with header files (tarantool && tarantool-dev packages)
* PostgreSQL 9.1+ header files (libpq-dev package)
* Tarantool 1.10+ with headers (tarantool && tarantool-dev packages)
* PostgreSQL 9.1+ headers (libpq-dev package)
* OpenSSL headers (libssl-dev package)
* libuuid headers (buuid-dev package)
* Recommended: lua-cjon and lpeg

### Installation
@@ -35,7 +37,7 @@ data = link:query("SELECT 1 as ONE, '2' as TWOSTRING")

## API Documentation

### `link = pg:connect(opts = {connections = 1})`
### `link = pg.connect(opts = {connections = 1})`

Connect to a database.
Returns _link_ (connections pool).
@@ -49,31 +51,52 @@ Returns _link_ (connections pool).
- `dbname` - a database name
- `sslmode` - a ssl mode used in this connection (see [Table 31-1. SSL Mode Descriptions][PG_sslmode]) (default = 'disable')
- `connections` - connection pool size (0 means do not create connection pool, just establish single connection) (default = '0')
- `conn_string` (mutual exclusive with host, port, user, pass, db) (see [PostgreSQL Connection String][PG_connstring]

*Returns*:

- `link`, database connections pool,
- `false`, `error_reason` on error

### `link:query(statement, ...)`
### `link:query(statement, arg1, arg2, ...)`

Execute a statement with arguments in the current transaction.
Arguments can be: numeric, string or table types. Arguments will escape-quote automatically.
For table (array) types, different PostgreSQL storage types are supported: `Array`, `JSON`.
By default, for simple array `Array` time will be used, and for associative — `jsonb`.
Executes a statement with given arguments in `printf`-like style.
Arguments can be: numeric, string or table types. They will be escaped and formatted automatically.

*Returns*:

- `{result1}`, `{result2}`, `...` on success
- `false`, `error description`, on error

*Example*:
```
### `link:query("SELECT %d AS a, %s AS b", 42, 'string_value')
---
- - - a: 42
b: "string_value"
...
```

### `link:prepare(statement, arg1, arg2, ...)`

Prepare a SQL statement.

*Returns*:
- `{ {column1 = value, ... }, ... }`, `nil`, `executed SQL expression` on success
- `nil`, `error reason`, 'failed SQL expression` on error

- `statement` object on success
- `false`, `error description`, on error

*Statement object methods*:

- `persist(true)` — do not deallocate current statement after execution
- `deallocate()` — deallocate current statement
- `execute(arg1, arg2...)` — execute current statement with given arguments
- `(...)` — alias to `execute`

*Example*:
```
### `link:query(statement, ...)` ("SELECT ? AS a, 'xx' AS b", 42)
### `link:prepare("SELECT $1 AS a, $2 AS b"):execute(1, 'string_value') -- or link:prepare("SELECT $1 AS a, $2 AS b")(1, 'string_value')`
---
- - - a: 42
b: xx
b: "string_value"
...
```

@@ -81,16 +104,15 @@ By default, for simple array `Array` time will be used, and for associative —

Closes database link.

*Returns*: `true`

### Configuration
*Returns*: `nil`

*Writable variables*:

- `pg.types.null` -- convert postgresql `NULL` to this value (default: `\0`).
- `pg.error` -- function called on query or connection error (default: `error`)
- `pg.debug` -- function called on query execution to debug SQL queries (default: .
### Writable variables

- `pg.types.null` -- convert postgresql `NULL` to this value (default: `nil`).
- `link.error` -- function called on SQL query failure (default: ``)
- `link.debug` -- function called on each SQL query (default: ``) .

## Comments


+ 493
- 0
cmake/FindOpenSSL.cmake View File

@@ -0,0 +1,493 @@
# Distributed under the OSI-approved BSD 3-Clause License. See accompanying
# file Copyright.txt or https://cmake.org/licensing for details.

#[=======================================================================[.rst:
FindOpenSSL
-----------

Find the OpenSSL encryption library.

Optional COMPONENTS
^^^^^^^^^^^^^^^^^^^

This module supports two optional COMPONENTS: ``Crypto`` and ``SSL``. Both
components have associated imported targets, as described below.

Imported Targets
^^^^^^^^^^^^^^^^

This module defines the following :prop_tgt:`IMPORTED` targets:

``OpenSSL::SSL``
The OpenSSL ``ssl`` library, if found.
``OpenSSL::Crypto``
The OpenSSL ``crypto`` library, if found.

Result Variables
^^^^^^^^^^^^^^^^

This module will set the following variables in your project:

``OPENSSL_FOUND``
System has the OpenSSL library. If no components are requested it only
requires the crypto library.
``OPENSSL_INCLUDE_DIR``
The OpenSSL include directory.
``OPENSSL_CRYPTO_LIBRARY``
The OpenSSL crypto library.
``OPENSSL_SSL_LIBRARY``
The OpenSSL SSL library.
``OPENSSL_LIBRARIES``
All OpenSSL libraries.
``OPENSSL_VERSION``
This is set to ``$major.$minor.$revision$patch`` (e.g. ``0.9.8s``).

Hints
^^^^^

Set ``OPENSSL_ROOT_DIR`` to the root directory of an OpenSSL installation.
Set ``OPENSSL_USE_STATIC_LIBS`` to ``TRUE`` to look for static libraries.
Set ``OPENSSL_MSVC_STATIC_RT`` set ``TRUE`` to choose the MT version of the lib.
#]=======================================================================]

if (UNIX)
find_package(PkgConfig QUIET)
pkg_check_modules(_OPENSSL QUIET openssl)
endif ()

# Support preference of static libs by adjusting CMAKE_FIND_LIBRARY_SUFFIXES
if(OPENSSL_USE_STATIC_LIBS)
set(_openssl_ORIG_CMAKE_FIND_LIBRARY_SUFFIXES ${CMAKE_FIND_LIBRARY_SUFFIXES})
if(WIN32)
set(CMAKE_FIND_LIBRARY_SUFFIXES .lib .a ${CMAKE_FIND_LIBRARY_SUFFIXES})
else()
set(CMAKE_FIND_LIBRARY_SUFFIXES .a )
endif()
endif()

if (WIN32)
# http://www.slproweb.com/products/Win32OpenSSL.html
set(_OPENSSL_ROOT_HINTS
${OPENSSL_ROOT_DIR}
"[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\OpenSSL (32-bit)_is1;Inno Setup: App Path]"
"[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\OpenSSL (64-bit)_is1;Inno Setup: App Path]"
ENV OPENSSL_ROOT_DIR
)
file(TO_CMAKE_PATH "$ENV{PROGRAMFILES}" _programfiles)
set(_OPENSSL_ROOT_PATHS
"${_programfiles}/OpenSSL"
"${_programfiles}/OpenSSL-Win32"
"${_programfiles}/OpenSSL-Win64"
"C:/OpenSSL/"
"C:/OpenSSL-Win32/"
"C:/OpenSSL-Win64/"
)
unset(_programfiles)
else ()
set(_OPENSSL_ROOT_HINTS
${OPENSSL_ROOT_DIR}
ENV OPENSSL_ROOT_DIR
)
endif ()

set(_OPENSSL_ROOT_HINTS_AND_PATHS
HINTS ${_OPENSSL_ROOT_HINTS}
PATHS ${_OPENSSL_ROOT_PATHS}
)

find_path(OPENSSL_INCLUDE_DIR
NAMES
openssl/ssl.h
${_OPENSSL_ROOT_HINTS_AND_PATHS}
HINTS
${_OPENSSL_INCLUDEDIR}
PATH_SUFFIXES
include
)

if(WIN32 AND NOT CYGWIN)
if(MSVC)
# /MD and /MDd are the standard values - if someone wants to use
# others, the libnames have to change here too
# use also ssl and ssleay32 in debug as fallback for openssl < 0.9.8b
# enable OPENSSL_MSVC_STATIC_RT to get the libs build /MT (Multithreaded no-DLL)
# In Visual C++ naming convention each of these four kinds of Windows libraries has it's standard suffix:
# * MD for dynamic-release
# * MDd for dynamic-debug
# * MT for static-release
# * MTd for static-debug

# Implementation details:
# We are using the libraries located in the VC subdir instead of the parent directory even though :
# libeay32MD.lib is identical to ../libeay32.lib, and
# ssleay32MD.lib is identical to ../ssleay32.lib
# enable OPENSSL_USE_STATIC_LIBS to use the static libs located in lib/VC/static

if (OPENSSL_MSVC_STATIC_RT)
set(_OPENSSL_MSVC_RT_MODE "MT")
else ()
set(_OPENSSL_MSVC_RT_MODE "MD")
endif ()

# Since OpenSSL 1.1, lib names are like libcrypto32MTd.lib and libssl32MTd.lib
if( "${CMAKE_SIZEOF_VOID_P}" STREQUAL "8" )
set(_OPENSSL_MSVC_ARCH_SUFFIX "64")
else()
set(_OPENSSL_MSVC_ARCH_SUFFIX "32")
endif()

if(OPENSSL_USE_STATIC_LIBS)
set(_OPENSSL_PATH_SUFFIXES
"lib/VC/static"
"VC/static"
"lib"
)
else()
set(_OPENSSL_PATH_SUFFIXES
"lib/VC"
"VC"
"lib"
)
endif ()

find_library(LIB_EAY_DEBUG
NAMES
libcrypto${_OPENSSL_MSVC_ARCH_SUFFIX}${_OPENSSL_MSVC_RT_MODE}d
libcrypto${_OPENSSL_MSVC_RT_MODE}d
libcryptod
libeay32${_OPENSSL_MSVC_RT_MODE}d
libeay32d
cryptod
NAMES_PER_DIR
${_OPENSSL_ROOT_HINTS_AND_PATHS}
PATH_SUFFIXES
${_OPENSSL_PATH_SUFFIXES}
)

find_library(LIB_EAY_RELEASE
NAMES
libcrypto${_OPENSSL_MSVC_ARCH_SUFFIX}${_OPENSSL_MSVC_RT_MODE}
libcrypto${_OPENSSL_MSVC_RT_MODE}
libcrypto
libeay32${_OPENSSL_MSVC_RT_MODE}
libeay32
crypto
NAMES_PER_DIR
${_OPENSSL_ROOT_HINTS_AND_PATHS}
PATH_SUFFIXES
${_OPENSSL_PATH_SUFFIXES}
)

find_library(SSL_EAY_DEBUG
NAMES
libssl${_OPENSSL_MSVC_ARCH_SUFFIX}${_OPENSSL_MSVC_RT_MODE}d
libssl${_OPENSSL_MSVC_RT_MODE}d
libssld
ssleay32${_OPENSSL_MSVC_RT_MODE}d
ssleay32d
ssld
NAMES_PER_DIR
${_OPENSSL_ROOT_HINTS_AND_PATHS}
PATH_SUFFIXES
${_OPENSSL_PATH_SUFFIXES}
)

find_library(SSL_EAY_RELEASE
NAMES
libssl${_OPENSSL_MSVC_ARCH_SUFFIX}${_OPENSSL_MSVC_RT_MODE}
libssl${_OPENSSL_MSVC_RT_MODE}
libssl
ssleay32${_OPENSSL_MSVC_RT_MODE}
ssleay32
ssl
NAMES_PER_DIR
${_OPENSSL_ROOT_HINTS_AND_PATHS}
PATH_SUFFIXES
${_OPENSSL_PATH_SUFFIXES}
)

set(LIB_EAY_LIBRARY_DEBUG "${LIB_EAY_DEBUG}")
set(LIB_EAY_LIBRARY_RELEASE "${LIB_EAY_RELEASE}")
set(SSL_EAY_LIBRARY_DEBUG "${SSL_EAY_DEBUG}")
set(SSL_EAY_LIBRARY_RELEASE "${SSL_EAY_RELEASE}")

include(${CMAKE_CURRENT_LIST_DIR}/SelectLibraryConfigurations.cmake)
select_library_configurations(LIB_EAY)
select_library_configurations(SSL_EAY)

mark_as_advanced(LIB_EAY_LIBRARY_DEBUG LIB_EAY_LIBRARY_RELEASE
SSL_EAY_LIBRARY_DEBUG SSL_EAY_LIBRARY_RELEASE)
set(OPENSSL_SSL_LIBRARY ${SSL_EAY_LIBRARY} )
set(OPENSSL_CRYPTO_LIBRARY ${LIB_EAY_LIBRARY} )
elseif(MINGW)
# same player, for MinGW
set(LIB_EAY_NAMES crypto libeay32)
set(SSL_EAY_NAMES ssl ssleay32)
find_library(LIB_EAY
NAMES
${LIB_EAY_NAMES}
NAMES_PER_DIR
${_OPENSSL_ROOT_HINTS_AND_PATHS}
PATH_SUFFIXES
"lib/MinGW"
"lib"
)

find_library(SSL_EAY
NAMES
${SSL_EAY_NAMES}
NAMES_PER_DIR
${_OPENSSL_ROOT_HINTS_AND_PATHS}
PATH_SUFFIXES
"lib/MinGW"
"lib"
)

mark_as_advanced(SSL_EAY LIB_EAY)
set(OPENSSL_SSL_LIBRARY ${SSL_EAY} )
set(OPENSSL_CRYPTO_LIBRARY ${LIB_EAY} )
unset(LIB_EAY_NAMES)
unset(SSL_EAY_NAMES)
else()
# Not sure what to pick for -say- intel, let's use the toplevel ones and hope someone report issues:
find_library(LIB_EAY
NAMES
libcrypto
libeay32
NAMES_PER_DIR
${_OPENSSL_ROOT_HINTS_AND_PATHS}
HINTS
${_OPENSSL_LIBDIR}
PATH_SUFFIXES
lib
)

find_library(SSL_EAY
NAMES
libssl
ssleay32
NAMES_PER_DIR
${_OPENSSL_ROOT_HINTS_AND_PATHS}
HINTS
${_OPENSSL_LIBDIR}
PATH_SUFFIXES
lib
)

mark_as_advanced(SSL_EAY LIB_EAY)
set(OPENSSL_SSL_LIBRARY ${SSL_EAY} )
set(OPENSSL_CRYPTO_LIBRARY ${LIB_EAY} )
endif()
else()

find_library(OPENSSL_SSL_LIBRARY
NAMES
ssl
ssleay32
ssleay32MD
NAMES_PER_DIR
${_OPENSSL_ROOT_HINTS_AND_PATHS}
HINTS
${_OPENSSL_LIBDIR}
PATH_SUFFIXES
lib
)

find_library(OPENSSL_CRYPTO_LIBRARY
NAMES
crypto
NAMES_PER_DIR
${_OPENSSL_ROOT_HINTS_AND_PATHS}
HINTS
${_OPENSSL_LIBDIR}
PATH_SUFFIXES
lib
)

mark_as_advanced(OPENSSL_CRYPTO_LIBRARY OPENSSL_SSL_LIBRARY)

# compat defines
set(OPENSSL_SSL_LIBRARIES ${OPENSSL_SSL_LIBRARY})
set(OPENSSL_CRYPTO_LIBRARIES ${OPENSSL_CRYPTO_LIBRARY})

endif()

function(from_hex HEX DEC)
string(TOUPPER "${HEX}" HEX)
set(_res 0)
string(LENGTH "${HEX}" _strlen)

while (_strlen GREATER 0)
math(EXPR _res "${_res} * 16")
string(SUBSTRING "${HEX}" 0 1 NIBBLE)
string(SUBSTRING "${HEX}" 1 -1 HEX)
if (NIBBLE STREQUAL "A")
math(EXPR _res "${_res} + 10")
elseif (NIBBLE STREQUAL "B")
math(EXPR _res "${_res} + 11")
elseif (NIBBLE STREQUAL "C")
math(EXPR _res "${_res} + 12")
elseif (NIBBLE STREQUAL "D")
math(EXPR _res "${_res} + 13")
elseif (NIBBLE STREQUAL "E")
math(EXPR _res "${_res} + 14")
elseif (NIBBLE STREQUAL "F")
math(EXPR _res "${_res} + 15")
else()
math(EXPR _res "${_res} + ${NIBBLE}")
endif()

string(LENGTH "${HEX}" _strlen)
endwhile()

set(${DEC} ${_res} PARENT_SCOPE)
endfunction()

if(OPENSSL_INCLUDE_DIR AND EXISTS "${OPENSSL_INCLUDE_DIR}/openssl/opensslv.h")
file(STRINGS "${OPENSSL_INCLUDE_DIR}/openssl/opensslv.h" openssl_version_str
REGEX "^#[\t ]*define[\t ]+OPENSSL_VERSION_NUMBER[\t ]+0x([0-9a-fA-F])+.*")

if(openssl_version_str)
# The version number is encoded as 0xMNNFFPPS: major minor fix patch status
# The status gives if this is a developer or prerelease and is ignored here.
# Major, minor, and fix directly translate into the version numbers shown in
# the string. The patch field translates to the single character suffix that
# indicates the bug fix state, which 00 -> nothing, 01 -> a, 02 -> b and so
# on.

string(REGEX REPLACE "^.*OPENSSL_VERSION_NUMBER[\t ]+0x([0-9a-fA-F])([0-9a-fA-F][0-9a-fA-F])([0-9a-fA-F][0-9a-fA-F])([0-9a-fA-F][0-9a-fA-F])([0-9a-fA-F]).*$"
"\\1;\\2;\\3;\\4;\\5" OPENSSL_VERSION_LIST "${openssl_version_str}")
list(GET OPENSSL_VERSION_LIST 0 OPENSSL_VERSION_MAJOR)
list(GET OPENSSL_VERSION_LIST 1 OPENSSL_VERSION_MINOR)
from_hex("${OPENSSL_VERSION_MINOR}" OPENSSL_VERSION_MINOR)
list(GET OPENSSL_VERSION_LIST 2 OPENSSL_VERSION_FIX)
from_hex("${OPENSSL_VERSION_FIX}" OPENSSL_VERSION_FIX)
list(GET OPENSSL_VERSION_LIST 3 OPENSSL_VERSION_PATCH)

if (NOT OPENSSL_VERSION_PATCH STREQUAL "00")
from_hex("${OPENSSL_VERSION_PATCH}" _tmp)
# 96 is the ASCII code of 'a' minus 1
math(EXPR OPENSSL_VERSION_PATCH_ASCII "${_tmp} + 96")
unset(_tmp)
# Once anyone knows how OpenSSL would call the patch versions beyond 'z'
# this should be updated to handle that, too. This has not happened yet
# so it is simply ignored here for now.
string(ASCII "${OPENSSL_VERSION_PATCH_ASCII}" OPENSSL_VERSION_PATCH_STRING)
endif ()

set(OPENSSL_VERSION "${OPENSSL_VERSION_MAJOR}.${OPENSSL_VERSION_MINOR}.${OPENSSL_VERSION_FIX}${OPENSSL_VERSION_PATCH_STRING}")
endif ()
endif ()

set(OPENSSL_LIBRARIES ${OPENSSL_SSL_LIBRARY} ${OPENSSL_CRYPTO_LIBRARY} )

foreach(_comp IN LISTS OpenSSL_FIND_COMPONENTS)
if(_comp STREQUAL "Crypto")
if(EXISTS "${OPENSSL_INCLUDE_DIR}" AND
(EXISTS "${OPENSSL_CRYPTO_LIBRARY}" OR
EXISTS "${LIB_EAY_LIBRARY_DEBUG}" OR
EXISTS "${LIB_EAY_LIBRARY_RELEASE}")
)
set(OpenSSL_${_comp}_FOUND TRUE)
else()
set(OpenSSL_${_comp}_FOUND FALSE)
endif()
elseif(_comp STREQUAL "SSL")
if(EXISTS "${OPENSSL_INCLUDE_DIR}" AND
(EXISTS "${OPENSSL_SSL_LIBRARY}" OR
EXISTS "${SSL_EAY_LIBRARY_DEBUG}" OR
EXISTS "${SSL_EAY_LIBRARY_RELEASE}")
)
set(OpenSSL_${_comp}_FOUND TRUE)
else()
set(OpenSSL_${_comp}_FOUND FALSE)
endif()
else()
message(WARNING "${_comp} is not a valid OpenSSL component")
set(OpenSSL_${_comp}_FOUND FALSE)
endif()
endforeach()
unset(_comp)

include(${CMAKE_CURRENT_LIST_DIR}/FindPackageHandleStandardArgs.cmake)
find_package_handle_standard_args(OpenSSL
REQUIRED_VARS
OPENSSL_CRYPTO_LIBRARY
OPENSSL_INCLUDE_DIR
VERSION_VAR
OPENSSL_VERSION
HANDLE_COMPONENTS
FAIL_MESSAGE
"Could NOT find OpenSSL, try to set the path to OpenSSL root folder in the system variable OPENSSL_ROOT_DIR"
)

mark_as_advanced(OPENSSL_INCLUDE_DIR OPENSSL_LIBRARIES)

if(OPENSSL_FOUND)
if(NOT TARGET OpenSSL::Crypto AND
(EXISTS "${OPENSSL_CRYPTO_LIBRARY}" OR
EXISTS "${LIB_EAY_LIBRARY_DEBUG}" OR
EXISTS "${LIB_EAY_LIBRARY_RELEASE}")
)
add_library(OpenSSL::Crypto UNKNOWN IMPORTED)
set_target_properties(OpenSSL::Crypto PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "${OPENSSL_INCLUDE_DIR}")
if(EXISTS "${OPENSSL_CRYPTO_LIBRARY}")
set_target_properties(OpenSSL::Crypto PROPERTIES
IMPORTED_LINK_INTERFACE_LANGUAGES "C"
IMPORTED_LOCATION "${OPENSSL_CRYPTO_LIBRARY}")
endif()
if(EXISTS "${LIB_EAY_LIBRARY_RELEASE}")
set_property(TARGET OpenSSL::Crypto APPEND PROPERTY
IMPORTED_CONFIGURATIONS RELEASE)
set_target_properties(OpenSSL::Crypto PROPERTIES
IMPORTED_LINK_INTERFACE_LANGUAGES_RELEASE "C"
IMPORTED_LOCATION_RELEASE "${LIB_EAY_LIBRARY_RELEASE}")
endif()
if(EXISTS "${LIB_EAY_LIBRARY_DEBUG}")
set_property(TARGET OpenSSL::Crypto APPEND PROPERTY
IMPORTED_CONFIGURATIONS DEBUG)
set_target_properties(OpenSSL::Crypto PROPERTIES
IMPORTED_LINK_INTERFACE_LANGUAGES_DEBUG "C"
IMPORTED_LOCATION_DEBUG "${LIB_EAY_LIBRARY_DEBUG}")
endif()
endif()

if(NOT TARGET OpenSSL::SSL AND
(EXISTS "${OPENSSL_SSL_LIBRARY}" OR
EXISTS "${SSL_EAY_LIBRARY_DEBUG}" OR
EXISTS "${SSL_EAY_LIBRARY_RELEASE}")
)
add_library(OpenSSL::SSL UNKNOWN IMPORTED)
set_target_properties(OpenSSL::SSL PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "${OPENSSL_INCLUDE_DIR}")
if(EXISTS "${OPENSSL_SSL_LIBRARY}")
set_target_properties(OpenSSL::SSL PROPERTIES
IMPORTED_LINK_INTERFACE_LANGUAGES "C"
IMPORTED_LOCATION "${OPENSSL_SSL_LIBRARY}")
endif()
if(EXISTS "${SSL_EAY_LIBRARY_RELEASE}")
set_property(TARGET OpenSSL::SSL APPEND PROPERTY
IMPORTED_CONFIGURATIONS RELEASE)
set_target_properties(OpenSSL::SSL PROPERTIES
IMPORTED_LINK_INTERFACE_LANGUAGES_RELEASE "C"
IMPORTED_LOCATION_RELEASE "${SSL_EAY_LIBRARY_RELEASE}")
endif()
if(EXISTS "${SSL_EAY_LIBRARY_DEBUG}")
set_property(TARGET OpenSSL::SSL APPEND PROPERTY
IMPORTED_CONFIGURATIONS DEBUG)
set_target_properties(OpenSSL::SSL PROPERTIES
IMPORTED_LINK_INTERFACE_LANGUAGES_DEBUG "C"
IMPORTED_LOCATION_DEBUG "${SSL_EAY_LIBRARY_DEBUG}")
endif()
if(TARGET OpenSSL::Crypto)
set_target_properties(OpenSSL::SSL PROPERTIES
INTERFACE_LINK_LIBRARIES OpenSSL::Crypto)
endif()
endif()
endif()

# Restore the original find library ordering
if(OPENSSL_USE_STATIC_LIBS)
set(CMAKE_FIND_LIBRARY_SUFFIXES ${_openssl_ORIG_CMAKE_FIND_LIBRARY_SUFFIXES})
endif()

+ 116
- 0
cmake/FindUUID.cmake View File

@@ -0,0 +1,116 @@
# - Try to find UUID
# Once done this will define
#
# UUID_FOUND - system has UUID
# UUID_INCLUDE_DIRS - the UUID include directory
# UUID_LIBRARIES - Link these to use UUID
# UUID_DEFINITIONS - Compiler switches required for using UUID
#
# Copyright (c) 2006 Andreas Schneider <mail@cynapses.org>
#
# Redistribution and use is allowed according to the terms of the New
# BSD license.
# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
#


if (UUID_LIBRARIES AND UUID_INCLUDE_DIRS)
# in cache already
set(UUID_FOUND TRUE)
else (UUID_LIBRARIES AND UUID_INCLUDE_DIRS)
find_path(UUID_INCLUDE_DIR
NAMES
uuid.h
PATH_SUFFIXES
uuid
HINTS
${UUID_DIR}/include
$ENV{UUID_DIR}/include
$ENV{UUID_DIR}
${DELTA3D_EXT_DIR}/inc
$ENV{DELTA_ROOT}/ext/inc
$ENV{DELTA_ROOT}
PATHS
~/Library/Frameworks
/Library/Frameworks
/usr/local/include
/usr/include
/usr/include/gdal
/sw/include # Fink
/opt/csw/include # Blastwave
/opt/include
[HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\Session\ Manager\\Environment;OSG_ROOT]/include
/usr/freeware/include
)

find_library(UUID_LIBRARY
NAMES
uuid ossp-uuid
HINTS
${UUID_DIR}/lib
$ENV{UUID_DIR}/lib
$ENV{UUID_DIR}
${DELTA3D_EXT_DIR}/lib
$ENV{DELTA_ROOT}/ext/lib
$ENV{DELTA_ROOT}
$ENV{OSG_ROOT}/lib
PATHS
~/Library/Frameworks
/Library/Frameworks
/usr/local/lib
/usr/lib
/sw/lib
/opt/csw/lib
/opt/lib
/usr/freeware/lib64
)

find_library(UUID_LIBRARY_DEBUG
NAMES
uuidd
HINTS
${UUID_DIR}/lib
$ENV{UUID_DIR}/lib
$ENV{UUID_DIR}
${DELTA3D_EXT_DIR}/lib
$ENV{DELTA_ROOT}/ext/lib
$ENV{DELTA_ROOT}
$ENV{OSG_ROOT}/lib
PATHS
~/Library/Frameworks
/Library/Frameworks
/usr/local/lib
/usr/lib
/sw/lib
/opt/csw/lib
/opt/lib
/usr/freeware/lib64
)
if (NOT UUID_LIBRARY AND BSD)
set(UUID_LIBRARY "")
endif(NOT UUID_LIBRARY AND BSD)

set(UUID_INCLUDE_DIRS ${UUID_INCLUDE_DIR})
set(UUID_LIBRARIES ${UUID_LIBRARY})

if (UUID_INCLUDE_DIRS)
if (BSD OR UUID_LIBRARIES)
set(UUID_FOUND TRUE)
endif (BSD OR UUID_LIBRARIES)
endif (UUID_INCLUDE_DIRS)

if (UUID_FOUND)
if (NOT UUID_FIND_QUIETLY)
message(STATUS "Found UUID: ${UUID_LIBRARIES}")
endif (NOT UUID_FIND_QUIETLY)
else (UUID_FOUND)
if (UUID_FIND_REQUIRED)
message(FATAL_ERROR "Could not find UUID")
endif (UUID_FIND_REQUIRED)
endif (UUID_FOUND)

# show the UUID_INCLUDE_DIRS and UUID_LIBRARIES variables only in the advanced view
mark_as_advanced(UUID_INCLUDE_DIRS UUID_LIBRARIES)

endif (UUID_LIBRARIES AND UUID_INCLUDE_DIRS)

+ 6
- 2
pg-tarantool-hg-999.rockspec View File

@@ -16,8 +16,12 @@ external_dependencies = {
header = 'tarantool/module.h';
},
OPENSSL = {
header = "openssl/ssl.h",
library = "ssl"
header = "openssl/ssl.h",
library = "ssl"
},
LIBUUID = {
header = "uuid/uuid.h",
library = "uuid"
}
}
build = {

+ 68
- 127
pg_tarantool/driver.c View File

@@ -26,11 +26,14 @@
* THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#define lua_swap(L) lua_insert(L, -2)

#include <stdint.h>
#include <stddef.h>
#include <assert.h>
#include <stdlib.h>
#include <errno.h>
#include <uuid/uuid.h>

#include <lua.h>
#include <lauxlib.h>
@@ -49,21 +52,6 @@
#define TIMEOUT_INFINITY 365 * 86400 * 100.0
static const char pg_driver_label[] = "__tnt_pg_driver";

static int
save_pushstring_wrapped(struct lua_State *L)
{
char *str = (char *)lua_topointer(L, 1);
lua_pushstring(L, str);
return 1;
}

static int
safe_pushstring(struct lua_State *L, char *str)
{
lua_pushcfunction(L, save_pushstring_wrapped);
lua_pushlightuserdata(L, str);
return lua_pcall(L, 1, 1, 0);
}

static inline PGconn *
lua_check_pgconn(struct lua_State *L, int index)
@@ -75,16 +63,6 @@ lua_check_pgconn(struct lua_State *L, int index)
return *conn_p;
}

/**
* Push native lua error with code -3
*/
static int
lua_push_error(struct lua_State *L)
{
lua_pushnumber(L, -3);
lua_insert(L, -2);
return 2;
}

/**
* Parse pg values to lua
@@ -150,32 +128,6 @@ safe_pg_parsetuples(struct lua_State *L)
return 1;
}

#if 0
Now we return only recordset without status
/**
* Push query execution status to lua stack
*/
static int
safe_pg_parsestatus(struct lua_State *L)
{
PGresult *r = (PGresult *)lua_topointer(L, 1);
lua_newtable(L);
lua_pushstring(L, "tuples");
if (*PQcmdTuples(r) == 0) {
lua_pushnumber(L, 0);
} else {
lua_pushstring(L, PQcmdTuples(r));
double v = lua_tonumber(L, -1);
lua_pop(L, 1);
lua_pushnumber(L, v);
}
lua_settable(L, -3);
lua_pushstring(L, "message");
lua_pushstring(L, PQcmdStatus(r));
lua_settable(L, -3);
return 1;
}
#endif

/**
* Wait until postgres returns something
@@ -201,66 +153,42 @@ pg_wait_for_result(PGconn *conn)
* Appends result fom postgres to lua table
*/
static int
pg_resultget(struct lua_State *L, PGconn *conn, int *res_no, int status_ok)
pg_resultget(struct lua_State *L, PGconn *conn, int *state, char **error_msg)
{
int wait_res = pg_wait_for_result(conn);
if (wait_res != 1)
{
lua_pushinteger(L, wait_res);
if (wait_res == -2)
safe_pushstring(L, "Fiber was cancelled");
else
lua_pushstring(L, PQerrorMessage(conn));
return 0;
}
if (wait_res != 1) return 0;

PGresult *pg_res = PQgetResult(conn);
if (!pg_res) {
return 0;
}
if (status_ok != 1) {
// Fail mode, just skip all other results
PQclear(pg_res);
return status_ok;
}
if (!pg_res) return 0;

//
int res = -1;
int fail = 0;
int status = PQresultStatus(pg_res);
switch (status) {
case PGRES_COMMAND_OK:
lua_pushinteger(L, *res_no);
lua_pushinteger(L, atoi(PQcmdTuples(pg_res)));
lua_settable(L, -3);
*state = atoi(PQcmdTuples(pg_res));
lua_pushinteger(L, 1);
res = 1;
break;
case PGRES_TUPLES_OK:
lua_pushinteger(L, *res_no);
*state = 1;
lua_pushcfunction(L, safe_pg_parsetuples);
lua_pushlightuserdata(L, pg_res);
fail = lua_pcall(L, 1, 1, 0);
if (!fail)
lua_settable(L, -3);
lua_pcall(L, 1, 1, 0);
res = 1;
break;
case PGRES_FATAL_ERROR:
// case PGRES_NONFATAL_ERROR:
case PGRES_EMPTY_QUERY:
case PGRES_NONFATAL_ERROR:
lua_pushinteger(L,
(PQstatus(conn) == CONNECTION_BAD) ? -1: 1);
fail = safe_pushstring(L, PQerrorMessage(conn));
case PGRES_BAD_RESPONSE:
case PGRES_FATAL_ERROR:
*state = ((PQstatus(conn) != CONNECTION_OK) ? PQstatus(conn)-128 : -1);
*error_msg = PQerrorMessage(conn);
lua_pushnil(L);
res = -1;
break;
default:
lua_pushinteger(L, -1);
fail = safe_pushstring(L,
"Unwanted execution result status");
}

PQclear(pg_res);
(*res_no)++;
if (fail) {
lua_push_error(L);
res = -1;
}
return res;
}

@@ -270,34 +198,36 @@ pg_resultget(struct lua_State *L, PGconn *conn, int *res_no, int status_ok)
static int
lua_pg_execute(struct lua_State *L)
{
PGconn *conn = lua_check_pgconn(L, 1);
if (!lua_isstring(L, 2)) {
safe_pushstring(L, "Second param should be a sql command");
return lua_push_error(L);
}
const char *sql = lua_tostring(L, 2);
int paramCount = lua_gettop(L) - 2;

const char **paramValues = NULL;
int *paramLengths = NULL;
Oid *paramTypes = NULL;

int res = 0;
res = PQsendQuery(conn, sql);
if (!lua_isstring(L, 2)) return 0; // we need two args

if (res == -1) {
lua_pushinteger(L, PQstatus(conn) == CONNECTION_BAD ? -1: 0);
lua_pushstring(L, PQerrorMessage(conn));
return 2;
PGconn *conn = lua_check_pgconn(L, 1); // postgresql connection
const char *sql = lua_tostring(L, 2); // sql query
/* query send error
returns: nil, conn_state, error_msg */
if (!PQsendQuery(conn, sql)) {
lua_pushnil(L); // results {}
lua_pushinteger(L, PQstatus(conn) - 128); // state
lua_pushstring(L, PQerrorMessage(conn)); // err_msg
return 3;
}
lua_pushinteger(L, 0);
lua_newtable(L);

int res_no = 1;
int status_ok = 1;
while ((status_ok = pg_resultget(L, conn, &res_no, status_ok)));

return 2;
// returns: {{results}}, exec_state [0 — no result, 1 — got result, -1 — query error, >-127 ­— link error], error_msg
lua_newtable(L); // result = {}
int result;
int state = 0;
int res_no = 1;
char* error_msg = "";
while (result = pg_resultget(L, conn, &state, &error_msg)) {
lua_pushinteger(L, (res_no)++); // result[res_no] = ...
lua_swap(L);
lua_settable(L, -3);
}
lua_pushinteger(L, state); // exec_ state
if(strlen(error_msg) > 0) lua_pushstring(L, error_msg); else lua_pushnil(L); // error_msg or nil
return 3;
}

/**
@@ -378,11 +308,10 @@ lua_pg_quote(struct lua_State *L)

s = PQescapeLiteral(conn, s, len);

if (!s)
luaL_error(L, "Can't allocate memory");
int fail = safe_pushstring(L, (char *)s);
if (!s) luaL_error(L, "Can't allocate memory");
lua_pushstring(L, s);
free((void *)s);
return fail ? lua_push_error(L): 1;
return 1;
}

/**
@@ -401,15 +330,28 @@ lua_pg_quote_ident(struct lua_State *L)

s = PQescapeIdentifier(conn, s, len);

if (!s)
luaL_error(L, "Can't allocate memory");
int fail = safe_pushstring(L, (char *)s);
if (!s) luaL_error(L, "Can't allocate memory");
lua_pushstring(L, s);
free((void *)s);
return fail ? lua_push_error(L): 1;
return 1;
}

#endif

/*
* Generate UUID (for prepared statements)
*/
static int lua_uuid_generate(lua_State *L) /** new([s]) */
{
uuid_t c;
char s[2*sizeof(c)+4+1];
uuid_generate(c);
uuid_unparse(c,s);
s[8] = s[13] = s[18] = s[23] = 95;
lua_pushlstring(L,s,sizeof(s)-1);
return 1;
}

/**
*
Types conversion (dummy)
@@ -443,10 +385,8 @@ lua_pg_connect(struct lua_State *L)

conn = PQconnectStart(constr);
if (!conn) {
lua_pushnil(L);
lua_pushinteger(L, -1);
int fail = safe_pushstring(L,
"Can't allocate PG connection structure");
return fail ? lua_push_error(L): 2;
}

if (PQstatus(conn) == CONNECTION_BAD) {
@@ -502,6 +442,7 @@ luaopen_pg_tarantool_driver(lua_State *L)
{"decode", lua_noop},
{"close", lua_pg_close},
{"active", lua_pg_transaction_active},
{"uuid", lua_uuid_generate},
{"__tostring", lua_pg_tostring},
{"__gc", lua_pg_gc},
{NULL, NULL}

+ 92
- 34
pg_tarantool/init.lua View File

@@ -5,55 +5,108 @@ local types = require('pg_tarantool.types')
local driver = require('pg_tarantool.driver')
driver.decode = types.decode

_M = {}
_M.types = types
_M.error = error
_M.debug = function() end
local noop = function() end
-----------------------------------------------------------------------------------------------------------------
-- postgresql prepared statements --
--
local function prepare(this, sql, ...)
local conn, uuid, result, state, err

conn = this:get() -- get connection from pool --
uuid = "pg_tarantool_" .. conn:uuid() -- create uuid for prepared statement --
sql = string.format("prepare %s as (%s)", uuid, sql, ...)
result, state, err = conn:execute(sql) -- create prepared statement --
this.debug(sql, state, result, err) -- debug function --
if state < 0 then this:put(state > -127 and conn); return false, err, this.error(err) end -- failed to prepare statement -- return false --

---------------------------------------------------------
---------------------------------------------------------
-- our statement --
statement = {
conn = conn,
uuid = uuid,
is_persist = false,
persist = function(stmt, set)
stmt.is_persist = (set == true) and true or false
return stmt
end,
close = function(stmt, persist)
if persist == true or not stmt.conn then return stmt end
local _, state = stmt.conn:execute("deallocate "..stmt.uuid)
stmt.conn, stmt.execute = this:put(state > -127 and stmt.conn), nil
end,
execute = function(stmt, ...)
local result, state, err
stmt.args = types.parse({...}, function(s) return stmt.conn:quote(s) end)
stmt.query = string.format(#stmt.args>0 and "execute %s (%s)" or "execute %s", stmt.uuid, table.concat(stmt.args, ','))
result, state, err = stmt.conn:execute(stmt.query)
this.debug(stmt.query, state, result, err)
stmt:close(stmt.is_persist == true)
return (state > 0) and unpack(result) or false, err, this.error(err)
end,
}
--
return setmetatable(statement, {__call = statement.execute})
end

-----------------------------------------------------------------------------------------------------------------
-- get first available connection from connection pool and execute a query --
local function query(self, sql, ...)
local conn, state, response, dbg
-- get connectionfrom pool --
conn = self.queue:get() or driver.connect(self.conn_string)
if not conn then return false, _M.error('link broken'); elseif conn:active() then conn:execute('BEGIN; RESET ALL; COMMIT;'); end
-- build and execute query --
local n, arg = 1, {...}
sql = sql:gsub('%?', function() n=n+1; return types.encode(arg[n-1], function(...) return conn.quote(conn, ...) end) end); _M.debug(sql);
state, response = conn:execute(sql)
-- parse errors --
if state < 0 then self.queue:put(false); return false, _M.error('connection broken'); end -- connection no more usable --
if state ~= 0 then return false, _M.error(response:match("([^\n]+)")); end -- query error --
-- return result --
self.queue:put(conn)
return unpack(response)
--
local function query(this, sql, ...)
local conn, params, result, state, err
conn = this:get() -- get connection from pool --
params = types.parse({...}, function(s) return conn:quote(s) end) -- escape params --
sql = string.format(sql, unpack(params)) -- format query --
result, state, err = conn:execute(sql) -- execute query --
this.debug(sql, state, result, state) -- debug function --
this:put(state > -127 and conn) -- put connection to pool --

-- return result if succeed, else -- return false and error --
return (state > 0) and unpack(result) or false, err, this.error(err)
end

local function close(self) while self.queue:count() > 0 do local conn = self.queue:get(); conn:close(); end; end
-----------------------------------------------------------------------------------------------------------------
-- internal functions for: --
-- - get connection from pool-
local function get(this)
local conn = this.queue:get() or driver.connect(this.conn_string) or error 'db link broken'
if conn:active() then conn:execute('begin; reset all; commit;') end
return conn
end
-- - return connection to pool --
local function put(this, conn)
this.queue:put(conn)
return nil
end
-- - close connection or whole pool --
local function close(this, conn)
if conn then conn:close() else while this.queue:count() > 0 do this:get():close() end end
return nil
end

-----------------------------------------------------------------------------------------------------------------
-- create connection pool. Accepts pg connection params (host, port, user,
-- password, dbname) separatelly or in one string, connections ( pool size or 0 if single ) and raise flag.
function _M.connect(cfg)
local params, conn_string = {host=true,port=true,user=true,password=true,dbname=true,sslmode=true}, ''
for k,v in pairs(cfg) do conn_string = (params[k]) and conn_string .. string.format("%s='%s' ", k, v) or conn_string end;

local size = cfg.connections or 1
local queue = fiber.channel(size)
for i = 1, (size) do
local conn, status = driver.connect(conn_string)
local function connect(cfg)
--
cfg.conn_string = ''
for _,param in pairs{'host','port','user','password','dbname','sslmode'} do
cfg.conn_string = cfg[param] and cfg.conn_string .. string.format("%s='%s' ", param, cfg[param]) or conn_string
end

--
local queue = fiber.channel(cfg.connections or 1)
for i = 1, (cfg.connections or 1) do
local conn, status = driver.connect(cfg.conn_string)
if status >= 0 then queue:put(conn) end
end
if queue:count() == 0 then return false, _M.error('failed to establish db link') end
return setmetatable({ queue = queue, conn_string = conn_string, query = query, close = close }, {__call=query})
--
if queue:count() == 0 then return error 'failed to establish db link' end
return setmetatable({ conn_string = cfg.conn_string, queue = queue, get = get, put = put, prepare = prepare, query = query, close = close, error = noop, debug = noop }, {__call = query})
end

return _M

--
return { connect = connect, types = types }

+ 6
- 2
pg_tarantool/types/init.lua View File

@@ -40,11 +40,15 @@ _M.decode = function(var, id)
return (enum[id] or enum.default)(var)
end
_M.encode = function(var, escape)
local i = require 'inspect'
local escape = type(escape) == 'function' and escape or function(str) return "'"..str.."'" end
local escape = type(escape) == 'function' and escape or function(s) return "'"..s.."'" end
local id = ( type(var) == 'table' and #var == 0 and next(var) ) and 'json' or type(var)
return (enum[id] or enum.default)(var, escape)
end
_M.parse = function(arr, escape)
local r = {}
for i=1,#arr do r[i] = _M.encode(arr[i], escape) end
return r
end

-- return pg-tarantool types handler --
return _M

Loading…
Cancel
Save