Skip to content

Commit 352ddcb

Browse files
committed
feat(core): add parameterized query interface to database_backend
Add select_prepared() and execute_prepared() virtual methods with default fallback implementations that expand placeholders via string interpolation. This establishes the interface for wire-level prepared statements while maintaining backward compatibility. Supports both $N (PostgreSQL-style) and ? (SQLite-style) placeholders in the fallback. Backends should override these methods with native prepared statement implementations for true SQL injection protection. Fix pre-existing broken markdown anchors in docs/README.kr.md. Part of #557
1 parent 470664a commit 352ddcb

2 files changed

Lines changed: 127 additions & 21 deletions

File tree

โ€Ždatabase/core/database_backend.hโ€Ž

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,47 @@ class database_backend
151151
*/
152152
virtual kcenon::common::VoidResult execute_query(const std::string& query_string) = 0;
153153

154+
/**
155+
* @brief Execute a parameterized SELECT query (prepared statement)
156+
*
157+
* Parameters are bound at the wire-protocol level, providing stronger
158+
* SQL injection protection than string escaping. Backends that support
159+
* native prepared statements (PostgreSQL, SQLite) should override this.
160+
*
161+
* @param query SQL with positional placeholders ($1, $2, ... or ?, ?, ...)
162+
* @param params Parameter values to bind
163+
* @return Query results as rows, or error
164+
*
165+
* @note Default implementation falls back to string interpolation via
166+
* execute_query/select_query for backends that have not yet
167+
* implemented native prepared statement support.
168+
*/
169+
[[nodiscard]] virtual kcenon::common::Result<database_result> select_prepared(
170+
const std::string& query,
171+
const std::vector<database_value>& params)
172+
{
173+
// Default fallback: substitute params inline (less secure, but functional)
174+
auto expanded = expand_params(query, params);
175+
return select_query(expanded);
176+
}
177+
178+
/**
179+
* @brief Execute a parameterized DML/DDL query (prepared statement)
180+
*
181+
* @param query SQL with positional placeholders ($1, $2, ... or ?, ?, ...)
182+
* @param params Parameter values to bind
183+
* @return VoidResult::ok() on success, error on failure
184+
*
185+
* @see select_prepared for details on parameterized queries
186+
*/
187+
[[nodiscard]] virtual kcenon::common::VoidResult execute_prepared(
188+
const std::string& query,
189+
const std::vector<database_value>& params)
190+
{
191+
auto expanded = expand_params(query, params);
192+
return execute_query(expanded);
193+
}
194+
154195
/**
155196
* @brief Begin a transaction
156197
* @return VoidResult::ok() on success, error on failure
@@ -188,6 +229,71 @@ class database_backend
188229
* Example keys: "server_version", "connection_id", "protocol_version"
189230
*/
190231
virtual std::map<std::string, std::string> connection_info() const = 0;
232+
233+
protected:
234+
/**
235+
* @brief Expand positional parameters into a SQL string (fallback)
236+
*
237+
* Substitutes $1, $2, ... or ?, ?, ... placeholders with stringified
238+
* parameter values. Used by the default select_prepared/execute_prepared
239+
* implementations. Backends with native prepared statements should
240+
* override the virtual methods instead of relying on this.
241+
*
242+
* @warning This performs string interpolation, NOT wire-level binding.
243+
* Override select_prepared/execute_prepared for true security.
244+
*/
245+
static std::string expand_params(
246+
const std::string& query,
247+
const std::vector<database_value>& params)
248+
{
249+
std::string result = query;
250+
251+
// Replace $N placeholders (PostgreSQL-style, 1-indexed)
252+
for (size_t i = params.size(); i > 0; --i) {
253+
auto placeholder = "$" + std::to_string(i);
254+
auto pos = result.find(placeholder);
255+
if (pos != std::string::npos) {
256+
result.replace(pos, placeholder.size(), value_to_sql(params[i - 1]));
257+
}
258+
}
259+
260+
// Replace ? placeholders (SQLite-style, left-to-right)
261+
size_t param_idx = 0;
262+
auto pos = result.find('?');
263+
while (pos != std::string::npos && param_idx < params.size()) {
264+
auto val = value_to_sql(params[param_idx++]);
265+
result.replace(pos, 1, val);
266+
pos = result.find('?', pos + val.size());
267+
}
268+
269+
return result;
270+
}
271+
272+
private:
273+
static std::string value_to_sql(const database_value& val)
274+
{
275+
return std::visit([](const auto& v) -> std::string {
276+
using T = std::decay_t<decltype(v)>;
277+
if constexpr (std::is_same_v<T, std::nullptr_t>) {
278+
return "NULL";
279+
} else if constexpr (std::is_same_v<T, bool>) {
280+
return v ? "TRUE" : "FALSE";
281+
} else if constexpr (std::is_same_v<T, std::string>) {
282+
// Basic escaping โ€” backends should override for proper security
283+
std::string escaped;
284+
escaped.reserve(v.size() + 2);
285+
escaped += '\'';
286+
for (char c : v) {
287+
if (c == '\'') escaped += "''";
288+
else escaped += c;
289+
}
290+
escaped += '\'';
291+
return escaped;
292+
} else {
293+
return std::to_string(v);
294+
}
295+
}, val);
296+
}
191297
};
192298

