sqlglot_rust/ffi.rs
1//! C FFI bindings for sqlglot-rust.
2//!
3//! This module exposes a C-compatible API so the library can be consumed
4//! from C, C++, or any language that supports the C ABI.
5//!
6//! # Memory management
7//!
8//! Every `*mut c_char` returned by a function in this module **must** be freed
9//! by calling [`sqlglot_free`]. Failing to do so will leak memory.
10
11use std::ffi::{CStr, CString};
12use std::os::raw::c_char;
13use std::ptr;
14
15use crate::dialects::Dialect;
16
17// ── helpers ──────────────────────────────────────────────────────────────
18
19/// Convert a nullable C string to an `Option<&str>`.
20/// Returns `None` when the pointer is null or the bytes are not valid UTF-8.
21unsafe fn cstr_to_option(p: *const c_char) -> Option<&'static str> {
22 if p.is_null() {
23 return None;
24 }
25 unsafe { CStr::from_ptr(p) }.to_str().ok()
26}
27
28/// Resolve a C dialect string to a `Dialect` enum, falling back to `Ansi`.
29fn resolve_dialect(name: Option<&str>) -> Dialect {
30 name.and_then(Dialect::from_str).unwrap_or(Dialect::Ansi)
31}
32
33/// Return a C-owned string, or null on encoding failure.
34fn to_c_string(s: String) -> *mut c_char {
35 CString::new(s)
36 .map(CString::into_raw)
37 .unwrap_or(ptr::null_mut())
38}
39
40// ── public C API ─────────────────────────────────────────────────────────
41
42/// Parse a SQL string and return its AST serialised as JSON.
43///
44/// * `sql` – null-terminated SQL string (required).
45/// * `dialect` – null-terminated dialect name, e.g. `"postgres"`. Pass `NULL`
46/// for ANSI SQL.
47///
48/// Returns a heap-allocated JSON string on success, or `NULL` on failure.
49/// The caller **must** free a non-null return value with [`sqlglot_free`].
50#[unsafe(no_mangle)]
51pub unsafe extern "C" fn sqlglot_parse(sql: *const c_char, dialect: *const c_char) -> *mut c_char {
52 let sql_str = match unsafe { cstr_to_option(sql) } {
53 Some(s) => s,
54 None => return ptr::null_mut(),
55 };
56 let dialect_enum = resolve_dialect(unsafe { cstr_to_option(dialect) });
57
58 match crate::parse(sql_str, dialect_enum) {
59 Ok(ast) => match serde_json::to_string(&ast) {
60 Ok(json) => to_c_string(json),
61 Err(_) => ptr::null_mut(),
62 },
63 Err(_) => ptr::null_mut(),
64 }
65}
66
67/// Transpile a single SQL statement from one dialect to another.
68///
69/// * `sql` – null-terminated SQL string (required).
70/// * `from_dialect` – source dialect name, or `NULL` for ANSI.
71/// * `to_dialect` – target dialect name, or `NULL` for ANSI.
72///
73/// Returns a heap-allocated SQL string on success, or `NULL` on failure.
74/// The caller **must** free a non-null return value with [`sqlglot_free`].
75#[unsafe(no_mangle)]
76pub unsafe extern "C" fn sqlglot_transpile(
77 sql: *const c_char,
78 from_dialect: *const c_char,
79 to_dialect: *const c_char,
80) -> *mut c_char {
81 let sql_str = match unsafe { cstr_to_option(sql) } {
82 Some(s) => s,
83 None => return ptr::null_mut(),
84 };
85 let from = resolve_dialect(unsafe { cstr_to_option(from_dialect) });
86 let to = resolve_dialect(unsafe { cstr_to_option(to_dialect) });
87
88 match crate::transpile(sql_str, from, to) {
89 Ok(result) => to_c_string(result),
90 Err(_) => ptr::null_mut(),
91 }
92}
93
94/// Generate SQL from a JSON-serialised AST for the given dialect.
95///
96/// * `ast_json` – null-terminated JSON string of a serialised `Statement`.
97/// * `dialect` – target dialect name, or `NULL` for ANSI.
98///
99/// Returns a heap-allocated SQL string on success, or `NULL` on failure.
100/// The caller **must** free a non-null return value with [`sqlglot_free`].
101#[unsafe(no_mangle)]
102pub unsafe extern "C" fn sqlglot_generate(
103 ast_json: *const c_char,
104 dialect: *const c_char,
105) -> *mut c_char {
106 let json_str = match unsafe { cstr_to_option(ast_json) } {
107 Some(s) => s,
108 None => return ptr::null_mut(),
109 };
110 let dialect_enum = resolve_dialect(unsafe { cstr_to_option(dialect) });
111
112 match serde_json::from_str::<crate::ast::Statement>(json_str) {
113 Ok(ast) => to_c_string(crate::generate(&ast, dialect_enum)),
114 Err(_) => ptr::null_mut(),
115 }
116}
117
118/// Return the library version as a static null-terminated string.
119///
120/// The returned pointer **must not** be freed — it points to static memory.
121#[unsafe(no_mangle)]
122pub extern "C" fn sqlglot_version() -> *const c_char {
123 // The trailing \0 makes this a valid C string.
124 concat!(env!("CARGO_PKG_VERSION"), "\0").as_ptr() as *const c_char
125}
126
127/// Free a string previously returned by any `sqlglot_*` function.
128///
129/// Passing `NULL` is safe and results in a no-op.
130#[unsafe(no_mangle)]
131pub unsafe extern "C" fn sqlglot_free(ptr: *mut c_char) {
132 if !ptr.is_null() {
133 drop(unsafe { CString::from_raw(ptr) });
134 }
135}