summaryrefslogtreecommitdiff
path: root/test/src/premature-test.c
blob: 8bbb73d52ab87971a55be89cc8a1176ecca08896 (plain)
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
/*
 * This test case tests the case that a query needs to be described before
 * executing it. That happens when an application calls e.h. SQLDescribeCol()
 * or SQLNumResultCols() after preparing a query with SQLPrepare(), but
 * before calling SQLExecute().
 *
 * The driver used to have an option to do so-called "premature execution",
 * where it simply executed the driver prematurely, before the SQLExecute()
 * call. That was of course quite dangerous, if the query was not read-only,
 * and the application didn't follow through with the SQLExecute(). It could
 * be disabled with the DisallowPremature setting. The driver doesn't do
 * that anymore, but it's a good case to test.
 */
#include <stdio.h>
#include <stdlib.h>

#include "common.h"

static void
runtest(const char *query, char *bind_before, char *bind_after, int execute)
{
	SQLRETURN	rc;
	HSTMT		hstmt;
	SQLLEN		cbParam1;
	SQLSMALLINT colcount;

	rc = SQLAllocHandle(SQL_HANDLE_STMT, conn, &hstmt);
	CHECK_CONN_RESULT(rc, "SQLAllocHandle failed", conn);

	rc = SQLPrepare(hstmt, (SQLCHAR *) query, SQL_NTS);
	CHECK_STMT_RESULT(rc, "SQLPrepare failed", hstmt);

	if (bind_before)
	{
		cbParam1 = SQL_NTS;
		rc = SQLBindParameter(hstmt, 1, SQL_PARAM_INPUT,
						  SQL_C_CHAR,	/* value type */
						  SQL_CHAR,		/* param type */
						  20,			/* column size */
						  0,			/* dec digits */
						  bind_before,	/* param value ptr */
						  0,			/* buffer len */
						  &cbParam1		/* StrLen_or_IndPtr */);
		CHECK_STMT_RESULT(rc, "SQLBindParameter failed", hstmt);
	}

	/* Test SQLNumResultCols, called before SQLExecute() */
	rc = SQLNumResultCols(hstmt, &colcount);
	CHECK_STMT_RESULT(rc, "SQLNumResultCols failed", hstmt);
	printf("# of result cols: %d\n", colcount);

	if (bind_after)
	{
		cbParam1 = SQL_NTS;
		rc = SQLBindParameter(hstmt, 1, SQL_PARAM_INPUT,
						  SQL_C_CHAR,	/* value type */
						  SQL_CHAR,		/* param type */
						  20,			/* column size */
						  0,			/* dec digits */
						  bind_after,	/* param value ptr */
						  0,			/* buffer len */
						  &cbParam1		/* StrLen_or_IndPtr */);
		CHECK_STMT_RESULT(rc, "SQLBindParameter failed", hstmt);
	}
	
	/* Don't execute the statement! The row should not be inserted. */

	/* And execute */
	if (execute)
	{
		rc = SQLExecute(hstmt);
		CHECK_STMT_RESULT(rc, "SQLExecute failed", hstmt);
	}

	rc = SQLFreeStmt(hstmt, SQL_DROP);
	CHECK_STMT_RESULT(rc, "SQLFreeStmt failed", hstmt);
}

int main(int argc, char **argv)
{
	SQLRETURN rc;
	HSTMT hstmt = SQL_NULL_HSTMT;

	test_connect();

	rc = SQLAllocHandle(SQL_HANDLE_STMT, conn, &hstmt);
	CHECK_CONN_RESULT(rc, "SQLAllocHandle failed", conn);

	/**** Set up a test table and function ****/

	rc = SQLExecDirect(hstmt, (SQLCHAR *) "CREATE TEMPORARY TABLE premature_test (t text)", SQL_NTS);
	CHECK_STMT_RESULT(rc, "SQLExecDirect failed", hstmt);

	rc = SQLFreeStmt(hstmt, SQL_CLOSE);
	CHECK_STMT_RESULT(rc, "SQLFreeStmt failed", hstmt);

	rc = SQLExecDirect(hstmt, (SQLCHAR *)
		"CREATE OR REPLACE FUNCTION insertfunc (t text) RETURNS text AS "
		"$$ INSERT INTO premature_test VALUES ($1) RETURNING 'func insert'::text $$ "
		"LANGUAGE sql",
					   SQL_NTS);
	CHECK_STMT_RESULT(rc, "SQLExecDirect failed", hstmt);

	rc = SQLFreeStmt(hstmt, SQL_CLOSE);
	CHECK_STMT_RESULT(rc, "SQLFreeStmt failed", hstmt);


	/****
	 * First, try with a plain INSERT. Prepare the statement, run
	 * SQLNumResultCols() which forces the statement to be sent and parsed
	 * in the backend.
	 */
	printf("\nPreparing an INSERT statement\n");
	runtest("INSERT INTO premature_test VALUES (?) RETURNING 'plain insert'::text", "plain insert", NULL, 0);

	/*** Now, do the same with the function ***/
	printf("\nPreparing an insert using a function\n");
	runtest("SELECT insertfunc(?)", "function insert", NULL, 0);

	/*** Same with the function, used in a multi-statement ***/

	printf("\nPreparing a multi-statement\n");
	runtest("SELECT 'foo', 2, 3; SELECT insertfunc(?), 2; SELECT 'bar'",
			"function insert in multi-statement", NULL, 0);

	/*** Again with the function, but this time execute it too. With a
	 * twist: we rebind a different parameter after the SQLNumResultCols
	 * call.
	 */
	printf("\nPrepare with function, but execute with different param\n");
	runtest("SELECT insertfunc(?)", "function insert wrong",
			"function insert right", 1);

	/*** Now check that the table contains only the last insertion  ***/

	printf("\nChecking table contents. Should contain only one row.\n");
	rc = SQLExecDirect(hstmt, (SQLCHAR *) "SELECT * FROM premature_test", SQL_NTS);
	CHECK_STMT_RESULT(rc, "SQLExecDirect failed", hstmt);
	print_result(hstmt);

	/* Clean up */
	test_disconnect();

	return 0;
}