1use crate::graph::{GraphState, KgliteGraph};
9use crate::result::{KgliteCypherResult, ResultState};
10use crate::status::KgliteStatusCode;
11use crate::strings::alloc_c_string;
12use kglite::api::param::json_value_to_kglite_value;
13use kglite::api::session::{execute_mut, execute_read, ExecuteOptions, Session};
14use kglite::api::{Embedder, Value};
15use std::collections::HashMap;
16use std::ffi::{c_char, CStr};
17use std::sync::Arc;
18
19#[repr(C)]
22pub struct KgliteSession {
23 _opaque: [u8; 0],
24 _marker: core::marker::PhantomData<(*mut u8, core::marker::PhantomPinned)>,
25}
26
27pub(crate) struct SessionState {
29 pub(crate) inner: Session,
30 pub(crate) embedder: Option<Arc<dyn Embedder>>,
36}
37
38impl SessionState {
39 fn into_handle(session: Session) -> *mut KgliteSession {
40 let boxed = Box::new(SessionState {
41 inner: session,
42 embedder: None,
43 });
44 Box::into_raw(boxed).cast::<KgliteSession>()
45 }
46
47 pub(crate) unsafe fn from_handle<'a>(handle: *const KgliteSession) -> &'a SessionState {
48 unsafe { &*handle.cast::<SessionState>() }
49 }
50
51 pub(crate) unsafe fn from_handle_mut<'a>(handle: *mut KgliteSession) -> &'a mut SessionState {
52 unsafe { &mut *handle.cast::<SessionState>() }
53 }
54
55 unsafe fn free_handle(handle: *mut KgliteSession) {
56 if handle.is_null() {
57 return;
58 }
59 let _ = unsafe { Box::from_raw(handle.cast::<SessionState>()) };
60 }
61}
62
63#[no_mangle]
87pub unsafe extern "C" fn kglite_session_new(
88 graph: *mut KgliteGraph,
89 out_session: *mut *mut KgliteSession,
90) -> KgliteStatusCode {
91 if graph.is_null() || out_session.is_null() {
92 return KgliteStatusCode::NullPointer;
93 }
94 let graph_state = unsafe { Box::from_raw(graph.cast::<GraphState>()) };
98 let session = Session::from_arc(graph_state.inner);
99 unsafe {
100 *out_session = SessionState::into_handle(session);
101 }
102 KgliteStatusCode::Ok
103}
104
105#[no_mangle]
130pub unsafe extern "C" fn kglite_session_execute_read(
131 session: *const KgliteSession,
132 query: *const c_char,
133 params_json: *const c_char,
134 out_result: *mut *mut KgliteCypherResult,
135 out_error_msg: *mut *const c_char,
136) -> KgliteStatusCode {
137 if session.is_null() || query.is_null() || out_result.is_null() {
138 return KgliteStatusCode::NullPointer;
139 }
140 let query_str = match unsafe { CStr::from_ptr(query) }.to_str() {
141 Ok(s) => s,
142 Err(_) => return KgliteStatusCode::InvalidUtf8,
143 };
144 let params = match parse_params_json(params_json) {
145 Ok(p) => p,
146 Err(rc) => return rc,
147 };
148
149 let session_state = unsafe { SessionState::from_handle(session) };
150 let snapshot = session_state.inner.snapshot();
151 let mut opts = ExecuteOptions::eager(¶ms);
152 opts.embedder = session_state.embedder.clone();
153
154 match execute_read(&snapshot, query_str, &opts) {
155 Ok(outcome) => {
156 unsafe {
157 *out_result = ResultState::into_handle(outcome.result);
158 }
159 if !out_error_msg.is_null() {
160 unsafe {
161 *out_error_msg = std::ptr::null();
162 }
163 }
164 KgliteStatusCode::Ok
165 }
166 Err(err) => {
167 unsafe {
168 *out_result = std::ptr::null_mut();
169 }
170 let code = KgliteStatusCode::from_kg_error_code(err.code());
171 if !out_error_msg.is_null() {
172 unsafe {
173 *out_error_msg = alloc_c_string(&err.to_string());
174 }
175 }
176 code
177 }
178 }
179}
180
181#[no_mangle]
194pub unsafe extern "C" fn kglite_session_execute_mut(
195 session: *mut KgliteSession,
196 query: *const c_char,
197 params_json: *const c_char,
198 out_result: *mut *mut KgliteCypherResult,
199 out_error_msg: *mut *const c_char,
200) -> KgliteStatusCode {
201 if session.is_null() || query.is_null() || out_result.is_null() {
202 return KgliteStatusCode::NullPointer;
203 }
204 let query_str = match unsafe { CStr::from_ptr(query) }.to_str() {
205 Ok(s) => s,
206 Err(_) => return KgliteStatusCode::InvalidUtf8,
207 };
208 let params = match parse_params_json(params_json) {
209 Ok(p) => p,
210 Err(rc) => return rc,
211 };
212
213 let session_state = unsafe { SessionState::from_handle(session) };
218 let mut opts = ExecuteOptions::eager(¶ms);
219 opts.embedder = session_state.embedder.clone();
220
221 let mut tx = session_state.inner.begin();
227 let exec_result = {
228 let working = match tx.working_mut() {
229 Ok(w) => w,
230 Err(err) => {
231 let code = KgliteStatusCode::from_kg_error_code(err.code());
232 if !out_error_msg.is_null() {
233 unsafe {
234 *out_error_msg = alloc_c_string(&err.to_string());
235 }
236 }
237 unsafe {
238 *out_result = std::ptr::null_mut();
239 }
240 return code;
241 }
242 };
243 execute_mut(working, query_str, &opts)
244 };
245
246 match exec_result {
247 Ok(outcome) => {
248 let _ = session_state.inner.commit(tx, false);
253 unsafe {
254 *out_result = ResultState::into_handle(outcome.result);
255 }
256 if !out_error_msg.is_null() {
257 unsafe {
258 *out_error_msg = std::ptr::null();
259 }
260 }
261 KgliteStatusCode::Ok
262 }
263 Err(err) => {
264 unsafe {
267 *out_result = std::ptr::null_mut();
268 }
269 let code = KgliteStatusCode::from_kg_error_code(err.code());
270 if !out_error_msg.is_null() {
271 unsafe {
272 *out_error_msg = alloc_c_string(&err.to_string());
273 }
274 }
275 code
276 }
277 }
278}
279
280#[no_mangle]
287pub unsafe extern "C" fn kglite_session_free(session: *mut KgliteSession) {
288 unsafe { SessionState::free_handle(session) };
289}
290
291fn parse_params_json(
296 params_json: *const c_char,
297) -> Result<HashMap<String, Value>, KgliteStatusCode> {
298 if params_json.is_null() {
299 return Ok(HashMap::new());
300 }
301 let s = match unsafe { CStr::from_ptr(params_json) }.to_str() {
302 Ok(s) => s,
303 Err(_) => return Err(KgliteStatusCode::InvalidUtf8),
304 };
305 if s.is_empty() {
306 return Ok(HashMap::new());
307 }
308 let parsed: serde_json::Value = match serde_json::from_str(s) {
309 Ok(v) => v,
310 Err(_) => return Err(KgliteStatusCode::InvalidArgument),
311 };
312 match parsed {
313 serde_json::Value::Object(obj) => Ok(obj
314 .into_iter()
315 .map(|(k, v)| (k, json_value_to_kglite_value(&v)))
316 .collect()),
317 serde_json::Value::Null => Ok(HashMap::new()),
318 _ => Err(KgliteStatusCode::InvalidArgument),
319 }
320}
321
322#[cfg(test)]
323mod tests {
324 use super::*;
325 use std::ffi::CString;
326
327 #[test]
328 fn parse_params_empty_string_is_empty_map() {
329 let s = CString::new("").unwrap();
330 let m = parse_params_json(s.as_ptr()).unwrap();
331 assert!(m.is_empty());
332 }
333
334 #[test]
335 fn parse_params_object_round_trips() {
336 let s = CString::new(r#"{"x": 42, "y": "hello"}"#).unwrap();
337 let m = parse_params_json(s.as_ptr()).unwrap();
338 assert_eq!(m.get("x"), Some(&Value::Int64(42)));
339 assert_eq!(m.get("y"), Some(&Value::String("hello".to_string())));
340 }
341
342 #[test]
343 fn parse_params_null_pointer_is_empty_map() {
344 let m = parse_params_json(std::ptr::null()).unwrap();
345 assert!(m.is_empty());
346 }
347
348 #[test]
349 fn parse_params_array_is_invalid_argument() {
350 let s = CString::new("[1, 2, 3]").unwrap();
351 let err = parse_params_json(s.as_ptr()).unwrap_err();
352 assert_eq!(err, KgliteStatusCode::InvalidArgument);
353 }
354}