annelin 8 months ago
commit
c7fe2e7702
20 changed files with 1512 additions and 0 deletions
  1. 33
    0
      CMakeLists.txt
  2. 8
    0
      Jenkinsfile
  3. 31
    0
      LICENSE
  4. 159
    0
      README.md
  5. 154
    0
      cmake/FindPostgreSQL.cmake
  6. 45
    0
      cmake/FindTarantool.cmake
  7. 5
    0
      debian/.gitignore
  8. 5
    0
      debian/changelog
  9. 1
    0
      debian/compat
  10. 19
    0
      debian/control
  11. 34
    0
      debian/copyright
  12. 1
    0
      debian/docs
  13. 8
    0
      debian/rules
  14. 1
    0
      debian/source/format
  15. 29
    0
      pg-scm-1.rockspec
  16. 6
    0
      pg/CMakeLists.txt
  17. 573
    0
      pg/driver.c
  18. 234
    0
      pg/init.lua
  19. 41
    0
      rpm/tarantool-pg.spec
  20. 125
    0
      test/pg.test.lua

+ 33
- 0
CMakeLists.txt View File

@@ -0,0 +1,33 @@
cmake_minimum_required(VERSION 2.8 FATAL_ERROR)

project(pg C)
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Debug)
endif()
set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake" ${CMAKE_MODULE_PATH})

# Find Tarantool
set(TARANTOOL_FIND_REQUIRED ON)
find_package(Tarantool)
include_directories(${TARANTOOL_INCLUDE_DIRS})

# Find Postgrsql
set(PostgreSQL_FIND_REQUIRED ON)
find_package(PostgreSQL)
include_directories(${PostgreSQL_INCLUDE_DIRS})
link_directories(${PostgreSQL_LIBRARY_DIRS})

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

if(${CMAKE_SYSTEM_NAME} STREQUAL "Darwin")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -undefined dynamic_lookup")
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -undefined dynamic_lookup")
endif()

# Build module
add_subdirectory(pg)

add_custom_target(check
COMMAND ${PROJECT_SOURCE_DIR}/test/pg.test.lua)

+ 8
- 0
Jenkinsfile View File

@@ -0,0 +1,8 @@
stage('Build'){
packpack = new org.tarantool.packpack()
node {
checkout scm
packpack.prepareSources()
}
packpack.packpackBuildMatrix('result')
}

+ 31
- 0
LICENSE View File

@@ -0,0 +1,31 @@
Copyright (C) 2010-2013 Tarantool AUTHORS:
please see AUTHORS file in tarantool/tarantool repository.

