-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathPrepared.cpp
More file actions
234 lines (182 loc) · 7.2 KB
/
Copy pathPrepared.cpp
File metadata and controls
234 lines (182 loc) · 7.2 KB
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
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
// Unit tests for Async-SQLite Wrapper (Group 3: Prepared Statement Lifecycle)
#include "async_sqlite3.h"
#include "DelegateMQ.h"
#include <iostream>
#include <cstdio>
#include <vector>
#include <string>
using namespace async;
static const char* TEST_DB_FILE_STMT = "test_stmt.sqlite";
// -----------------------------------------------------------------------------
// Helper Infrastructure
// -----------------------------------------------------------------------------
static void CleanupDB() {
std::remove(TEST_DB_FILE_STMT);
}
static sqlite3* SetupDB() {
sqlite3* db = nullptr;
sqlite3_init_async();
int rc = async::sqlite3_open(TEST_DB_FILE_STMT, &db);
DMQ_ASSERT_TRUE(rc == SQLITE_OK);
return db;
}
static void TearDownDB(sqlite3* db) {
if (db) {
async::sqlite3_close(db);
}
CleanupDB();
}
// -----------------------------------------------------------------------------
// Test 1: Basic Prepare, Step, Finalize (DDL)
// -----------------------------------------------------------------------------
static void Test_Stmt_Lifecycle_Basic()
{
sqlite3* db = SetupDB();
sqlite3_stmt* stmt = nullptr;
int rc = 0;
// 1. Prepare: Create Table
const char* sql = "CREATE TABLE test_lifecycle (id INT, data TEXT);";
rc = async::sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr);
DMQ_ASSERT_TRUE(rc == SQLITE_OK);
DMQ_ASSERT_TRUE(stmt != nullptr);
// 2. Step: Execute the statement
// DDL statements return SQLITE_DONE on the first step
rc = async::sqlite3_step(stmt);
DMQ_ASSERT_TRUE(rc == SQLITE_DONE);
// 3. Finalize: Clean up memory on the worker thread
rc = async::sqlite3_finalize(stmt);
DMQ_ASSERT_TRUE(rc == SQLITE_OK);
TearDownDB(db);
}
// -----------------------------------------------------------------------------
// Test 2: Reading Rows (Select Loop with Columns)
// -----------------------------------------------------------------------------
static void Test_Stmt_SelectLoop()
{
sqlite3* db = SetupDB();
// Seed data using simple exec
const char* seed = "CREATE TABLE users (id INT, name TEXT);"
"INSERT INTO users VALUES (10, 'Alice');"
"INSERT INTO users VALUES (20, 'Bob');";
async::sqlite3_exec(db, seed, nullptr, nullptr, nullptr);
sqlite3_stmt* stmt = nullptr;
const char* query = "SELECT id, name FROM users ORDER BY id ASC;";
// 1. Prepare
int rc = async::sqlite3_prepare_v2(db, query, -1, &stmt, nullptr);
DMQ_ASSERT_TRUE(rc == SQLITE_OK);
// 2. Step Row 1
rc = async::sqlite3_step(stmt);
DMQ_ASSERT_TRUE(rc == SQLITE_ROW);
// 3. Verify Columns (Row 1)
int id = async::sqlite3_column_int(stmt, 0);
DMQ_ASSERT_TRUE(id == 10);
const unsigned char* text = async::sqlite3_column_text(stmt, 1);
DMQ_ASSERT_TRUE(text != nullptr);
std::string name1(reinterpret_cast<const char*>(text));
DMQ_ASSERT_TRUE(name1 == "Alice");
// 4. Step Row 2
rc = async::sqlite3_step(stmt);
DMQ_ASSERT_TRUE(rc == SQLITE_ROW);
// 5. Verify Columns (Row 2)
id = async::sqlite3_column_int(stmt, 0);
DMQ_ASSERT_TRUE(id == 20);
text = async::sqlite3_column_text(stmt, 1);
std::string name2(reinterpret_cast<const char*>(text));
DMQ_ASSERT_TRUE(name2 == "Bob");
// 6. Step End
rc = async::sqlite3_step(stmt);
DMQ_ASSERT_TRUE(rc == SQLITE_DONE);
// 7. Finalize
async::sqlite3_finalize(stmt);
TearDownDB(db);
}
// -----------------------------------------------------------------------------
// Test 3: Reset and Re-execution
// -----------------------------------------------------------------------------
static void Test_Stmt_Reset()
{
sqlite3* db = SetupDB();
async::sqlite3_exec(db, "CREATE TABLE counter (val INT);", nullptr, nullptr, nullptr);
sqlite3_stmt* stmt = nullptr;
// Note: In real usage we would bind inputs, but here we just reuse the SQL
const char* insertSql = "INSERT INTO counter VALUES (100);";
// 1. Prepare ONCE
async::sqlite3_prepare_v2(db, insertSql, -1, &stmt, nullptr);
// 2. Run First Time
int rc = async::sqlite3_step(stmt);
DMQ_ASSERT_TRUE(rc == SQLITE_DONE);
// 3. Reset (Reset puts the statement back to the beginning)
// IMPORTANT: It does not clear bindings (tested in Group 4)
rc = async::sqlite3_reset(stmt);
DMQ_ASSERT_TRUE(rc == SQLITE_OK);
// 4. Run Second Time
rc = async::sqlite3_step(stmt);
DMQ_ASSERT_TRUE(rc == SQLITE_DONE);
async::sqlite3_finalize(stmt);
// Verify we have 2 rows now
int rowCount = 0;
auto countCb = [](void* ptr, int, char**, char**) -> int {
(*static_cast<int*>(ptr))++;
return 0;
};
async::sqlite3_exec(db, "SELECT * FROM counter;", countCb, &rowCount, nullptr);
DMQ_ASSERT_TRUE(rowCount == 2);
TearDownDB(db);
}
// -----------------------------------------------------------------------------
// Test 4: Prepare Failure (Invalid SQL)
// -----------------------------------------------------------------------------
static void Test_Stmt_PrepareFail()
{
sqlite3* db = SetupDB();
sqlite3_stmt* stmt = nullptr;
// Syntax error (missing FROM table)
const char* badSql = "SELECT * missing_table";
int rc = async::sqlite3_prepare_v2(db, badSql, -1, &stmt, nullptr);
// Should fail
DMQ_ASSERT_TRUE(rc != SQLITE_OK);
// stmt should be NULL on failure (usually, depends on specific SQLite version behavior,
// but wrapper should propagate it)
DMQ_ASSERT_TRUE(stmt == nullptr);
// Verify we can get the error message from the DB handle
const char* errMsg = async::sqlite3_errmsg(db);
DMQ_ASSERT_TRUE(errMsg != nullptr);
// Just ensure the string isn't empty
DMQ_ASSERT_TRUE(std::string(errMsg).length() > 0);
TearDownDB(db);
}
// -----------------------------------------------------------------------------
// Test 5: Column Metadata
// -----------------------------------------------------------------------------
static void Test_Stmt_ColumnMetadata()
{
sqlite3* db = SetupDB();
sqlite3_stmt* stmt = nullptr;
const char* sql = "SELECT 1 AS my_num, 'text' AS my_str";
async::sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr);
// Must step at least once (or just prepare, depending on SQLite version)
// to get column count, but Step is usually not required for Col Count.
// However, Step IS required for Column Data.
int colCount = async::sqlite3_column_count(stmt);
DMQ_ASSERT_TRUE(colCount == 2);
const char* name0 = async::sqlite3_column_name(stmt, 0);
DMQ_ASSERT_TRUE(std::string(name0) == "my_num");
const char* name1 = async::sqlite3_column_name(stmt, 1);
DMQ_ASSERT_TRUE(std::string(name1) == "my_str");
async::sqlite3_finalize(stmt);
TearDownDB(db);
}
// -----------------------------------------------------------------------------
// Main Entry Point
// -----------------------------------------------------------------------------
void Prepared_UT()
{
std::cout << "Running SQLite Prepared Statement Tests..." << std::endl;
CleanupDB();
Test_Stmt_Lifecycle_Basic();
Test_Stmt_SelectLoop();
Test_Stmt_Reset();
Test_Stmt_PrepareFail();
Test_Stmt_ColumnMetadata();
std::cout << "SQLite Prepared Statement Tests Passed!" << std::endl;
}