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(
52 sql: *const c_char,
53 dialect: *const c_char,
54) -> *mut c_char {
55 let sql_str = match unsafe { cstr_to_option(sql) } {
56 Some(s) => s,
57 None => return ptr::null_mut(),
58 };
59 let dialect_enum = resolve_dialect(unsafe { cstr_to_option(dialect) });
60
61 match crate::parse(sql_str, dialect_enum) {
62 Ok(ast) => match serde_json::to_string(&ast) {
63 Ok(json) => to_c_string(json),
64 Err(_) => ptr::null_mut(),
65 },
66 Err(_) => ptr::null_mut(),
67 }
68}
69
70/// Transpile a single SQL statement from one dialect to another.
71///
72/// * `sql` – null-terminated SQL string (required).
73/// * `from_dialect` – source dialect name, or `NULL` for ANSI.
74/// * `to_dialect` – target dialect name, or `NULL` for ANSI.
75///
76/// Returns a heap-allocated SQL string on success, or `NULL` on failure.
77/// The caller **must** free a non-null return value with [`sqlglot_free`].
78#[unsafe(no_mangle)]
79pub unsafe extern "C" fn sqlglot_transpile(
80 sql: *const c_char,
81 from_dialect: *const c_char,
82 to_dialect: *const c_char,
83) -> *mut c_char {
84 let sql_str = match unsafe { cstr_to_option(sql) } {
85 Some(s) => s,
86 None => return ptr::null_mut(),
87 };
88 let from = resolve_dialect(unsafe { cstr_to_option(from_dialect) });
89 let to = resolve_dialect(unsafe { cstr_to_option(to_dialect) });
90
91 match crate::transpile(sql_str, from, to) {
92 Ok(result) => to_c_string(result),
93 Err(_) => ptr::null_mut(),
94 }
95}
96
97/// Generate SQL from a JSON-serialised AST for the given dialect.
98///
99/// * `ast_json` – null-terminated JSON string of a serialised `Statement`.
100/// * `dialect` – target dialect name, or `NULL` for ANSI.
101///
102/// Returns a heap-allocated SQL string on success, or `NULL` on failure.
103/// The caller **must** free a non-null return value with [`sqlglot_free`].
104#[unsafe(no_mangle)]
105pub unsafe extern "C" fn sqlglot_generate(
106 ast_json: *const c_char,
107 dialect: *const c_char,
108) -> *mut c_char {
109 let json_str = match unsafe { cstr_to_option(ast_json) } {
110 Some(s) => s,
111 None => return ptr::null_mut(),
112 };
113 let dialect_enum = resolve_dialect(unsafe { cstr_to_option(dialect) });
114
115 match serde_json::from_str::<crate::ast::Statement>(json_str) {
116 Ok(ast) => to_c_string(crate::generate(&ast, dialect_enum)),
117 Err(_) => ptr::null_mut(),
118 }
119}
120
121/// Return the library version as a static null-terminated string.
122///
123/// The returned pointer **must not** be freed — it points to static memory.
124#[unsafe(no_mangle)]
125pub extern "C" fn sqlglot_version() -> *const c_char {
126 // The trailing \0 makes this a valid C string.
127 concat!(env!("CARGO_PKG_VERSION"), "\0").as_ptr() as *const c_char
128}
129
130/// Free a string previously returned by any `sqlglot_*` function.
131///
132/// Passing `NULL` is safe and results in a no-op.
133#[unsafe(no_mangle)]
134pub unsafe extern "C" fn sqlglot_free(ptr: *mut c_char) {
135 if !ptr.is_null() {
136 drop(unsafe { CString::from_raw(ptr) });
137 }
138}