/*
* Redistribution and use in source and binary forms, with or
* without modification, are permitted provided that the following
* conditions are met:
*
* 1. Redistributions of source code must retain the above
* copyright notice, this list of conditions and the
* following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY AUTHORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
* AUTHORS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
* THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/

+ 159
- 0
README.md View File

@@ -0,0 +1,159 @@
# pg - PostgreSQL connector for [Tarantool][]

[![Build Status](https://travis-ci.org/tarantool/pg.png?branch=master)](https://travis-ci.org/tarantool/pg)

## Getting Started

### Prerequisites

* Tarantool 1.6.5+ with header files (tarantool && tarantool-dev packages)
* PostgreSQL 8.1+ header files (libpq-dev package)

### Installation

Clone repository and then build it using CMake:

``` bash
git clone https://github.com/tarantool/pg.git
cd pg && cmake . -DCMAKE_BUILD_TYPE=RelWithDebugInfo
make
make install
```

You can also use LuaRocks:

``` bash
luarocks install https://raw.githubusercontent.com/tarantool/pg/master/pg-scm-1.rockspec --local
```

See [tarantool/rocks][TarantoolRocks] for LuaRocks configuration details.

### Usage

``` lua
local pg = require('pg')
local conn = pg.connect({host = localhost, user = 'user', pass = 'pass', db = 'db'})
local tuples = conn:execute("SELECT ? AS a, 'xx' AS b", 42))
conn:begin()
conn:execute("INSERT INTO test VALUES(1, 2, 3)")
conn:commit()
```

## API Documentation

### `conn = pg:connect(opts = {})`

Connect to a database.

*Options*:

- `host` - a hostname to connect
- `port` - a port numner to connect
- `user` - username
- `pass` or `password` - a password
- `db` - a database name
- `conn_string` (mutual exclusive with host, port, user, pass, db) - PostgreSQL
[connection string][PQconnstring]

*Returns*:

- `connection ~= nil` on success
- `error(reason)` on error

### `conn:execute(statement, ...)`

Execute a statement with arguments in the current transaction.

*Returns*:
- `{ { { column1 = value, column2 = value }, ... }, { {column1 = value, ... }, ...}, ...}, true` on success
- `error(reason)` on error

*Example*:
```
tarantool> conn:execute("SELECT ? AS a, 'xx' AS b", 42)
---
- - - a: 42
b: xx
...
```

### `conn:begin()`

Begin a transaction.

*Returns*: `true`

### `conn:commit()`

Commit current transaction.

*Returns*: `true`

### `conn:rollback()`

Rollback current transaction.

*Returns*: `true`

### `conn:ping()`

Execute a dummy statement to check that connection is alive.

*Returns*:

- `true` on success
- `false` on failure

#### `pool = pg.pool_create(opts = {})`

Create a connection pool with count of size established connections.

*Options*:

- `host` - hostname to connect to
- `port` - port number to connect to
- `user` - username
- `password` - password
- `db` - database name
- `size` - count of connections in pool

*Returns*

- `pool ~=nil` on success
- `error(reason)` on error

### `conn = pool:get()`

Get a connection from pool. Reset connection before returning it. If connection
is broken then it will be reestablished. If there is no free connections then
calling fiber will sleep until another fiber returns some connection to pool.

*Returns*:

- `conn ~= nil`
### `pool:put(conn)`

Return a connection to connection pool.

*Options*

- `conn` - a connection

## Comments

All calls to connections api will be serialized, so it should to be safe to
use one connection from some count of fibers. But you should understand,
that you can have some unwanted behavior across db calls, for example if
another fiber 'injects' some sql between two your calls.

# See Also

* [Tests][]
* [Tarantool][]
* [Tarantool Rocks][TarantoolRocks]

[Tarantool]: http://github.com/tarantool/tarantool
[Tests]: https://github.com/tarantool/pg/tree/master/test
[PQconnstring]: http://www.postgresql.org/docs/9.4/static/libpq-connect.html#LIBPQ-CONNSTRING
[TarantoolRocks]: https://github.com/tarantool/rocks

+ 154
- 0
cmake/FindPostgreSQL.cmake View File

@@ -0,0 +1,154 @@
# - Find the PostgreSQL installation.
# In Windows, we make the assumption that, if the PostgreSQL files are installed, the default directory
# will be C:\Program Files\PostgreSQL.
#
# This module defines
# PostgreSQL_LIBRARIES - the PostgreSQL libraries needed for linking
# PostgreSQL_INCLUDE_DIRS - the directories of the PostgreSQL headers
# PostgreSQL_VERSION_STRING - the version of PostgreSQL found (since CMake 2.8.8)

#=============================================================================
# Copyright 2004-2009 Kitware, Inc.
#
# Distributed under the OSI-approved BSD License (the "License");
# see accompanying file Copyright.txt for details.
#
# This software is distributed WITHOUT ANY WARRANTY; without even the
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the License for more information.
#=============================================================================
# (To distribute this file outside of CMake, substitute the full
# License text for the above reference.)

# ----------------------------------------------------------------------------
# History:
# This module is derived from the module originally found in the VTK source tree.
#
# ----------------------------------------------------------------------------
# Note:
# PostgreSQL_ADDITIONAL_VERSIONS is a variable that can be used to set the
# version mumber of the implementation of PostgreSQL.
# In Windows the default installation of PostgreSQL uses that as part of the path.
# E.g C:\Program Files\PostgreSQL\8.4.
# Currently, the following version numbers are known to this module:
# "9.1" "9.0" "8.4" "8.3" "8.2" "8.1" "8.0"
#
# To use this variable just do something like this:
# set(PostgreSQL_ADDITIONAL_VERSIONS "9.2" "8.4.4")
# before calling find_package(PostgreSQL) in your CMakeLists.txt file.
# This will mean that the versions you set here will be found first in the order
# specified before the default ones are searched.
#
# ----------------------------------------------------------------------------
# You may need to manually set:
# PostgreSQL_INCLUDE_DIR - the path to where the PostgreSQL include files are.
# PostgreSQL_LIBRARY_DIR - The path to where the PostgreSQL library files are.
# If FindPostgreSQL.cmake cannot find the include files or the library files.
#
# ----------------------------------------------------------------------------
# The following variables are set if PostgreSQL is found:
# PostgreSQL_FOUND - Set to true when PostgreSQL is found.
# PostgreSQL_INCLUDE_DIRS - Include directories for PostgreSQL
# PostgreSQL_LIBRARY_DIRS - Link directories for PostgreSQL libraries
# PostgreSQL_LIBRARIES - The PostgreSQL libraries.
#
# ----------------------------------------------------------------------------
# If you have installed PostgreSQL in a non-standard location.
# (Please note that in the following comments, it is assumed that <Your Path>
# points to the root directory of the include directory of PostgreSQL.)
# Then you have three options.
# 1) After CMake runs, set PostgreSQL_INCLUDE_DIR to <Your Path>/include and
# PostgreSQL_LIBRARY_DIR to wherever the library pq (or libpq in windows) is
# 2) Use CMAKE_INCLUDE_PATH to set a path to <Your Path>/PostgreSQL<-version>. This will allow find_path()
# to locate PostgreSQL_INCLUDE_DIR by utilizing the PATH_SUFFIXES option. e.g. In your CMakeLists.txt file
# set(CMAKE_INCLUDE_PATH ${CMAKE_INCLUDE_PATH} "<Your Path>/include")
# 3) Set an environment variable called ${PostgreSQL_ROOT} that points to the root of where you have
# installed PostgreSQL, e.g. <Your Path>.
#
# ----------------------------------------------------------------------------

set(PostgreSQL_INCLUDE_PATH_DESCRIPTION "top-level directory containing the PostgreSQL include directories. E.g /usr/local/include/PostgreSQL/8.4 or C:/Program Files/PostgreSQL/8.4/include")
set(PostgreSQL_INCLUDE_DIR_MESSAGE "Set the PostgreSQL_INCLUDE_DIR cmake cache entry to the ${PostgreSQL_INCLUDE_PATH_DESCRIPTION}")
set(PostgreSQL_LIBRARY_PATH_DESCRIPTION "top-level directory containing the PostgreSQL libraries.")
set(PostgreSQL_LIBRARY_DIR_MESSAGE "Set the PostgreSQL_LIBRARY_DIR cmake cache entry to the ${PostgreSQL_LIBRARY_PATH_DESCRIPTION}")
set(PostgreSQL_ROOT_DIR_MESSAGE "Set the PostgreSQL_ROOT system variable to where PostgreSQL is found on the machine E.g C:/Program Files/PostgreSQL/8.4")

set(PostgreSQL_KNOWN_ROOTS "/usr/include" "/usr/include/postgres"
"/usr/include/pgsql" "/usr/include/postgresql")
set(PostgreSQL_KNOWN_VERSIONS "9.6" "9.5" "9.4" "9.3" "9.2" "9.1" "9.0")

set(PostgreSQL_ROOT_DIRECTORIES)
foreach (root ${PostgreSQL_KNOWN_ROOTS} )
list(APPEND PostgreSQL_ROOT_DIRECTORIES "${root}")
foreach (version ${PostgreSQL_KNOWN_VERSIONS} )
list(APPEND PostgreSQL_ROOT_DIRECTORIES "${root}/${version}" )
endforeach()
endforeach()

foreach (version ${PostgreSQL_KNOWN_VERSIONS} )
list(APPEND PostgreSQL_ROOT_DIRECTORIES "/usr/pgsql-${version}" )
endforeach()

#
# Look for an installation.
#
find_path(PostgreSQL_INCLUDE_DIR
NAMES libpq-fe.h
PATHS
# Look in other places.
${PostgreSQL_ROOT_DIRECTORIES}
PATH_SUFFIXES
pgsql
postgresql
include
# Help the user find it if we cannot.
DOC "The ${PostgreSQL_INCLUDE_DIR_MESSAGE}"
)

# The PostgreSQL library.
set (PostgreSQL_LIBRARY_TO_FIND pq)
# Setting some more prefixes for the library
set (PostgreSQL_LIB_PREFIX "")
if ( WIN32 )
set (PostgreSQL_LIB_PREFIX ${PostgreSQL_LIB_PREFIX} "lib")
set ( PostgreSQL_LIBRARY_TO_FIND ${PostgreSQL_LIB_PREFIX}${PostgreSQL_LIBRARY_TO_FIND})
endif()

find_library( PostgreSQL_LIBRARY
NAMES ${PostgreSQL_LIBRARY_TO_FIND}
PATHS
${PostgreSQL_ROOT_DIRECTORIES}
PATH_SUFFIXES
lib
)
get_filename_component(PostgreSQL_LIBRARY_DIR ${PostgreSQL_LIBRARY} PATH)

if (PostgreSQL_INCLUDE_DIR AND EXISTS "${PostgreSQL_INCLUDE_DIR}/pg_config.h")
file(STRINGS "${PostgreSQL_INCLUDE_DIR}/pg_config.h" pgsql_version_str
REGEX "^#define[\t ]+PG_VERSION[\t ]+\".*\"")

string(REGEX REPLACE "^#define[\t ]+PG_VERSION[\t ]+\"([^\"]*)\".*" "\\1"
PostgreSQL_VERSION_STRING "${pgsql_version_str}")
unset(pgsql_version_str)
endif()

# Did we find anything?
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(PostgreSQL
REQUIRED_VARS PostgreSQL_LIBRARY PostgreSQL_INCLUDE_DIR
VERSION_VAR PostgreSQL_VERSION_STRING)
set( PostgreSQL_FOUND ${POSTGRESQL_FOUND})

# Now try to get the include and library path.
if(PostgreSQL_FOUND)

set(PostgreSQL_INCLUDE_DIRS ${PostgreSQL_INCLUDE_DIR} )
set(PostgreSQL_LIBRARY_DIRS ${PostgreSQL_LIBRARY_DIR} )
set(PostgreSQL_LIBRARIES ${PostgreSQL_LIBRARY_TO_FIND})

#message("Final PostgreSQL include dir: ${PostgreSQL_INCLUDE_DIRS}")
#message("Final PostgreSQL library dir: ${PostgreSQL_LIBRARY_DIRS}")
#message("Final PostgreSQL libraries: ${PostgreSQL_LIBRARIES}")
endif()

mark_as_advanced(PostgreSQL_INCLUDE_DIR PostgreSQL_LIBRARY )

+ 45
- 0
cmake/FindTarantool.cmake View File

@@ -0,0 +1,45 @@
# Define GNU standard installation directories
include(GNUInstallDirs)

macro(extract_definition name output input)
string(REGEX MATCH "#define[\t ]+${name}[\t ]+\"([^\"]*)\""
_t "${input}")
string(REGEX REPLACE "#define[\t ]+${name}[\t ]+\"(.*)\"" "\\1"
${output} "${_t}")
endmacro()

find_path(TARANTOOL_INCLUDE_DIR tarantool/module.h
HINTS ${TARANTOOL_DIR} ENV TARANTOOL_DIR
PATH_SUFFIXES include
)

if(TARANTOOL_INCLUDE_DIR)
set(_config "-")
file(READ "${TARANTOOL_INCLUDE_DIR}/tarantool/module.h" _config0)
string(REPLACE "\\" "\\\\" _config ${_config0})
unset(_config0)
extract_definition(PACKAGE_VERSION TARANTOOL_VERSION ${_config})
extract_definition(INSTALL_PREFIX _install_prefix ${_config})
unset(_config)
endif()

include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(TARANTOOL
REQUIRED_VARS TARANTOOL_INCLUDE_DIR VERSION_VAR TARANTOOL_VERSION)
if(TARANTOOL_FOUND)
set(TARANTOOL_INCLUDE_DIRS "${TARANTOOL_INCLUDE_DIR}"
"${TARANTOOL_INCLUDE_DIR}/tarantool/"
CACHE PATH "Include directories for Tarantool")
set(TARANTOOL_INSTALL_LIBDIR "${CMAKE_INSTALL_LIBDIR}/tarantool"
CACHE PATH "Directory for storing Lua modules written in Lua")
set(TARANTOOL_INSTALL_LUADIR "${CMAKE_INSTALL_DATADIR}/tarantool"
CACHE PATH "Directory for storing Lua modules written in C")

if (NOT TARANTOOL_FIND_QUIETLY AND NOT FIND_TARANTOOL_DETAILS)
set(FIND_TARANTOOL_DETAILS ON CACHE INTERNAL "Details about TARANTOOL")
message(STATUS "Tarantool LUADIR is ${TARANTOOL_INSTALL_LUADIR}")
message(STATUS "Tarantool LIBDIR is ${TARANTOOL_INSTALL_LIBDIR}")
endif ()
endif()
mark_as_advanced(TARANTOOL_INCLUDE_DIRS TARANTOOL_INSTALL_LIBDIR
TARANTOOL_INSTALL_LUADIR)

+ 5
- 0
debian/.gitignore View File

@@ -0,0 +1,5 @@
tarantool-pg/
files
stamp-*
*.substvars
*.log

+ 5
- 0
debian/changelog View File

@@ -0,0 +1,5 @@
tarantool-pg (1.0.2-1) unstable; urgency=medium

* Initial release

-- Roman Tsisyk <roman@tarantool.org> Wed, 16 Sep 2015 17:16:00 +0300

+ 1
- 0
debian/compat View File

@@ -0,0 +1 @@
9

+ 19
- 0
debian/control View File

@@ -0,0 +1,19 @@
Source: tarantool-pg
Priority: optional
Section: database
Maintainer: Roman Tsisyk <roman@tarantool.org>
Build-Depends: debhelper (>= 9), cdbs,
cmake (>= 2.8),
tarantool-dev (>= 1.6.8.0),
libpq-dev (>= 8.1.0)
Standards-Version: 3.9.6
Homepage: https://github.com/tarantool/pg
Vcs-Git: git://github.com/tarantool/pg.git
Vcs-Browser: https://github.com/tarantool/pg

Package: tarantool-pg
Architecture: i386 amd64 armhf arm64
Depends: tarantool (>= 1.6.8.0), ${shlibs:Depends}, ${misc:Depends}
Pre-Depends: ${misc:Pre-Depends}
Description: PostgreSQL connector for Tarantool
A PostgreSQL connector for Tarantool.

+ 34
- 0
debian/copyright View File

@@ -0,0 +1,34 @@
Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: tarantool-pg
Upstream-Contact: roman@tarantool.org
Source: https://github.com/tarantool/pg

Files: *
Copyright: 2010-2013 Tarantool AUTHORS
License: BSD-2-Clause
Redistribution and use in source and binary forms, with or
without modification, are permitted provided that the following
conditions are met:
.
1. Redistributions of source code must retain the above
copyright notice, this list of conditions and the
following disclaimer.
.
2. Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials
provided with the distribution.
.
THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
<COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.

+ 1
- 0
debian/docs View File

@@ -0,0 +1 @@
README.md

+ 8
- 0
debian/rules View File

@@ -0,0 +1,8 @@
#!/usr/bin/make -f

DEB_CMAKE_EXTRA_FLAGS := -DCMAKE_INSTALL_LIBDIR=lib/$(DEB_HOST_MULTIARCH) \
-DCMAKE_BUILD_TYPE=RelWithDebInfo
DEB_MAKE_CHECK_TARGET :=

include /usr/share/cdbs/1/rules/debhelper.mk
include /usr/share/cdbs/1/class/cmake.mk

+ 1
- 0
debian/source/format View File

@@ -0,0 +1 @@
3.0 (quilt)

+ 29
- 0
pg-scm-1.rockspec View File

@@ -0,0 +1,29 @@
package = 'pg'
version = 'scm-1'
source = {
url = 'git://github.com/tarantool/pg.git',
branch = 'master',
}
description = {
summary = "PostgreSQL connector for Tarantool",
homepage = 'https://github.com/tarantool/pg',
license = 'BSD',
}
dependencies = {
'lua >= 5.1'
}
external_dependencies = {
TARANTOOL = {
header = 'tarantool/module.h';
};
}
build = {
type = 'cmake';
variables = {
CMAKE_BUILD_TYPE="RelWithDebInfo";
TARANTOOL_DIR="$(TARANTOOL_DIR)";
TARANTOOL_INSTALL_LIBDIR="$(LIBDIR)";
TARANTOOL_INSTALL_LUADIR="$(LUADIR)";
};
}
-- vim: syntax=lua

+ 6
- 0
pg/CMakeLists.txt View File

@@ -0,0 +1,6 @@
add_library(driver SHARED driver.c)
target_link_libraries(driver ${PostgreSQL_LIBRARIES} -rdynamic)
set_target_properties(driver PROPERTIES PREFIX "" OUTPUT_NAME "driver")

install(TARGETS driver LIBRARY DESTINATION ${TARANTOOL_INSTALL_LIBDIR}/pg)
install(FILES init.lua DESTINATION ${TARANTOOL_INSTALL_LUADIR}/pg)

+ 573
- 0
pg/driver.c View File

@@ -0,0 +1,573 @@
/*
* Redistribution and use in source and binary forms, with or
* without modification, are permitted provided that the following
* conditions are met:
*
* 1. Redistributions of source code must retain the above
* copyright notice, this list of conditions and the
* following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
* <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
* THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <stddef.h>
#include <assert.h>
#include <stdlib.h>
#include <errno.h>

#include <lua.h>
#include <lauxlib.h>

#include <libpq-fe.h>
#include <pg_config.h>
/* PostgreSQL types (see catalog/pg_type.h) */
#define INT2OID 21
#define INT4OID 23
#define INT8OID 20
#define NUMERICOID 1700
#define BOOLOID 16
#define TEXTOID 25

