Skip to main content

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, &params).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, &params).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}