Skip to content

Commit

Permalink
Experimental parameter<> support for #71
Browse files Browse the repository at this point in the history
  • Loading branch information
d-frey committed Dec 16, 2023
1 parent f25b9e2 commit 0810262
Show file tree
Hide file tree
Showing 4 changed files with 191 additions and 0 deletions.
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ set(taopq_INCLUDE_FILES
${taopq_INCLUDE_DIRS}/tao/pq/notification.hpp
${taopq_INCLUDE_DIRS}/tao/pq/null.hpp
${taopq_INCLUDE_DIRS}/tao/pq/oid.hpp
${taopq_INCLUDE_DIRS}/tao/pq/parameter.hpp
${taopq_INCLUDE_DIRS}/tao/pq/parameter_traits.hpp
${taopq_INCLUDE_DIRS}/tao/pq/parameter_traits_aggregate.hpp
${taopq_INCLUDE_DIRS}/tao/pq/parameter_traits_array.hpp
Expand Down
100 changes: 100 additions & 0 deletions include/tao/pq/parameter.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// Copyright (c) 2023 Daniel Frey and Dr. Colin Hirsch
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt)

#ifndef TAO_PQ_PARAMETER_HPP
#define TAO_PQ_PARAMETER_HPP

#include <cassert>
#include <cstddef>
#include <memory>
#include <stdexcept>
#include <type_traits>
#include <utility>

#include <tao/pq/oid.hpp>
#include <tao/pq/parameter_traits.hpp>

namespace tao::pq
{
class transaction;

// NOTE: for now, this is experimental and might change or vanish at any time!
// TODO: naming?
template< std::size_t Max = 16 >
class parameter
{
private:
struct binder
{
virtual ~binder() = default;
};

template< typename T >
class traits_binder : public binder
{
private:
const parameter_traits< T > m_traits;

public:
explicit traits_binder( const T& t ) noexcept( noexcept( parameter_traits< T >( t ) ) )
: m_traits( t )
{}

template< std::size_t... Is >
void fill( Oid* types, const char** values, int* lengths, int* formats, std::index_sequence< Is... > /*unused*/ ) const // TODO: noexcept( ... )?
{
( ( types[ Is ] = static_cast< Oid >( m_traits.template type< Is >() ) ), ... );
( ( values[ Is ] = m_traits.template value< Is >() ), ... );
( ( lengths[ Is ] = m_traits.template length< Is >() ), ... );
( ( formats[ Is ] = m_traits.template format< Is >() ), ... );
}
};

std::size_t m_pos = 0;
std::unique_ptr< binder > m_binder[ Max ];

std::size_t m_size = 0;
Oid m_types[ Max ];
const char* m_values[ Max ];
int m_lengths[ Max ];
int m_formats[ Max ];

friend class transaction;

template< typename A >
void bind_impl( const A& a ) // TODO: protect against binding temporaries!
{
constexpr auto columns = parameter_traits< std::decay_t< const A& > >::columns;
if( m_size + columns > Max ) {
throw std::length_error( "too many parameters!" );
}

auto* bptr = new traits_binder< std::decay_t< const A& > >( a );
m_binder[ m_pos++ ].reset( bptr );

bptr->fill( &m_types[ m_size ], &m_values[ m_size ], &m_lengths[ m_size ], &m_formats[ m_size ], std::make_index_sequence< columns >() );
m_size += columns;
}

public:
// NOTE: arguments must remain VALID and UNMODIFIED until this object is destroyed or reset.
template< typename... As >
void bind( As&&... as )
{
( bind_impl( std::forward< As >( as ) ), ... );
}

void reset() noexcept
{
for( std::size_t i = 0; i < m_pos; ++i ) {
m_binder[ i ].reset();
}
m_pos = 0;
m_size = 0;
}
};

} // namespace tao::pq

#endif
13 changes: 13 additions & 0 deletions include/tao/pq/transaction.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include <tao/pq/internal/gen.hpp>
#include <tao/pq/internal/zsv.hpp>
#include <tao/pq/oid.hpp>
#include <tao/pq/parameter.hpp>
#include <tao/pq/parameter_traits.hpp>
#include <tao/pq/result.hpp>

Expand Down Expand Up @@ -104,6 +105,18 @@ namespace tao::pq
}
}

template< std::size_t Max >
void send( const internal::zsv statement, const parameter< Max >& as )
{
send_params( statement, as.m_size, as.m_types, as.m_values, as.m_lengths, as.m_formats );
}

template< std::size_t Max >
void send( const internal::zsv statement, parameter< Max >& as )
{
send( statement, const_cast< const parameter< Max >& >( as ) );
}

[[nodiscard]] auto get_result( const std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now() ) -> result;

template< typename... As >
Expand Down
77 changes: 77 additions & 0 deletions src/test/pq/parameter.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// Copyright (c) 2023 Daniel Frey and Dr. Colin Hirsch
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt)

#include "../getenv.hpp"
#include "../macros.hpp"

#include <iostream>
#include <tao/pq.hpp>

void run()
{
// overwrite the default with an environment variable if needed
const auto connection_string = tao::pq::internal::getenv( "TAOPQ_TEST_DATABASE", "dbname=template1" );

// open a connection to the database
const auto conn = tao::pq::connection::create( connection_string );

// execute statements
conn->execute( "DROP TABLE IF EXISTS tao_parameter" );
conn->execute( "CREATE TABLE tao_parameter ( name TEXT PRIMARY KEY, age INTEGER NOT NULL )" );

// prepare statements
conn->prepare( "insert_user", "INSERT INTO tao_parameter ( name, age ) VALUES ( $1, $2 )" );

{
// begin transaction
const auto tr = conn->transaction();

// execute previously prepared statements
{
tao::pq::parameter p;
p.bind( "Daniel", 42 );
tr->execute( "insert_user", p );
p.reset();
p.bind( "Tom" );
p.bind( 41 );
tr->execute( "insert_user", p );
}

{
tao::pq::parameter< 2 > p;
p.bind( "Jerry" );
p.bind( 29 );
tr->execute( "insert_user", p );
}

// commit transaction
tr->commit();
}

// query data
const auto users = conn->execute( "SELECT name, age FROM tao_parameter WHERE age >= $1", 40 );

// iterate and convert results
for( const auto& row : users ) {
std::cout << row[ "name" ].as< std::string >() << " is "
<< row[ "age" ].as< unsigned >() << " years old.\n";
}
}

auto main() -> int
{
try {
run();
}
// LCOV_EXCL_START
catch( const std::exception& e ) {
std::cerr << "exception: " << e.what() << std::endl;
throw;
}
catch( ... ) {
std::cerr << "unknown exception" << std::endl;
throw;
}
// LCOV_EXCL_STOP
}

0 comments on commit 0810262

Please sign in to comment.