#include <stdint.h>

#undef PACKAGE_VERSION
#include <module.h>

/**
* Infinity timeout from tarantool_ev.c. I mean, this should be in
* a module.h file.
*/
#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)
{
PGconn **conn_p = (PGconn **)luaL_checkudata(L, index, pg_driver_label);
if (conn_p == NULL || *conn_p == NULL)
luaL_error(L, "Driver fatal error (closed connection "
"or not a connection)");
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
*/
static int
parse_pg_value(struct lua_State *L, PGresult *res, int row, int col)
{
if (PQgetisnull(res, row, col))
return false;
// Procedure called in pcall environment, don't use safe_pushstring
lua_pushstring(L, PQfname(res, col));
const char *val = PQgetvalue(res, row, col);
int len = PQgetlength(res, row, col);

switch (PQftype(res, col)) {
case INT2OID:
case INT4OID:
case NUMERICOID: {
lua_pushlstring(L, val, len);
double v = lua_tonumber(L, -1);
lua_pop(L, 1);
lua_pushnumber(L, v);
break;
}
case INT8OID: {
long long v = strtoll(val, NULL, 10);
luaL_pushint64(L, v);
break;
}
case BOOLOID:
if (*val == 't' || *val == 'T')
lua_pushboolean(L, 1);
else
lua_pushboolean(L, 0);
break;
default:
lua_pushlstring(L, val, len);
}
lua_settable(L, -3);
return true;
}

/**
* Push query result tuples to given table on lua stack
*/
static int
safe_pg_parsetuples(struct lua_State *L)
{
PGresult *res = (PGresult *)lua_topointer(L, 1);
int row, rows = PQntuples(res);
int col, cols = PQnfields(res);
lua_newtable(L);
for (row = 0; row < rows; ++row) {
lua_pushnumber(L, row + 1);
lua_newtable(L);
for (col = 0; col < cols; ++col)
parse_pg_value(L, res, row, col);
lua_settable(L, -3);
}
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
*/
static int
pg_wait_for_result(PGconn *conn)
{
int sock = PQsocket(conn);
while (true) {
if (fiber_is_cancelled())
return -2;
if (PQconsumeInput(conn) != 1)
return PQstatus(conn) == CONNECTION_BAD ? -1: 0;
if (PQisBusy(conn))
coio_wait(sock, COIO_READ, TIMEOUT_INFINITY);
else
break;
}
return 1;
}

/**
* Appends result fom postgres to lua table
*/
static int
pg_resultget(struct lua_State *L, PGconn *conn, int *res_no, int status_ok)
{
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;
}

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;
}
int res = -1;
int fail = 0;
int status = PQresultStatus(pg_res);
switch (status) {
case PGRES_TUPLES_OK:
lua_pushinteger(L, (*res_no)++);
lua_pushcfunction(L, safe_pg_parsetuples);
lua_pushlightuserdata(L, pg_res);
fail = lua_pcall(L, 1, 1, 0);
if (!fail)
lua_settable(L, -3);
case PGRES_COMMAND_OK:
res = 1;
break;
case PGRES_FATAL_ERROR:
case PGRES_EMPTY_QUERY:
case PGRES_NONFATAL_ERROR:
lua_pushinteger(L,
(PQstatus(conn) == CONNECTION_BAD) ? -1: 1);
fail = safe_pushstring(L, PQerrorMessage(conn));
break;
default:
lua_pushinteger(L, -1);
fail = safe_pushstring(L,
"Unwanted execution result status");
}

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

/**
* Parse lua value
*/
static void
lua_parse_param(struct lua_State *L,
int idx, const char **value, int *length, Oid *type)
{
if (lua_isnil(L, idx)) {
*value = NULL;
*length = 0;
*type = 0;
return;
}

if (lua_isboolean(L, idx)) {
static const char pg_true[] = "t";
static const char pg_false[] = "f";
*value = lua_toboolean(L, idx) ? pg_true : pg_false;
*length = 1;
*type = BOOLOID;
return;
}

if (lua_isnumber(L, idx)) {
size_t len;
*value = lua_tolstring(L, idx, &len);
*length = len;
*type = NUMERICOID;
return;
}

// We will pass all other types as strings
size_t len;
*value = lua_tolstring(L, idx, &len);
*length = len;
*type = TEXTOID;
}

/**
* Start query execution
*/
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;
if (paramCount > 0) {
/* Allocate chunk of memory for params */
char *buf = (char *)lua_newuserdata(L, paramCount *
(sizeof(*paramValues) + sizeof(*paramLengths) +
sizeof(*paramTypes)));

paramValues = (const char **) buf;
buf += paramCount * sizeof(*paramValues);
paramLengths = (int *) buf;
buf += paramCount * sizeof(*paramLengths);
paramTypes = (Oid *) buf;

int idx;
for (idx = 0; idx < paramCount; ++idx) {
lua_parse_param(L, idx + 3, paramValues + idx,
paramLengths + idx, paramTypes + idx);
}
res = PQsendQueryParams(conn, sql, paramCount, paramTypes,
paramValues, paramLengths, NULL, 0);
}
else
res = PQsendQuery(conn, sql);

if (res == -1) {
lua_pushinteger(L, PQstatus(conn) == CONNECTION_BAD ? -1: 0);
lua_pushstring(L, PQerrorMessage(conn));
return 2;
}
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;
}

