Skip to main content

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}