ri/c/database.rs
1//! Copyright © 2025-2026 Wenze Wei. All Rights Reserved.
2//!
3//! This file is part of Ri.
4//! The Ri project belongs to the Dunimd Team.
5//!
6//! Licensed under the Apache License, Version 2.0 (the "License");
7//! You may not use this file except in compliance with the License.
8//! You may obtain a copy of the License at
9//!
10//! http://www.apache.org/licenses/LICENSE-2.0
11//!
12//! Unless required by applicable law or agreed to in writing, software
13//! distributed under the License is distributed on an "AS IS" BASIS,
14//! WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15//! See the License for the specific language governing permissions and
16//! limitations under the License.
17
18//! # Database Module C API
19//!
20//! This module provides C language bindings for Ri's database subsystem. The database
21//! module delivers unified database access patterns across multiple database backends,
22//! including PostgreSQL, MySQL, SQLite, and Redis. This C API enables C/C++ applications
23//! to leverage Ri's sophisticated database management capabilities including connection
24//! pooling, transaction management, query building, and result set handling.
25//!
26//! ## Module Architecture
27//!
28//! The database module comprises three primary components that together provide a complete
29//! database access layer:
30//!
31//! - **RiDatabaseConfig**: Configuration container for database connection parameters.
32//! Manages connection strings, pool sizes, timeout settings, and backend-specific options.
33//! The configuration object is required for initializing database pools and controls
34//! resource allocation and behavior characteristics for all database operations.
35//!
36//! - **RiDatabasePool**: Connection pool management interface providing efficient
37//! database connection reuse across multiple concurrent requests. The pool implements
38//! dynamic scaling, health checking, and automatic reconnection for maintaining
39//! reliable database connectivity. Connection pooling significantly improves performance
40//! by avoiding the overhead of establishing new connections for each operation.
41//!
42//! - **RiDBRow**: Result row abstraction providing type-safe access to query results.
43//! The row object supports column-by-column access with automatic type conversion.
44//! Multiple rows are typically returned as a collection that can be iterated efficiently.
45//!
46//! ## Supported Databases
47//!
48//! The database module provides native support for:
49//!
50//! - **PostgreSQL**: Full-featured relational database with advanced data types,
51//! JSON support, and powerful query capabilities. Accessed via sqlx with
52//! async/await support and connection pooling.
53//!
54//! - **MySQL**: Popular relational database with high performance and wide adoption.
55//! Supports replication, partitioning, and stored procedures through sqlx.
56//!
57//! - **SQLite**: Embedded database requiring no separate server process. Ideal for
58//! local storage, testing, and applications with modest concurrency requirements.
59//!
60//! - **Redis**: In-memory data store used for caching, session storage, and
61//! message queuing. Accessed through the Redis protocol with pub/sub support.
62//!
63//! ## Connection Pooling
64//!
65//! The connection pool implementation provides:
66//!
67//! - **Dynamic Sizing**: Pool size adjusts based on demand up to configured maximum.
68//! Idle connections are released when not needed to conserve resources.
69//!
70//! - **Health Checking**: Connections are validated before use to detect stale or
71//! broken connections. Failed connections are automatically recreated.
72//!
73//! - **Connection Lifetime**: Connections have maximum lifetime limits to prevent
74//! resource exhaustion. Long-lived connections are periodically refreshed.
75//!
76//! - **Wait Semantics**: When pool is exhausted, requests can either wait for a
77//! connection or fail immediately based on configuration.
78//!
79//! ## Transaction Management
80//!
81//! Full transaction support includes:
82//!
83//! - **Explicit Transactions**: BEGIN, COMMIT, ROLLBACK control for fine-grained
84//! transaction boundaries.
85//!
86//! - **Savepoints**: Nested transaction support with savepoint rollback capabilities
87//! for partial transaction recovery.
88//!
89//! - **Isolation Levels**: Configurable isolation levels (Read Committed, Repeatable
90//! Read, Serializable) matching database capabilities.
91//!
92//! - **Auto-Commit Mode**: Per-statement execution with automatic commit for
93//! simple operations without transaction overhead.
94//!
95//! ## Query Operations
96//!
97//! The module provides comprehensive query capabilities:
98//!
99//! - **Prepared Statements**: Pre-compiled queries for repeated execution with
100//! parameter binding and automatic type conversion.
101//!
102//! - **Query Builder**: Fluent API for constructing queries programmatically
103//! without raw SQL string manipulation.
104//!
105//! - **Batch Operations**: Efficient bulk inserts and updates with transaction
106//! batching for high-throughput data loading.
107//!
108//! - **Streaming Results**: Large result sets can be streamed row-by-row to
109//! minimize memory footprint for big queries.
110//!
111//! ## Memory Management
112//!
113//! All C API objects use opaque pointers with manual memory management:
114//!
115//! - Constructor functions allocate new instances on the heap
116//! - Destructor functions must be called to release memory
117//! - Result sets must be properly iterated and freed
118//! - Null pointer checks are required before all operations
119//!
120//! ## Thread Safety
121//!
122//! The underlying implementations are thread-safe:
123//!
124//! - Connection pools support concurrent access from multiple threads
125//! - Individual connections are not thread-safe (use pool for concurrency)
126//! - Query execution and result processing require synchronization
127//!
128//! ## Performance Characteristics
129//!
130//! Database operations have the following performance profiles:
131//!
132//! - Connection acquisition: O(1) average case
133//! - Simple query execution: O(log n) for query planning, O(n) for results
134//! - Bulk operations: O(n) with batching optimizations
135//! - Connection reuse: Eliminates ~10ms connection establishment overhead
136//!
137//! ## Error Handling
138//!
139//! Database operations use error codes and optional error messages:
140//!
141//! - Success/failure indication through return values
142//! - Detailed error messages available for debugging
143//! - Connection failures trigger automatic retry (configurable)
144//! - Deadlock detection and transaction restart
145//!
146//! ## Usage Example
147//!
148//! ```c
149//! // Create database configuration
150//! RiDatabaseConfig* config = ri_database_config_new();
151//! ri_database_config_set_connection_string(config, "postgresql://localhost/mydb");
152//! ri_database_config_set_pool_size(config, 10);
153//!
154//! // Create connection pool
155//! RiDatabasePool* pool = ri_database_pool_new(config);
156//!
157//! // Execute query
158//! RiDBRow* row;
159//! int result = ri_database_pool_query(pool, "SELECT * FROM users WHERE id = $1", 1, &row);
160//!
161//! if (result == 0) {
162//! // Process row
163//! char* name = ri_db_row_get_string(row, "name");
164//! int age = ri_db_row_get_int(row, "age");
165//!
166//! // Cleanup row
167//! ri_db_row_free(row);
168//! }
169//!
170//! // Cleanup
171//! ri_database_pool_free(pool);
172//! ri_database_config_free(config);
173//! ```
174//!
175//! ## Dependencies
176//!
177//! This module depends on the following Ri components:
178//!
179//! - `crate::database`: Rust database module implementation
180//! - `crate::prelude`: Common types and traits
181//!
182//! ## Feature Flags
183//!
184//! Database support is enabled through individual feature flags:
185//!
186//! - "postgres": PostgreSQL database support
187//! - "mysql": MySQL database support
188//! - "sqlite": SQLite database support
189//! - Disable features to reduce binary size
190
191use crate::database::{RiDatabase, RiDatabaseConfig, RiDatabasePool, RiDBRow, RiDBResult, DatabaseType};
192
193
194c_wrapper!(CRiDatabaseConfig, RiDatabaseConfig);
195c_wrapper!(CRiDatabasePool, RiDatabasePool);
196c_wrapper!(CRiDBRow, RiDBRow);
197c_wrapper!(CRiDBResult, RiDBResult);
198
199c_constructor!(ri_database_config_new, CRiDatabaseConfig, RiDatabaseConfig, RiDatabaseConfig::default());
200c_destructor!(ri_database_config_free, CRiDatabaseConfig);
201
202// RiDatabaseConfig setters
203c_string_setter!(
204 ri_database_config_set_connection_string,
205 CRiDatabaseConfig,
206 |inner: &mut RiDatabaseConfig, val: &str| { inner.host = val.to_string(); }
207);
208
209#[no_mangle]
210pub extern "C" fn ri_database_config_set_pool_size(config: *mut CRiDatabaseConfig, size: u32) -> std::ffi::c_int {
211 if config.is_null() {
212 return -1;
213 }
214 unsafe {
215 (*config).inner.max_connections = size;
216 }
217 0
218}
219
220#[no_mangle]
221pub extern "C" fn ri_database_config_set_min_idle(config: *mut CRiDatabaseConfig, size: u32) -> std::ffi::c_int {
222 if config.is_null() {
223 return -1;
224 }
225 unsafe {
226 (*config).inner.min_idle_connections = size;
227 }
228 0
229}
230
231#[no_mangle]
232pub extern "C" fn ri_database_config_set_connection_timeout_secs(config: *mut CRiDatabaseConfig, secs: u64) -> std::ffi::c_int {
233 if config.is_null() {
234 return -1;
235 }
236 unsafe {
237 (*config).inner.connection_timeout_secs = secs;
238 }
239 0
240}
241
242#[no_mangle]
243pub extern "C" fn ri_database_config_set_database_type(config: *mut CRiDatabaseConfig, db_type: std::ffi::c_int) -> std::ffi::c_int {
244 if config.is_null() {
245 return -1;
246 }
247 unsafe {
248 let database_type = match db_type {
249 0 => DatabaseType::Postgres,
250 1 => DatabaseType::MySQL,
251 2 => DatabaseType::SQLite,
252 _ => DatabaseType::Postgres,
253 };
254 (*config).inner.database_type = database_type;
255 }
256 0
257}
258
259// RiDatabasePool C bindings
260#[no_mangle]
261pub extern "C" fn ri_database_pool_new(config: *mut CRiDatabaseConfig) -> *mut CRiDatabasePool {
262 if config.is_null() {
263 return std::ptr::null_mut();
264 }
265 let rt = match tokio::runtime::Runtime::new() {
266 Ok(rt) => rt,
267 Err(_) => return std::ptr::null_mut(),
268 };
269 unsafe {
270 let config = (*config).inner.clone();
271 match rt.block_on(async { RiDatabasePool::new(config).await }) {
272 Ok(pool) => {
273 let ptr = Box::into_raw(Box::new(CRiDatabasePool::new(pool)));
274 crate::c::register_ptr(ptr as usize);
275 ptr
276 }
277 Err(_) => std::ptr::null_mut(),
278 }
279 }
280}
281c_destructor!(ri_database_pool_free, CRiDatabasePool);
282
283#[no_mangle]
284pub extern "C" fn ri_database_pool_get_connection_count(pool: *mut CRiDatabasePool) -> usize {
285 if pool.is_null() {
286 return 0;
287 }
288 unsafe { (*pool).inner.metrics().total_connections as usize }
289}
290
291#[no_mangle]
292pub extern "C" fn ri_database_pool_get_idle_count(pool: *mut CRiDatabasePool) -> usize {
293 if pool.is_null() {
294 return 0;
295 }
296 unsafe { (*pool).inner.metrics().idle_connections as usize }
297}
298
299#[no_mangle]
300pub extern "C" fn ri_database_pool_get_active_count(pool: *mut CRiDatabasePool) -> usize {
301 if pool.is_null() {
302 return 0;
303 }
304 unsafe { (*pool).inner.metrics().active_connections as usize }
305}
306
307#[no_mangle]
308pub extern "C" fn ri_database_pool_execute(
309 pool: *mut CRiDatabasePool,
310 sql: *const std::ffi::c_char,
311 out_rows_affected: *mut u64,
312) -> std::ffi::c_int {
313 if pool.is_null() || sql.is_null() {
314 return -1;
315 }
316 let rt = match tokio::runtime::Runtime::new() {
317 Ok(rt) => rt,
318 Err(_) => return -2,
319 };
320 unsafe {
321 let sql_str = match std::ffi::CStr::from_ptr(sql).to_str() {
322 Ok(s) => s,
323 Err(_) => return -3,
324 };
325 match rt.block_on(async { (*pool).inner.get().await }) {
326 Ok(db) => match rt.block_on(async { db.execute(sql_str).await }) {
327 Ok(rows) => {
328 if !out_rows_affected.is_null() {
329 *out_rows_affected = rows;
330 }
331 0
332 }
333 Err(_) => -4,
334 },
335 Err(_) => -5,
336 }
337 }
338}
339
340#[no_mangle]
341pub extern "C" fn ri_database_pool_query(
342 pool: *mut CRiDatabasePool,
343 sql: *const std::ffi::c_char,
344 out_result: *mut *mut CRiDBResult,
345) -> std::ffi::c_int {
346 if pool.is_null() || sql.is_null() || out_result.is_null() {
347 return -1;
348 }
349 let rt = match tokio::runtime::Runtime::new() {
350 Ok(rt) => rt,
351 Err(_) => return -2,
352 };
353 unsafe {
354 let sql_str = match std::ffi::CStr::from_ptr(sql).to_str() {
355 Ok(s) => s,
356 Err(_) => return -3,
357 };
358 match rt.block_on(async { (*pool).inner.get().await }) {
359 Ok(db) => match rt.block_on(async { db.query(sql_str).await }) {
360 Ok(result) => {
361 let result_ptr = Box::into_raw(Box::new(CRiDBResult::new(result)));
362 crate::c::register_ptr(result_ptr as usize);
363 *out_result = result_ptr;
364 0
365 }
366 Err(_) => -4,
367 },
368 Err(_) => -5,
369 }
370 }
371}
372
373/// Execute parameterized SQL query (SAFE - prevents SQL injection)
374///
375/// # Safety
376/// - `pool` must be a valid pointer returned by `ri_database_pool_new`
377/// - `sql` must be a valid null-terminated C string
378/// - `param_count` must match the number of parameters in the SQL
379/// - `param_types` must be an array of `param_count` integers representing parameter types:
380/// - 0: NULL
381/// - 1: INTEGER (i64)
382/// - 2: REAL (f64)
383/// - 3: TEXT (const char*)
384/// - 4: BLOB (const uint8_t*, size_t)
385/// - `param_values` must be an array of `param_count` pointers to parameter values
386/// - `param_sizes` must be an array of `param_count` sizes (for BLOBs, 0 for others)
387///
388/// # SQL Injection Prevention
389/// This function uses parameterized queries, which are the recommended way to prevent SQL injection.
390/// Parameters are properly escaped and quoted by the database driver.
391///
392/// # Example
393/// ```c
394/// // SAFE: Parameterized query
395/// int types[] = {1, 3}; // INTEGER, TEXT
396/// int64_t user_id = 123;
397/// const char* username = "john";
398/// void* values[] = {&user_id, username};
399/// size_t sizes[] = {0, 0};
400///
401/// CRiDBResult* result;
402/// int ret = ri_database_pool_query_params(pool,
403/// "SELECT * FROM users WHERE id = $1 AND name = $2",
404/// 2, types, values, sizes, &result);
405/// ```
406/// Count the number of parameter placeholders ($1, $2, etc.) in SQL string
407/// Returns the count of placeholders found
408fn count_sql_placeholders(sql: &str) -> usize {
409 let mut count = 0;
410 let mut chars = sql.chars().peekable();
411
412 while let Some(c) = chars.next() {
413 if c == '$' {
414 // Check for numbered placeholder like $1, $2, etc.
415 let mut num_str = String::new();
416 while let Some(&next_c) = chars.peek() {
417 if next_c.is_ascii_digit() {
418 num_str.push(chars.next().unwrap());
419 } else {
420 break;
421 }
422 }
423 if !num_str.is_empty() {
424 count += 1;
425 }
426 } else if c == '?' {
427 // Anonymous placeholder (PostgreSQL also supports this)
428 count += 1;
429 } else if c == ':' {
430 // Named placeholder like :1, :name
431 let mut name_or_num = String::new();
432 while let Some(&next_c) = chars.peek() {
433 if next_c.is_alphanumeric() || next_c == '_' {
434 name_or_num.push(chars.next().unwrap());
435 } else {
436 break;
437 }
438 }
439 if !name_or_num.is_empty() {
440 count += 1;
441 }
442 }
443 }
444
445 count
446}
447
448/// Validate parameter types and values for security
449/// Returns Some(error_code) if invalid, None if valid
450fn validate_param_value(param_type: std::ffi::c_int, param_value: *const std::ffi::c_void, param_size: usize) -> Option<std::ffi::c_int> {
451 match param_type {
452 0 => None, // NULL - always valid
453 1 => { // INTEGER
454 if param_value.is_null() {
455 return Some(-10); // NULL value for non-nullable type
456 }
457 // Just check pointer is valid for i64 read
458 let _ = unsafe { *(param_value as *const i64) };
459 None
460 }
461 2 => { // REAL
462 if param_value.is_null() {
463 return Some(-10);
464 }
465 let _ = unsafe { *(param_value as *const f64) };
466 None
467 }
468 3 => { // TEXT
469 if param_value.is_null() {
470 return Some(-10);
471 }
472 let cstr = unsafe { std::ffi::CStr::from_ptr(param_value as *const std::ffi::c_char) };
473 match cstr.to_str() {
474 Ok(_) => None,
475 Err(_) => Some(-7), // Invalid UTF-8
476 }
477 }
478 4 => { // BLOB
479 if param_value.is_null() || param_size == 0 {
480 return Some(-11); // BLOB requires non-null pointer and size > 0
481 }
482 // Verify memory is readable
483 let _ = unsafe { std::slice::from_raw_parts(param_value as *const u8, param_size) };
484 None
485 }
486 _ => Some(-8), // Unknown type
487 }
488}
489
490#[no_mangle]
491pub extern "C" fn ri_database_pool_query_params(
492 pool: *mut CRiDatabasePool,
493 sql: *const std::ffi::c_char,
494 param_count: usize,
495 param_types: *const std::ffi::c_int,
496 param_values: *const *const std::ffi::c_void,
497 param_sizes: *const usize,
498 out_result: *mut *mut CRiDBResult,
499) -> std::ffi::c_int {
500 if pool.is_null() || sql.is_null() || out_result.is_null() {
501 return -1;
502 }
503
504 if param_count > 0 && (param_types.is_null() || param_values.is_null()) {
505 return -6; // Invalid parameters
506 }
507
508 let rt = match tokio::runtime::Runtime::new() {
509 Ok(rt) => rt,
510 Err(_) => return -2,
511 };
512
513 unsafe {
514 let sql_str = match std::ffi::CStr::from_ptr(sql).to_str() {
515 Ok(s) => s,
516 Err(_) => return -3,
517 };
518
519 // Validate SQL placeholder count matches parameter count
520 let placeholder_count = count_sql_placeholders(sql_str);
521 if placeholder_count != param_count {
522 return -9; // Parameter count mismatch
523 }
524
525 let mut params: Vec<serde_json::Value> = Vec::with_capacity(param_count);
526
527 for i in 0..param_count {
528 let param_type = *param_types.add(i);
529 let param_value = *param_values.add(i);
530 let param_size = *param_sizes.add(i);
531
532 // Validate parameter value security
533 if let Some(err_code) = validate_param_value(param_type, param_value, param_size) {
534 return err_code;
535 }
536
537 match param_type {
538 0 => { params.push(serde_json::Value::Null); }
539 1 => {
540 let val = *(param_value as *const i64);
541 params.push(serde_json::json!(val));
542 }
543 2 => {
544 let val = *(param_value as *const f64);
545 params.push(serde_json::json!(val));
546 }
547 3 => {
548 let val = std::ffi::CStr::from_ptr(param_value as *const std::ffi::c_char);
549 match val.to_str() {
550 Ok(s) => params.push(serde_json::json!(s)),
551 Err(_) => return -7,
552 }
553 }
554 4 => {
555 let data = std::slice::from_raw_parts(param_value as *const u8, param_size);
556 params.push(serde_json::json!(data));
557 }
558 _ => return -8,
559 }
560 }
561
562 match rt.block_on(async { (*pool).inner.get().await }) {
563 Ok(db) => match rt.block_on(async { db.query_with_params(sql_str, ¶ms).await }) {
564 Ok(result) => {
565 let result_ptr = Box::into_raw(Box::new(CRiDBResult::new(result)));
566 crate::c::register_ptr(result_ptr as usize);
567 *out_result = result_ptr;
568 0
569 }
570 Err(_) => -4,
571 },
572 Err(_) => -5,
573 }
574 }
575}
576
577/// Execute parameterized SQL statement (SAFE - prevents SQL injection)
578///
579/// # Safety
580/// Same as `ri_database_pool_query_params`
581///
582/// # SQL Injection Prevention
583/// This function uses parameterized statements, which are the recommended way to prevent SQL injection.
584#[no_mangle]
585pub extern "C" fn ri_database_pool_execute_params(
586 pool: *mut CRiDatabasePool,
587 sql: *const std::ffi::c_char,
588 param_count: usize,
589 param_types: *const std::ffi::c_int,
590 param_values: *const *const std::ffi::c_void,
591 param_sizes: *const usize,
592 out_rows_affected: *mut u64,
593) -> std::ffi::c_int {
594 if pool.is_null() || sql.is_null() {
595 return -1;
596 }
597
598 if param_count > 0 && (param_types.is_null() || param_values.is_null()) {
599 return -6;
600 }
601
602 let rt = match tokio::runtime::Runtime::new() {
603 Ok(rt) => rt,
604 Err(_) => return -2,
605 };
606
607 unsafe {
608 let sql_str = match std::ffi::CStr::from_ptr(sql).to_str() {
609 Ok(s) => s,
610 Err(_) => return -3,
611 };
612
613 // Validate SQL placeholder count matches parameter count
614 let placeholder_count = count_sql_placeholders(sql_str);
615 if placeholder_count != param_count {
616 return -9; // Parameter count mismatch
617 }
618
619 let mut params: Vec<serde_json::Value> = Vec::with_capacity(param_count);
620
621 for i in 0..param_count {
622 let param_type = *param_types.add(i);
623 let param_value = *param_values.add(i);
624 let param_size = *param_sizes.add(i);
625
626 // Validate parameter value security
627 if let Some(err_code) = validate_param_value(param_type, param_value, param_size) {
628 return err_code;
629 }
630
631 match param_type {
632 0 => { params.push(serde_json::Value::Null); }
633 1 => {
634 let val = *(param_value as *const i64);
635 params.push(serde_json::json!(val));
636 }
637 2 => {
638 let val = *(param_value as *const f64);
639 params.push(serde_json::json!(val));
640 }
641 3 => {
642 let val = std::ffi::CStr::from_ptr(param_value as *const std::ffi::c_char);
643 match val.to_str() {
644 Ok(s) => params.push(serde_json::json!(s)),
645 Err(_) => return -7,
646 }
647 }
648 4 => {
649 let data = std::slice::from_raw_parts(param_value as *const u8, param_size);
650 params.push(serde_json::json!(data));
651 }
652 _ => return -8,
653 }
654 }
655
656 match rt.block_on(async { (*pool).inner.get().await }) {
657 Ok(db) => match rt.block_on(async { db.execute_with_params(sql_str, ¶ms).await }) {
658 Ok(rows) => {
659 if !out_rows_affected.is_null() {
660 *out_rows_affected = rows;
661 }
662 0
663 }
664 Err(_) => -4,
665 },
666 Err(_) => -5,
667 }
668 }
669}
670
671#[no_mangle]
672pub extern "C" fn ri_database_pool_ping(pool: *mut CRiDatabasePool) -> std::ffi::c_int {
673 if pool.is_null() {
674 return -1;
675 }
676 let rt = match tokio::runtime::Runtime::new() {
677 Ok(rt) => rt,
678 Err(_) => return -2,
679 };
680 unsafe {
681 match rt.block_on(async { (*pool).inner.get().await }) {
682 Ok(db) => match rt.block_on(async { db.ping().await }) {
683 Ok(true) => 0,
684 Ok(false) => 1,
685 Err(_) => -3,
686 },
687 Err(_) => -4,
688 }
689 }
690}
691
692// RiDBRow C bindings
693c_destructor!(ri_db_row_free, CRiDBRow);
694
695#[no_mangle]
696pub extern "C" fn ri_db_row_get_string(
697 row: *mut CRiDBRow,
698 column: *const std::ffi::c_char,
699) -> *mut std::ffi::c_char {
700 if row.is_null() || column.is_null() {
701 return std::ptr::null_mut();
702 }
703 unsafe {
704 let column_str = match std::ffi::CStr::from_ptr(column).to_str() {
705 Ok(s) => s,
706 Err(_) => return std::ptr::null_mut(),
707 };
708 match (*row).inner.get::<String>(column_str) {
709 Some(val) => match std::ffi::CString::new(val) {
710 Ok(c_str) => c_str.into_raw(),
711 Err(_) => std::ptr::null_mut(),
712 },
713 None => std::ptr::null_mut(),
714 }
715 }
716}
717
718#[no_mangle]
719pub extern "C" fn ri_db_row_get_int(row: *mut CRiDBRow, column: *const std::ffi::c_char) -> std::ffi::c_int {
720 if row.is_null() || column.is_null() {
721 return 0;
722 }
723 unsafe {
724 let column_str = match std::ffi::CStr::from_ptr(column).to_str() {
725 Ok(s) => s,
726 Err(_) => return 0,
727 };
728 match (*row).inner.get::<i32>(column_str) {
729 Some(val) => val,
730 None => 0,
731 }
732 }
733}
734
735#[no_mangle]
736pub extern "C" fn ri_db_row_get_long(row: *mut CRiDBRow, column: *const std::ffi::c_char) -> i64 {
737 if row.is_null() || column.is_null() {
738 return 0;
739 }
740 unsafe {
741 let column_str = match std::ffi::CStr::from_ptr(column).to_str() {
742 Ok(s) => s,
743 Err(_) => return 0,
744 };
745 match (*row).inner.get::<i64>(column_str) {
746 Some(val) => val,
747 None => 0,
748 }
749 }
750}
751
752#[no_mangle]
753pub extern "C" fn ri_db_row_get_double(row: *mut CRiDBRow, column: *const std::ffi::c_char) -> f64 {
754 if row.is_null() || column.is_null() {
755 return 0.0;
756 }
757 unsafe {
758 let column_str = match std::ffi::CStr::from_ptr(column).to_str() {
759 Ok(s) => s,
760 Err(_) => return 0.0,
761 };
762 match (*row).inner.get::<f64>(column_str) {
763 Some(val) => val,
764 None => 0.0,
765 }
766 }
767}
768
769#[no_mangle]
770pub extern "C" fn ri_db_row_get_bool(row: *mut CRiDBRow, column: *const std::ffi::c_char) -> bool {
771 if row.is_null() || column.is_null() {
772 return false;
773 }
774 unsafe {
775 let column_str = match std::ffi::CStr::from_ptr(column).to_str() {
776 Ok(s) => s,
777 Err(_) => return false,
778 };
779 match (*row).inner.get::<bool>(column_str) {
780 Some(val) => val,
781 None => false,
782 }
783 }
784}
785
786// RiDBResult C bindings
787c_destructor!(ri_db_result_free, CRiDBResult);
788
789#[no_mangle]
790pub extern "C" fn ri_db_result_get_row_count(result: *mut CRiDBResult) -> usize {
791 if result.is_null() {
792 return 0;
793 }
794 unsafe { (*result).inner.len() }
795}
796
797#[no_mangle]
798pub extern "C" fn ri_db_result_get_row(result: *mut CRiDBResult, index: usize) -> *mut CRiDBRow {
799 if result.is_null() {
800 return std::ptr::null_mut();
801 }
802 unsafe {
803 match (*result).inner.get(index) {
804 Some(row) => Box::into_raw(Box::new(CRiDBRow::new(row.clone()))),
805 None => std::ptr::null_mut(),
806 }
807 }
808}
809
810#[no_mangle]
811pub extern "C" fn ri_db_result_is_empty(result: *mut CRiDBResult) -> bool {
812 if result.is_null() {
813 return true;
814 }
815 unsafe { (*result).inner.is_empty() }
816}