/**
* Test that connection has active transaction
*/
static int
lua_pg_transaction_active(struct lua_State *L)
{
PGconn *conn = lua_check_pgconn(L, 1);
PGTransactionStatusType status;
switch (status = PQtransactionStatus(conn)){
case PQTRANS_IDLE:
case PQTRANS_ACTIVE:
case PQTRANS_INTRANS:
case PQTRANS_INERROR:
lua_pushinteger(L, 1);
lua_pushboolean(L, status != PQTRANS_IDLE);
return 2;
default:
lua_pushinteger(L, -1);
lua_pushstring(L, PQerrorMessage(conn));
return 2;
}
}

/**
* Close connection
*/
static int
lua_pg_close(struct lua_State *L)
{
PGconn **conn_p = (PGconn **)luaL_checkudata(L, 1, pg_driver_label);
if (conn_p == NULL || *conn_p == NULL) {
lua_pushboolean(L, 0);
return 1;
}
PQfinish(*conn_p);
*conn_p = NULL;
lua_pushboolean(L, 1);
return 1;
}

/**
* Collect connection
*/
static int
lua_pg_gc(struct lua_State *L)
{
PGconn **conn_p = (PGconn **)luaL_checkudata(L, 1, pg_driver_label);
if (conn_p && *conn_p)
PQfinish(*conn_p);
if (conn_p)
*conn_p = NULL;
return 0;
}

