1#![allow(clippy::cast_possible_truncation)]
14#![allow(clippy::cast_sign_loss)]
15#![allow(clippy::cast_lossless)]
16#![allow(clippy::checked_conversions)]
17
18use crate::ffi;
19use sqlmodel_core::Value;
20use std::ffi::{CStr, c_int};
21
22pub unsafe fn bind_value(stmt: *mut ffi::sqlite3_stmt, index: c_int, value: &Value) -> c_int {
28 unsafe {
30 match value {
31 Value::Null => ffi::sqlite3_bind_null(stmt, index),
32
33 Value::Bool(b) => ffi::sqlite3_bind_int(stmt, index, if *b { 1 } else { 0 }),
34
35 Value::TinyInt(v) => ffi::sqlite3_bind_int(stmt, index, i32::from(*v)),
36
37 Value::SmallInt(v) => ffi::sqlite3_bind_int(stmt, index, i32::from(*v)),
38
39 Value::Int(v) => ffi::sqlite3_bind_int(stmt, index, *v),
40
41 Value::BigInt(v) => ffi::sqlite3_bind_int64(stmt, index, *v),
42
43 Value::Float(v) => ffi::sqlite3_bind_double(stmt, index, f64::from(*v)),
44
45 Value::Double(v) => ffi::sqlite3_bind_double(stmt, index, *v),
46
47 Value::Decimal(s) => {
48 let bytes = s.as_bytes();
49 ffi::sqlite3_bind_text(
50 stmt,
51 index,
52 bytes.as_ptr().cast(),
53 bytes.len() as c_int,
54 ffi::sqlite_transient(),
55 )
56 }
57
58 Value::Text(s) => {
59 let bytes = s.as_bytes();
60 ffi::sqlite3_bind_text(
61 stmt,
62 index,
63 bytes.as_ptr().cast(),
64 bytes.len() as c_int,
65 ffi::sqlite_transient(),
66 )
67 }
68
69 Value::Bytes(b) => ffi::sqlite3_bind_blob(
70 stmt,
71 index,
72 b.as_ptr().cast(),
73 b.len() as c_int,
74 ffi::sqlite_transient(),
75 ),
76
77 Value::Date(days) => {
79 let date = days_to_date(*days);
80 let bytes = date.as_bytes();
81 ffi::sqlite3_bind_text(
82 stmt,
83 index,
84 bytes.as_ptr().cast(),
85 bytes.len() as c_int,
86 ffi::sqlite_transient(),
87 )
88 }
89
90 Value::Time(micros) => {
92 let time = micros_to_time(*micros);
93 let bytes = time.as_bytes();
94 ffi::sqlite3_bind_text(
95 stmt,
96 index,
97 bytes.as_ptr().cast(),
98 bytes.len() as c_int,
99 ffi::sqlite_transient(),
100 )
101 }
102
103 Value::Timestamp(micros) | Value::TimestampTz(micros) => {
105 let ts = micros_to_timestamp(*micros);
106 let bytes = ts.as_bytes();
107 ffi::sqlite3_bind_text(
108 stmt,
109 index,
110 bytes.as_ptr().cast(),
111 bytes.len() as c_int,
112 ffi::sqlite_transient(),
113 )
114 }
115
116 Value::Uuid(bytes) => ffi::sqlite3_bind_blob(
118 stmt,
119 index,
120 bytes.as_ptr().cast(),
121 16,
122 ffi::sqlite_transient(),
123 ),
124
125 Value::Json(json) => {
127 let s = json.to_string();
128 let bytes = s.as_bytes();
129 ffi::sqlite3_bind_text(
130 stmt,
131 index,
132 bytes.as_ptr().cast(),
133 bytes.len() as c_int,
134 ffi::sqlite_transient(),
135 )
136 }
137
138 Value::Array(arr) => {
140 let json = serde_json::Value::Array(arr.iter().map(value_to_json).collect());
141 let s = json.to_string();
142 let bytes = s.as_bytes();
143 ffi::sqlite3_bind_text(
144 stmt,
145 index,
146 bytes.as_ptr().cast(),
147 bytes.len() as c_int,
148 ffi::sqlite_transient(),
149 )
150 }
151
152 Value::Default => ffi::sqlite3_bind_null(stmt, index),
155 }
156 }
157}
158
159pub unsafe fn read_column(stmt: *mut ffi::sqlite3_stmt, index: c_int) -> Value {
165 unsafe {
167 let col_type = ffi::sqlite3_column_type(stmt, index);
168
169 match col_type {
170 ffi::SQLITE_NULL => Value::Null,
171
172 ffi::SQLITE_INTEGER => {
173 let v = ffi::sqlite3_column_int64(stmt, index);
174 if v >= i32::MIN as i64 && v <= i32::MAX as i64 {
176 Value::Int(v as i32)
177 } else {
178 Value::BigInt(v)
179 }
180 }
181
182 ffi::SQLITE_FLOAT => {
183 let v = ffi::sqlite3_column_double(stmt, index);
184 Value::Double(v)
185 }
186
187 ffi::SQLITE_TEXT => {
188 let ptr = ffi::sqlite3_column_text(stmt, index);
189 let len = ffi::sqlite3_column_bytes(stmt, index);
190 if ptr.is_null() {
191 Value::Null
192 } else {
193 let slice = std::slice::from_raw_parts(ptr.cast::<u8>(), len as usize);
194 let s = String::from_utf8_lossy(slice).into_owned();
195 Value::Text(s)
196 }
197 }
198
199 ffi::SQLITE_BLOB => {
200 let ptr = ffi::sqlite3_column_blob(stmt, index);
201 let len = ffi::sqlite3_column_bytes(stmt, index);
202 if ptr.is_null() || len == 0 {
203 Value::Bytes(Vec::new())
204 } else {
205 let slice = std::slice::from_raw_parts(ptr.cast::<u8>(), len as usize);
206 Value::Bytes(slice.to_vec())
207 }
208 }
209
210 _ => Value::Null,
211 }
212 }
213}
214
215pub unsafe fn column_name(stmt: *mut ffi::sqlite3_stmt, index: c_int) -> Option<String> {
221 unsafe {
223 let ptr = ffi::sqlite3_column_name(stmt, index);
224 if ptr.is_null() {
225 None
226 } else {
227 CStr::from_ptr(ptr).to_str().ok().map(String::from)
228 }
229 }
230}
231
232fn days_to_date(days: i32) -> String {
234 let epoch = 719_528; let total_days = days + epoch;
238
239 let (year, month, day) = days_to_ymd(total_days);
241 format!("{:04}-{:02}-{:02}", year, month, day)
242}
243
244fn days_to_ymd(days: i32) -> (i32, u32, u32) {
246 let mut remaining = days;
248 let mut year = 0i32;
249
250 while remaining >= days_in_year(year) {
252 remaining -= days_in_year(year);
253 year += 1;
254 }
255 while remaining < 0 {
256 year -= 1;
257 remaining += days_in_year(year);
258 }
259
260 let mut month = 1u32;
262 while remaining >= days_in_month(year, month) as i32 {
263 remaining -= days_in_month(year, month) as i32;
264 month += 1;
265 }
266
267 let day = (remaining + 1) as u32;
268 (year, month, day)
269}
270
271fn is_leap_year(year: i32) -> bool {
272 (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)
273}
274
275fn days_in_year(year: i32) -> i32 {
276 if is_leap_year(year) { 366 } else { 365 }
277}
278
279fn days_in_month(year: i32, month: u32) -> u32 {
280 match month {
281 1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
282 4 | 6 | 9 | 11 => 30,
283 2 => {
284 if is_leap_year(year) {
285 29
286 } else {
287 28
288 }
289 }
290 _ => 30,
291 }
292}
293
294fn micros_to_time(micros: i64) -> String {
296 let total_secs = micros / 1_000_000;
297 let hours = (total_secs / 3600) % 24;
298 let minutes = (total_secs / 60) % 60;
299 let seconds = total_secs % 60;
300 let millis = (micros % 1_000_000) / 1000;
301
302 if millis > 0 {
303 format!("{:02}:{:02}:{:02}.{:03}", hours, minutes, seconds, millis)
304 } else {
305 format!("{:02}:{:02}:{:02}", hours, minutes, seconds)
306 }
307}
308
309fn micros_to_timestamp(micros: i64) -> String {
311 let secs = micros / 1_000_000;
312 let days = (secs / 86400) as i32;
313 let time_of_day = (micros % 86_400_000_000).unsigned_abs() as i64;
314
315 let date = days_to_date(days);
316 let time = micros_to_time(time_of_day);
317
318 format!("{}T{}", date, time)
319}
320
321fn value_to_json(value: &Value) -> serde_json::Value {
323 match value {
324 Value::Null => serde_json::Value::Null,
325 Value::Bool(b) => serde_json::Value::Bool(*b),
326 Value::TinyInt(v) => serde_json::Value::Number((*v).into()),
327 Value::SmallInt(v) => serde_json::Value::Number((*v).into()),
328 Value::Int(v) => serde_json::Value::Number((*v).into()),
329 Value::BigInt(v) => serde_json::Value::Number((*v).into()),
330 Value::Float(v) => serde_json::Number::from_f64(f64::from(*v))
331 .map_or(serde_json::Value::Null, serde_json::Value::Number),
332 Value::Double(v) => serde_json::Number::from_f64(*v)
333 .map_or(serde_json::Value::Null, serde_json::Value::Number),
334 Value::Decimal(s) | Value::Text(s) => serde_json::Value::String(s.clone()),
335 Value::Bytes(b) => serde_json::Value::String(base64_encode(b)),
336 Value::Date(d) => serde_json::Value::String(days_to_date(*d)),
337 Value::Time(t) => serde_json::Value::String(micros_to_time(*t)),
338 Value::Timestamp(ts) | Value::TimestampTz(ts) => {
339 serde_json::Value::String(micros_to_timestamp(*ts))
340 }
341 Value::Uuid(bytes) => serde_json::Value::String(uuid_to_string(bytes)),
342 Value::Json(j) => j.clone(),
343 Value::Array(arr) => serde_json::Value::Array(arr.iter().map(value_to_json).collect()),
344 Value::Default => serde_json::Value::Null,
345 }
346}
347
348fn base64_encode(data: &[u8]) -> String {
350 const ALPHABET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
351
352 let mut result = String::new();
353 for chunk in data.chunks(3) {
354 let b0 = chunk[0];
355 let b1 = chunk.get(1).copied().unwrap_or(0);
356 let b2 = chunk.get(2).copied().unwrap_or(0);
357
358 result.push(ALPHABET[(b0 >> 2) as usize] as char);
359 result.push(ALPHABET[(((b0 & 0x03) << 4) | (b1 >> 4)) as usize] as char);
360
361 if chunk.len() > 1 {
362 result.push(ALPHABET[(((b1 & 0x0f) << 2) | (b2 >> 6)) as usize] as char);
363 } else {
364 result.push('=');
365 }
366
367 if chunk.len() > 2 {
368 result.push(ALPHABET[(b2 & 0x3f) as usize] as char);
369 } else {
370 result.push('=');
371 }
372 }
373 result
374}
375
376fn uuid_to_string(bytes: &[u8; 16]) -> String {
378 format!(
379 "{:02x}{:02x}{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}",
380 bytes[0],
381 bytes[1],
382 bytes[2],
383 bytes[3],
384 bytes[4],
385 bytes[5],
386 bytes[6],
387 bytes[7],
388 bytes[8],
389 bytes[9],
390 bytes[10],
391 bytes[11],
392 bytes[12],
393 bytes[13],
394 bytes[14],
395 bytes[15]
396 )
397}
398
399#[cfg(test)]
400mod tests {
401 use super::*;
402
403 #[test]
404 fn test_days_to_date() {
405 assert_eq!(days_to_date(0), "1970-01-01");
407 assert_eq!(days_to_date(1), "1970-01-02");
409 }
410
411 #[test]
412 fn test_micros_to_time() {
413 assert_eq!(micros_to_time(0), "00:00:00");
414 assert_eq!(micros_to_time(3_600_000_000), "01:00:00");
415 assert_eq!(micros_to_time(3_661_123_000), "01:01:01.123");
416 }
417
418 #[test]
419 fn test_base64_encode() {
420 assert_eq!(base64_encode(b""), "");
421 assert_eq!(base64_encode(b"f"), "Zg==");
422 assert_eq!(base64_encode(b"fo"), "Zm8=");
423 assert_eq!(base64_encode(b"foo"), "Zm9v");
424 assert_eq!(base64_encode(b"foobar"), "Zm9vYmFy");
425 }
426
427 #[test]
428 fn test_uuid_to_string() {
429 let uuid = [
430 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
431 0x0f, 0x10,
432 ];
433 assert_eq!(
434 uuid_to_string(&uuid),
435 "01020304-0506-0708-090a-0b0c0d0e0f10"
436 );
437 }
438}