193299
/**

โ€Ždocs/README.kr.mdโ€Ž

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,14 @@ category: "GUID"
1616
1717
## ๋ชฉ์ฐจ
1818

19-
- [๐Ÿ“š ๋ฌธ์„œ ๊ฐœ์š”](#-๋ฌธ์„œ-๊ฐœ์š”)
20-
- [๐Ÿ“– ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๋ฌธ์„œ](#-์‚ฌ์šฉ-๊ฐ€๋Šฅํ•œ-๋ฌธ์„œ)
21-
- [๐Ÿš€ ๋น ๋ฅธ ์‹œ์ž‘](#-๋น ๋ฅธ-์‹œ์ž‘)
22-
- [๐Ÿ“‹ ํ”„๋กœ์ ํŠธ ์ •๋ณด](#-ํ”„๋กœ์ ํŠธ-์ •๋ณด)
19+
- [๐Ÿ“š ๋ฌธ์„œ ๊ฐœ์š”](#๋ฌธ์„œ-๊ฐœ์š”)
20+
- [๐Ÿ“– ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๋ฌธ์„œ](#์‚ฌ์šฉ-๊ฐ€๋Šฅํ•œ-๋ฌธ์„œ)
21+
- [๐Ÿš€ ๋น ๋ฅธ ์‹œ์ž‘](#๋น ๋ฅธ-์‹œ์ž‘)
22+
- [๐Ÿ“‹ ํ”„๋กœ์ ํŠธ ์ •๋ณด](#ํ”„๋กœ์ ํŠธ-์ •๋ณด)
2323
- [ํ˜„์žฌ ์ƒํƒœ](#ํ˜„์žฌ-์ƒํƒœ)
2424
- [์ง€์› ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค](#์ง€์›-๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค)
2525
- [์ฃผ์š” ๊ธฐ๋Šฅ](#์ฃผ์š”-๊ธฐ๋Šฅ)
26-
- [๐Ÿ“– ๋ฌธ์„œ ๊ตฌ์กฐ](#-๋ฌธ์„œ-๊ตฌ์กฐ)
26+
- [๐Ÿ“– ๋ฌธ์„œ ๊ตฌ์กฐ](#๋ฌธ์„œ-๊ตฌ์กฐ)
2727
- [ํ•ต์‹ฌ ๋ฌธ์„œ](#ํ•ต์‹ฌ-๋ฌธ์„œ)
2828
- [API Reference](#api-reference)
2929
- [Build Guide](#build-guide)
@@ -32,23 +32,23 @@ category: "GUID"
3232
- [์ถ”๊ฐ€ ๋ฆฌ์†Œ์Šค](#์ถ”๊ฐ€-๋ฆฌ์†Œ์Šค)
3333
- [Changelog](#changelog)
3434
- [Project README](#project-readme)
35-
- [๐ŸŽฏ ์‚ฌ์šฉ ์‚ฌ๋ก€๋ณ„ ๋ฌธ์„œ](#-์‚ฌ์šฉ-์‚ฌ๋ก€๋ณ„-๋ฌธ์„œ)
35+
- [๐ŸŽฏ ์‚ฌ์šฉ ์‚ฌ๋ก€๋ณ„ ๋ฌธ์„œ](#์‚ฌ์šฉ-์‚ฌ๋ก€๋ณ„-๋ฌธ์„œ)
3636
- [์ƒˆ ์‚ฌ์šฉ์ž](#์ƒˆ-์‚ฌ์šฉ์ž)
3737
- [์ˆ™๋ จ๋œ ๊ฐœ๋ฐœ์ž](#์ˆ™๋ จ๋œ-๊ฐœ๋ฐœ์ž)
3838
- [DevOps ๋ฐ ์‹œ์Šคํ…œ ๊ด€๋ฆฌ์ž](#devops-๋ฐ-์‹œ์Šคํ…œ-๊ด€๋ฆฌ์ž)
3939
- [ํ•™์ƒ ๋ฐ ์—ฐ๊ตฌ์ž](#ํ•™์ƒ-๋ฐ-์—ฐ๊ตฌ์ž)
40-
- [๐Ÿ” ์ •๋ณด ์ฐพ๊ธฐ](#-์ •๋ณด-์ฐพ๊ธฐ)
40+
- [๐Ÿ” ์ •๋ณด ์ฐพ๊ธฐ](#์ •๋ณด-์ฐพ๊ธฐ)
4141
- [๊ธฐ๋Šฅ๋ณ„](#๊ธฐ๋Šฅ๋ณ„)
4242
- [๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํƒ€์ž…๋ณ„](#๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค-ํƒ€์ž…๋ณ„)
43-
- [๐Ÿค ๋ฌธ์„œ ๊ธฐ์—ฌ](#-๋ฌธ์„œ-๊ธฐ์—ฌ)
43+
- [๐Ÿค ๋ฌธ์„œ ๊ธฐ์—ฌ](#๋ฌธ์„œ-๊ธฐ์—ฌ)
4444
- [๋ฌธ์„œ ํ‘œ์ค€](#๋ฌธ์„œ-ํ‘œ์ค€)
4545
- [๊ฐœ์„  ์˜์—ญ](#๊ฐœ์„ -์˜์—ญ)
4646
- [์ œ์ถœ ํ”„๋กœ์„ธ์Šค](#์ œ์ถœ-ํ”„๋กœ์„ธ์Šค)
47-
- [๐Ÿ“ž ๋„์›€ ๋ฐ›๊ธฐ](#-๋„์›€-๋ฐ›๊ธฐ)
47+
- [๐Ÿ“ž ๋„์›€ ๋ฐ›๊ธฐ](#๋„์›€-๋ฐ›๊ธฐ)
4848
- [๋ฌธ์„œ ์ด์Šˆ](#๋ฌธ์„œ-์ด์Šˆ)
4949
- [๊ธฐ์ˆ  ์ง€์›](#๊ธฐ์ˆ -์ง€์›)
5050
- [์ง€์› ๋ฆฌ์†Œ์Šค](#์ง€์›-๋ฆฌ์†Œ์Šค)
51-
- [๐Ÿ“… ๋ฌธ์„œ ๋กœ๋“œ๋งต](#-๋ฌธ์„œ-๋กœ๋“œ๋งต)
51+
- [๐Ÿ“… ๋ฌธ์„œ ๋กœ๋“œ๋งต](#๋ฌธ์„œ-๋กœ๋“œ๋งต)
5252
- [ํ˜„์žฌ (v3.0.0)](#ํ˜„์žฌ-v300)
5353
- [ํ–ฅํ›„ ๊ฐœ์„ ์‚ฌํ•ญ](#ํ–ฅํ›„-๊ฐœ์„ ์‚ฌํ•ญ)
5454

@@ -182,44 +182,44 @@ Database System์„ ๋นŒ๋“œํ•˜๊ณ  ๋ฐฐํฌํ•˜๋Š” ๋ฐ ํ•„์š”ํ•œ ๋ชจ๋“  ๊ฒƒ:
182182

183183
**์—ฐ๊ฒฐ ๊ด€๋ฆฌ**
184184
- API: [Database Manager](API_REFERENCE.kr.md#database-manager)
185-
- ์˜ˆ์ œ: [Basic Usage](guides/SAMPLES_GUIDE.kr.md#๊ธฐ๋ณธ-์‚ฌ์šฉ๋ฒ•-์ƒ˜ํ”Œ)
186-
- ๋นŒ๋“œ: [Database Dependencies](guides/BUILD_GUIDE.kr.md#๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค-์˜์กด์„ฑ)
185+
- ์˜ˆ์ œ: [Basic Usage](guides/SAMPLES_GUIDE.kr.md)
186+
- ๋นŒ๋“œ: [Database Dependencies](guides/BUILD_GUIDE.kr.md)
187187

188188
**์—ฐ๊ฒฐ ํ’€๋ง**
189189
- API: [Connection Pooling](API_REFERENCE.kr.md#์—ฐ๊ฒฐ-ํ’€๋ง)
190-
- ์˜ˆ์ œ: [Connection Pool Demo](guides/SAMPLES_GUIDE.kr.md#์—ฐ๊ฒฐ-ํ’€-๋ฐ๋ชจ)
190+
- ์˜ˆ์ œ: [Connection Pool Demo](guides/SAMPLES_GUIDE.kr.md)
191191
- ์„ฑ๋Šฅ: [Pool Performance](BENCHMARKS.kr.md#์—ฐ๊ฒฐ-ํ’€-์„ฑ๋Šฅ)
192192

193193
**์ฟผ๋ฆฌ ๋นŒ๋”ฉ**
194194
- API: [Query Builders](API_REFERENCE.kr.md#์ฟผ๋ฆฌ-๋นŒ๋”)
195-
- ์˜ˆ์ œ: [Query Builder Examples](guides/SAMPLES_GUIDE.kr.md#์ฟผ๋ฆฌ-๋นŒ๋”-์˜ˆ์ œ)
195+
- ์˜ˆ์ œ: [Query Builder Examples](guides/SAMPLES_GUIDE.kr.md)
196196
- ์„ฑ๋Šฅ: [Builder Performance](BENCHMARKS.kr.md#๋ฐฑ์—”๋“œ๋ณ„-์ฟผ๋ฆฌ-์„ฑ๋Šฅ)
197197

198198
**๋‹ค์ค‘ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ง€์›**
199199
- API: [Database Types](API_REFERENCE.kr.md#๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค-ํƒ€์ž…)
200-
- ์˜ˆ์ œ: [Multi-Database Examples](guides/SAMPLES_GUIDE.kr.md#๋‹ค์ค‘-๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค-์˜ˆ์ œ)
201-
- ๋นŒ๋“œ: [Build Configurations](guides/BUILD_GUIDE.kr.md#๋นŒ๋“œ-๊ตฌ์„ฑ)
200+
- ์˜ˆ์ œ: [Multi-Database Examples](guides/SAMPLES_GUIDE.kr.md)
201+
- ๋นŒ๋“œ: [Build Configurations](guides/BUILD_GUIDE.kr.md)
202202

203203
### ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํƒ€์ž…๋ณ„
204204

205205
**PostgreSQL**
206206
- API: [postgres_manager](API_REFERENCE.kr.md#database_base)
207-
- ์˜ˆ์ œ: [PostgreSQL Advanced](guides/SAMPLES_GUIDE.kr.md#postgresql-๊ณ ๊ธ‰-์ƒ˜ํ”Œ)
207+
- ์˜ˆ์ œ: [PostgreSQL Advanced](guides/SAMPLES_GUIDE.kr.md)
208208
- ์„ฑ๋Šฅ: [PostgreSQL Benchmarks](BENCHMARKS.kr.md#postgresql-๋ฒค์น˜๋งˆํฌ)
209209

210210
**SQLite**
211-
- ๋นŒ๋“œ: [SQLite Support](guides/BUILD_GUIDE.kr.md#๋นŒ๋“œ-๊ตฌ์„ฑ)
212-
- ์˜ˆ์ œ: [Local Database Usage](guides/SAMPLES_GUIDE.kr.md#๊ธฐ๋ณธ-์‚ฌ์šฉ๋ฒ•-์ƒ˜ํ”Œ)
211+
- ๋นŒ๋“œ: [SQLite Support](guides/BUILD_GUIDE.kr.md)
212+
- ์˜ˆ์ œ: [Local Database Usage](guides/SAMPLES_GUIDE.kr.md)
213213
- ์„ฑ๋Šฅ: [SQLite Benchmarks](BENCHMARKS.kr.md#sqlite-๋ฒค์น˜๋งˆํฌ)
214214

215215
**MongoDB**
216216
- API: [mongodb_query_builder](API_REFERENCE.kr.md#mongodb_query_builder)
217-
- ์˜ˆ์ œ: [MongoDB Examples](guides/SAMPLES_GUIDE.kr.md#mongodb-์ฟผ๋ฆฌ-๋นŒ๋”-์˜ˆ์ œ)
217+
- ์˜ˆ์ œ: [MongoDB Examples](guides/SAMPLES_GUIDE.kr.md)
218218
- ์„ฑ๋Šฅ: [MongoDB Performance](BENCHMARKS.kr.md#mongodb-๋ฒค์น˜๋งˆํฌ)
219219

220220
**Redis**
221221
- API: [redis_query_builder](API_REFERENCE.kr.md#redis_query_builder)
222-
- ์˜ˆ์ œ: [Redis Examples](guides/SAMPLES_GUIDE.kr.md#redis-์ฟผ๋ฆฌ-๋นŒ๋”-์˜ˆ์ œ)
222+
- ์˜ˆ์ œ: [Redis Examples](guides/SAMPLES_GUIDE.kr.md)
223223
- ์„ฑ๋Šฅ: [Redis Performance](BENCHMARKS.kr.md#redis-๋ฒค์น˜๋งˆํฌ)
224224

225225
## ๐Ÿค ๋ฌธ์„œ ๊ธฐ์—ฌ

0 commit comments

Comments
ย (0)