static int
lua_pg_tostring(struct lua_State *L)
{
PGconn *conn = lua_check_pgconn(L, 1);
lua_pushfstring(L, "PQconn: %p", conn);
return 1;
}

/**
* Prints warnings from Postgresql into tarantool log
*/
static void
pg_notice(void *arg, const char *message)
{
say_info("Postgresql: %s", message);
(void)arg;
}

#if PG_VERSION_NUM >= 90000
/**
* Quote variable
*/
static int
lua_pg_quote(struct lua_State *L)
{
if (lua_gettop(L) < 2) {
lua_pushnil(L);
return 1;
}
PGconn *conn = lua_check_pgconn(L, 1);
size_t len;
const char *s = lua_tolstring(L, 2, &len);

s = PQescapeLiteral(conn, s, len);

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

/**
* Quote identifier
*/
static int
lua_pg_quote_ident(struct lua_State *L)
{
if (lua_gettop(L) < 2) {
lua_pushnil(L);
return 1;
}
PGconn *conn = lua_check_pgconn(L, 1);
size_t len;
const char *s = lua_tolstring(L, 2, &len);

s = PQescapeIdentifier(conn, s, len);

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

#endif

/**
* Start connection to postgresql
*/
static int
lua_pg_connect(struct lua_State *L)
{
if (lua_gettop(L) != 1 || !lua_isstring(L, 1))
luaL_error(L, "Usage: pg.connect(connstring)");

const char *constr = lua_tostring(L, 1);
PGconn *conn = NULL;

conn = PQconnectStart(constr);
if (!conn) {
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) {
lua_pushinteger(L, -1);
int fail = safe_pushstring(L, PQerrorMessage(conn));
PQfinish(conn);
return fail ? lua_push_error(L): 2;
}

int sock = PQsocket(conn);
while (true) {
if (fiber_is_cancelled()) {
lua_pushinteger(L, -2);
safe_pushstring(L, "Fiber was cancelled");
return 1;
}
PostgresPollingStatusType status = PQconnectPoll(conn);
if (status == PGRES_POLLING_OK) {
PQsetNoticeProcessor(conn, pg_notice, NULL);
lua_pushinteger(L, 1);
PGconn **conn_p = (PGconn **)
lua_newuserdata(L, sizeof(conn));
*conn_p = conn;
luaL_getmetatable(L, pg_driver_label);
lua_setmetatable(L, -2);
return 2;
}
if (status == PGRES_POLLING_READING) {
coio_wait(sock, COIO_READ, TIMEOUT_INFINITY);
continue;
}
if (status == PGRES_POLLING_WRITING) {
coio_wait(sock, COIO_WRITE, TIMEOUT_INFINITY);
continue;
}
break;
}
lua_pushinteger(L, -1);
int fail = safe_pushstring(L, PQerrorMessage(conn));
PQfinish(conn);
return fail ? lua_push_error(L): 2;
}

LUA_API int
luaopen_pg_driver(lua_State *L)
{
static const struct luaL_Reg methods [] = {
{"execute", lua_pg_execute},
#if PG_VERSION_NUM >= 90000
{"quote", lua_pg_quote},
{"quote_ident", lua_pg_quote_ident},
#endif
{"close", lua_pg_close},
{"active", lua_pg_transaction_active},
{"__tostring", lua_pg_tostring},
{"__gc", lua_pg_gc},
{NULL, NULL}
};

luaL_newmetatable(L, pg_driver_label);
lua_pushvalue(L, -1);
luaL_register(L, NULL, methods);
lua_setfield(L, -2, "__index");
lua_pushstring(L, pg_driver_label);
lua_setfield(L, -2, "__metatable");
lua_pop(L, 1);

lua_newtable(L);
static const struct luaL_Reg meta [] = {
{"connect", lua_pg_connect},
{NULL, NULL}
};
luaL_register(L, NULL, meta);
return 1;
}

+ 234
- 0
pg/init.lua View File

@@ -0,0 +1,234 @@
-- init.lua (internal file)

local fiber = require('fiber')
local driver = require('pg.driver')
local ffi = require('ffi')

local pool_mt
local conn_mt

--create a new connection
local function conn_create(pg_conn)
local queue = fiber.channel(1)
queue:put(true)
local conn = setmetatable({
usable = true,
conn = pg_conn,
queue = queue,
}, conn_mt)

return conn
end

-- get connection from pool
local function conn_get(pool)
local pg_conn = pool.queue:get()
local status
if pg_conn == nil then
status, pg_conn = driver.connect(pool.conn_string)
if status < 0 then
return error(pg_conn)
end
end
local conn = conn_create(pg_conn, pool)
conn.__gc_hook = ffi.gc(ffi.new('void *'),
function(self)
pg_conn:close()
pool.queue:put(nil)
end)
return conn
end

local function conn_put(conn)
local pgconn = conn.conn
ffi.gc(conn.__gc_hook, nil)
if not conn.queue:get() then
conn.usable = false
return nil
end
conn.usable = false
return pgconn
end

conn_mt = {
__index = {
execute = function(self, sql, ...)
if not self.usable then
return get_error(self.raise.pool, 'Connection is not usable')
end
if not self.queue:get() then
self.queue:put(false)
return get_error(self.raise.pool, 'Connection is broken')
end
local status, datas = self.conn:execute(sql, ...)
if status ~= 0 then
self.queue:put(status > 0)
return error(datas)
end
self.queue:put(true)
return datas, true
end,
begin = function(self)
return self:execute('BEGIN') ~= nil
end,
commit = function(self)
return self:execute('COMMIT') ~= nil
end,
rollback = function(self)
return self:execute('ROLLBACK') ~= nil
end,
ping = function(self)
local status, data, msg = pcall(self.execute, self, 'SELECT 1 AS code')
return msg and data[1][1].code == 1
end,
close = function(self)
if not self.usable then
return error('Connection is not usable')
end
if not self.queue:get() then
self.queue:put(false)
return error('Connection is broken')
end
self.usable = false
self.conn:close()
self.queue:put(false)
return true
end,
active = function(self)
if not self.usable then
return get_error(self.raise.pool, 'Connection is not usable')
end
if not self.queue:get() then
self.queue:put(false)
return get_error(self.raise.pool, 'Connection is broken')
end
local status, msg = self.conn:active()
if status ~= 1 then
self.queue:put(false)
return get_error(self.raise.pool, msg)
end
self.queue:put(true)
return msg
end
}
}

local function build_conn_string(opts)
if opts.conn_string then
return opts.conn_string
end
local connb = {}
if opts.host then
table.insert(connb, string.format(" host='%s'", opts.host))
end
if opts.port then
table.insert(connb, string.format(" port='%s'", opts.port))
end
if opts.user then
table.insert(connb, string.format(" user='%s'", opts.user))
end
if opts.pass or opts.password then
table.insert(connb, string.format(" password='%s'",
opts.pass or opts.password))
end
if opts.db then
table.insert(connb, string.format(" dbname='%s'", opts.db))
end
return table.concat(connb)
end

-- Create connection pool. Accepts pg connection params (host, port, user,
-- password, dbname) separatelly or in one string, size and raise flag.
local function pool_create(opts)
opts = opts or {}
local conn_string = build_conn_string(opts)
opts.size = opts.size or 1
local queue = fiber.channel(opts.size)

for i = 1, opts.size do
local status, conn = driver.connect(conn_string)
if status < 0 then
while queue:count() > 0 do
local pg_conn = queue:get()
pg_conn:close()
end
if status < 0 then
return error(conn)
end
end
queue:put(conn)
end

return setmetatable({
-- connection variables
host = opts.host,
port = opts.port,
user = opts.user,
pass = opts.pass,
db = opts.db,
size = opts.size,
conn_string = conn_string,

-- private variables
queue = queue,
usable = true
}, pool_mt)
end

-- Close pool
local function pool_close(self)
self.usable = false
for i = 1, self.size do
local pg_conn = self.queue:get()
if pg_conn ~= nil then
pg_conn:close()
end
end
end

-- Returns connection
local function pool_get(self)
if not self.usable then
return get_error(self.raise, 'Pool is not usable')
end
local conn = conn_get(self)
local reset_sql = 'BEGIN; RESET ALL; COMMIT;'
if conn:active() then
reset_sql = 'ROLLBACK; ' .. reset_sql
end
conn:execute(reset_sql)
return conn
end

-- Free binded connection
local function pool_put(self, conn)
if conn.usable then
self.queue:put(conn_put(conn))
end
end

pool_mt = {
__index = {
get = pool_get;
put = pool_put;
close = pool_close;
}
}

-- Create connection. Accepts pg connection params (host, port, user,
-- password, dbname) separatelly or in one string and raise flag.
local function connect(opts)
opts = opts or {}

local conn_string = build_conn_string(opts)
local status, pg_conn = driver.connect(conn_string)
if status < 0 then
return error(pg_conn)
end
return conn_create(pg_conn)
end

return {
connect = connect;
pool_create = pool_create;
}

+ 41
- 0
rpm/tarantool-pg.spec View File

@@ -0,0 +1,41 @@
Name: tarantool-pg
Version: 1.0.2
Release: 1%{?dist}
Summary: PostgreSQL connector for Tarantool
Group: Applications/Databases
License: BSD
URL: https://github.com/tarantool/pg
Source0: https://github.com/tarantool/%{name}/archive/%{version}/%{name}-%{version}.tar.gz
BuildRequires: cmake >= 2.8
BuildRequires: gcc >= 4.5
BuildRequires: tarantool-devel >= 1.6.8.0
BuildRequires: postgresql-devel >= 8.1.0
Requires: tarantool >= 1.6.8.0

%description
PostgreSQL connector for Tarantool.

%prep
%setup -q -n %{name}-%{version}

%build
%cmake . -DCMAKE_BUILD_TYPE=RelWithDebInfo
make %{?_smp_mflags}

## Requires postgresql
#%%check
#make %%{?_smp_mflags} check

%install
%make_install

%files
%{_libdir}/tarantool/*/
%{_datarootdir}/tarantool/*/
%doc README.md
%{!?_licensedir:%global license %doc}
%license LICENSE

%changelog
* Wed Feb 17 2016 Roman Tsisyk <roman@tarantool.org> 1.0.1-1
- Initial version of the RPM spec

+ 125
- 0
test/pg.test.lua View File

@@ -0,0 +1,125 @@
#!/usr/bin/env tarantool

package.path = "../?/init.lua;./?/init.lua"
package.cpath = "../?.so;../?.dylib;./?.so;./?.dylib"

local pg = require('pg')
local json = require('json')
local tap = require('tap')
local f = require('fiber')

local host, port, user, pass, db = string.match(os.getenv('PG') or '',
"([^:]*):([^:]*):([^:]*):([^:]*):([^:]*)")

local p, msg = pg.pool_create({ host = host, port = port, user = user, pass = pass,
db = db, raise = false, size = 2 })
if p == nil then error(msg) end

local conn, msg = pg.connect({ host = host, port = port, user = user, pass = pass,
db = db, raise = false})
if conn == nil then error(msg) end

function test_old_api(t, c)
t:plan(14)
t:ok(c ~= nil, "connection")
-- Add an extension to 'tap' module
getmetatable(t).__index.q = function(test, stmt, result, ...)
test:is_deeply({c:execute(stmt, ...)}, {{result}, true},
... ~= nil and stmt..' % '..json.encode({...}) or stmt)
end
t:ok(c:ping(), "ping")
if p == nil then
return
end
t:q('SELECT 123::text AS bla, 345', {{ bla = '123', ['?column?'] = 345 }})
t:q('SELECT -1 AS neg, NULL AS abc', {{ neg = -1 }})
t:q('SELECT -1.1 AS neg, 1.2 AS pos', {{ neg = -1.1, pos = 1.2 }})
t:q('SELECT ARRAY[1,2] AS arr, 1.2 AS pos', {{ arr = '{1,2}', pos = 1.2}})
t:q('SELECT $1 AS val', {{ val = 'abc' }}, 'abc')
t:q('SELECT $1 AS val', {{ val = 123 }}, 123)
t:q('SELECT $1 AS val', {{ val = true }}, true)
t:q('SELECT $1 AS val', {{ val = false }}, false)
t:q('SELECT $1 AS val, $2 AS num, $3 AS str',
{{ val = false, num = 123, str = 'abc'}}, false, 123, 'abc')
t:q('SELECT * FROM (VALUES (1,2), (2,3)) t', {
{ column1 = 1, column2 = 2}, { column1 = 2, column2 = 3}})

t:test("tx", function(t)
t:plan(7)
if not c:execute("CREATE TABLE _tx_test (a int)") then
return
end

t:ok(c:begin(), "begin")
c:execute("INSERT INTO _tx_test VALUES(10)");
t:q('SELECT * FROM _tx_test', {{ a = 10 }})
t:ok(c:rollback(), "roolback")
t:q('SELECT * FROM _tx_test', {})

t:ok(c:begin(), "begin")
c:execute("INSERT INTO _tx_test VALUES(10)");
t:ok(c:commit(), "commit")
t:q('SELECT * FROM _tx_test', {{ a = 10 }})

c:execute("DROP TABLE _tx_test")
end)

local status, reason = pcall(c.execute, c, 'DROP TABLE unknown_table')
t:like(reason, 'unknown_table', 'error')
end

function test_gc(t, p)
t:plan(1)
p:get()
local c = p:get()
c = nil
collectgarbage('collect')
t:is(p.queue:count(), p.size, 'gc connections')
end

function test_conn_fiber1(c, q)
for i = 1, 10 do
c:execute('SELECT pg_sleep(0.05)')
end
q:put(true)
end

function test_conn_fiber2(c, q)
for i = 1, 25 do
c:execute('SELECT pg_sleep(0.02)')
end
q:put(true)
end

function test_conn_concurrent(t, p)
t:plan(1)
local c = p:get()
local q = f.channel(2)
local t1 = f.time()
f.create(test_conn_fiber1, c, q)
f.create(test_conn_fiber2, c, q)
q:get()
q:get()
p:put(c)
t:ok(f.time() - t1 >= 0.95, 'concurrent connections')
end

function test_pg_int64(t, p)
t:plan(1)
conn = p:get()
conn:execute('create table int64test (id bigint)')
conn:execute('insert into int64test values(1234567890123456789)')
local r, m = conn:execute('select id from int64test')
conn:execute('drop table int64test')
t:ok(r[1][1]['id'] == 1234567890123456789LL, 'int64 test')
p:put(conn)
end

tap.test('connection old api', test_old_api, conn)
local pool_conn = p:get()
tap.test('connection old api via pool', test_old_api, pool_conn)
p:put(pool_conn)
tap.test('test collection connections', test_gc, p)
tap.test('connection concurrent', test_conn_concurrent, p)
tap.test('int64', test_pg_int64, p)
p:close()

Loading…
Cancel
Save