File: test_prepared_statement.cxx

package info (click to toggle)
libpqxx 4.0.1%2Bdfsg3-8
  • links: PTS, VCS
  • area: main
  • in suites: stretch
  • size: 16,012 kB
  • ctags: 9,469
  • sloc: sh: 11,289; cpp: 10,801; xml: 1,256; makefile: 287; ansic: 195; python: 159
file content (205 lines) | stat: -rw-r--r-- 6,017 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
#include <cassert>
#include <iostream>
#include <list>

#include "test_helpers.hxx"

using namespace PGSTD;
using namespace pqxx;


// Test program for libpqxx.  Define and use prepared statements.

#define COMPARE_RESULTS(name, lhs, rhs) \
  PQXX_CHECK_EQUAL(	\
	rhs,		\
	lhs, 		\
	"Executing " name " as prepared statement yields different results.");

namespace
{
string stringize(transaction_base &t, const string &arg)
{
  return "'" + t.esc(arg) + "'";
}


template<typename T> string stringize(transaction_base &t, T i)
{
  return stringize(t, to_string(i));
}


// Substitute variables in raw query.  This is not likely to be very robust,
// but it should do for just this test.  The main shortcomings are escaping,
// and not knowing when to quote the variables.
// Note we do the replacement backwards (meaning forward_only iterators won't
// do!) to avoid substituting e.g. "$12" as "$1" first.
template<typename ITER> string subst(transaction_base &t,
	string q,
	ITER patbegin,
	ITER patend)
{
  ptrdiff_t i = pqxx::internal::distance(patbegin, patend);
  for (ITER arg = patend; i > 0; --i)
  {
   --arg;
    const string marker = "$" + to_string(i),
	  var = stringize(t, *arg);
    const string::size_type msz = marker.size();
    while (q.find(marker) != string::npos) q.replace(q.find(marker),msz,var);
  }
  return q;
}


template<typename CNTNR> string subst(transaction_base &t,
	string q,
	const CNTNR &patterns)
{
  return subst(t, q, patterns.begin(), patterns.end());
}


void test_prepared_statement(transaction_base &T)
{
  connection_base &C(T.conn());

  const string
	Q_readpgtables = "SELECT * FROM pg_tables",
	Q_seetable = Q_readpgtables + " WHERE tablename = $1",
	Q_seetables = Q_seetable + " OR tablename = $2";

  try
  {
    PQXX_CHECK(
	!(T.prepared("CountToTen").exists()),
	"Nonexistent prepared statement thinks it exists.");

    // Prepare a simple statement.
    C.prepare("CountToTen", "SELECT * FROM generate_series(1, 10)");

    PQXX_CHECK(
	T.prepared("CountToTen").exists(),
	"Prepared statement thinks it doesn't exist.");

    // See if a basic prepared statement works just like a regular query.
    PQXX_CHECK_EQUAL(
	T.prepared("CountToTen").exec(),
	T.exec("SELECT * FROM generate_series(1, 10)"),
	"CountToTen");
  }
  catch (const feature_not_supported &)
  {
    cout << "Backend version does not support prepared statements.  Skipping."
         << endl;
    return;
  }

  // Try prepare_now() on an already prepared statement.
  C.prepare_now("CountToTen");

  // Drop prepared statement.
  C.unprepare("CountToTen");

  PQXX_CHECK_THROWS(
	C.prepare_now("CountToTen"),
	exception,
	"prepare_now() succeeded on dropped statement.");

  // It's okay to unprepare a statement repeatedly.
  C.unprepare("CountToTen");
  C.unprepare("CountToTen");

  // Executing an unprepared statement fails.
  PQXX_CHECK_THROWS(
	T.prepared("CountToTen").exec(),
	exception,
	"Execute unprepared statement didn't fail.");

  // Once unprepared, a statement can be prepared and used again.
  C.prepare("CountToTen", "SELECT generate_series FROM generate_series(1, 10)");
  C.prepare_now("CountToTen");
  COMPARE_RESULTS("CountToTen_2",
	T.prepared("CountToTen").exec(),
	T.exec("SELECT * FROM generate_series(1, 10)"));

  // Double preparation of identical statement should be ignored...
  C.prepare("CountToTen", "SELECT generate_series FROM generate_series(1, 10)");
  COMPARE_RESULTS("CountToTen_double",
	T.prepared("CountToTen").exec(),
	T.exec("SELECT * FROM generate_series(1, 10)"));

  // ...But a modified definition shouldn't.
  PQXX_CHECK_THROWS(
	C.prepare(
		"CountToTen",
		"SELECT generate_series FROM generate_series(1, 11)"),
	exception,
	"Bad redefinition of statement went unnoticed.");

  // Test prepared statement with parameter.

  C.prepare("CountUpToTen", "SELECT * FROM generate_series($1, 10)");

  vector<int> args;
  args.push_back(2);
  COMPARE_RESULTS("CountUpToTen_seq",
	T.prepared("CountUpToTen")(args[0]).exec(),
	T.exec(subst(T, "SELECT * FROM generate_series($1, 10)", args)));

  // Test prepared statement with 2 parameters.

  C.prepare("CountRange", "SELECT * FROM generate_series($1::int, $2::int)");
  COMPARE_RESULTS("CountRange_seq",
      T.prepared("CountRange")(2)(5).exec(),
      T.exec("SELECT * FROM generate_series(2, 5)"));

  // Test prepared statement with a null parameter.
  vector<const char *> ptrs;
  ptrs.push_back(0);
  ptrs.push_back("99");

  COMPARE_RESULTS("CountRange_null1",
	T.prepared("CountRange")(ptrs[0])(ptrs[1]).exec(),
	T.exec("SELECT * FROM generate_series(NULL, 99)"));

  // Test prepared statement with a binary parameter.
  C.prepare("GimmeBinary", "SELECT $1::bytea");

  const binarystring bin_data(string("x \x01 \x02 \xff y"));

  PQXX_CHECK_EQUAL(
	binarystring(T.prepared("GimmeBinary")(bin_data).exec()[0][0]).str(),
	bin_data.str(),
	"Binary parameter was mangled somewhere along the way.");

  const binarystring nully("x\0y", 3);
  PQXX_CHECK_EQUAL(
	binarystring(T.prepared("GimmeBinary")(nully).exec()[0][0]).str(),
	nully.str(),
	"Binary string breaks on nul byte.");

  if (C.supports(connection_base::cap_prepare_unnamed_statement))
  {
    // Test unnamed prepared statement.
    C.prepare("SELECT 2*$1");
    int outcome = T.prepared()(9).exec()[0][0].as<int>();
    PQXX_CHECK_EQUAL(outcome, 18, "Unnamed prepared statement went mad.");

    // Redefine unnamed prepared statement.  Does not need to be unprepared
    // first.
    C.prepare("SELECT 2*$1 + $2");
    outcome = T.prepared()(9)(2).exec()[0][0].as<int>();
    PQXX_CHECK_EQUAL(outcome, 20, "Unnamed statement not properly redefined.");

    // Unlike how the unnamed prepared statement works in libpq, we can issue
    // other queries and then re-use the unnamed prepared statement.
    T.exec("SELECT 12");
    outcome = T.prepared()(3)(1).exec()[0][0].as<int>();
    PQXX_CHECK_EQUAL(outcome, 7, "Unnamed statement isn't what it was.");
  }
}
} // namespace

PQXX_REGISTER_TEST(test_prepared_statement)