@@ -181,45 +181,179 @@ jobs:
181181 extern void sqlite3_result_int64(sqlite3_context *ctx, long long v);
182182 void sqlite3_result_int(sqlite3_context *ctx, int v) { sqlite3_result_int64(ctx, (long long)v); }
183183
184- // SQLite's own formatting API. Turso doesn't export it, so without a
185- // shim pdo_sqlite falls through to system libsqlite3, where these
186- // functions interact badly with Turso's malloc/free and crash.
187- // This implementation is libc-based and ignores SQLite's %q / %Q
188- // format specifiers (used for SQL-quoting); pdo_sqlite uses the
189- // plain %s family for error message formatting, which is handled.
190- char *sqlite3_vsnprintf(int n, char *dst, const char *fmt, va_list ap) {
191- if (!dst || n <= 0) return dst;
192- vsnprintf(dst, (size_t)n, fmt, ap);
193- return dst;
184+ // SQLite's own formatting API. Turso doesn't export it. A naive
185+ // libc-only wrapper breaks because SQLite defines extra conversion
186+ // specifiers (%q, %Q, %w) that libc vsnprintf doesn't understand —
187+ // in particular, pdo_sqlite's quote() uses sqlite3_mprintf("'%q'",s),
188+ // so without %q support every quoted value becomes garbled. This
189+ // implementation parses the format itself, handles the SQLite
190+ // extensions explicitly, and delegates standard specifiers to libc.
191+ struct buf { char *p; size_t len, cap; };
192+
193+ static int buf_append(struct buf *b, const char *s, size_t n) {
194+ if (b->len + n + 1 > b->cap) {
195+ size_t new_cap = b->cap ? b->cap * 2 : 64;
196+ while (new_cap < b->len + n + 1) new_cap *= 2;
197+ char *np = (char *)realloc(b->p, new_cap);
198+ if (!np) return -1;
199+ b->p = np;
200+ b->cap = new_cap;
201+ }
202+ memcpy(b->p + b->len, s, n);
203+ b->len += n;
204+ b->p[b->len] = '\0';
205+ return 0;
194206 }
195207
196- char *sqlite3_snprintf(int n, char *dst, const char *fmt, ...) {
197- va_list ap;
198- va_start(ap, fmt);
199- sqlite3_vsnprintf(n, dst, fmt, ap);
200- va_end(ap);
201- return dst;
208+ static int buf_append_c(struct buf *b, char c) { return buf_append(b, &c, 1); }
209+
210+ static int buf_append_quoted(struct buf *b, const char *s, char q) {
211+ for (; *s; s++) {
212+ if (*s == q && buf_append_c(b, q) < 0) return -1;
213+ if (buf_append_c(b, *s) < 0) return -1;
214+ }
215+ return 0;
216+ }
217+
218+ // Build a result string by parsing fmt and consuming ap as needed.
219+ static char *vmprintf_impl(const char *fmt, va_list ap) {
220+ struct buf b = {0};
221+
222+ while (*fmt) {
223+ if (*fmt != '%') {
224+ if (buf_append_c(&b, *fmt++) < 0) { free(b.p); return NULL; }
225+ continue;
226+ }
227+ const char *spec_start = fmt;
228+ fmt++;
229+ // Flags, width, precision, length — collected for libc fallback.
230+ while (*fmt && strchr("-+ #0'", *fmt)) fmt++;
231+ while (*fmt == '*') { va_arg(ap, int); fmt++; }
232+ while (*fmt && *fmt >= '0' && *fmt <= '9') fmt++;
233+ if (*fmt == '.') {
234+ fmt++;
235+ if (*fmt == '*') { va_arg(ap, int); fmt++; }
236+ while (*fmt && *fmt >= '0' && *fmt <= '9') fmt++;
237+ }
238+ int is_long = 0, is_long_long = 0;
239+ while (*fmt && strchr("hljztL", *fmt)) {
240+ if (*fmt == 'l') { if (is_long) is_long_long = 1; else is_long = 1; }
241+ if (*fmt == 'j' || *fmt == 'z') is_long_long = 1;
242+ fmt++;
243+ }
244+ char conv = *fmt;
245+ if (!conv) break;
246+ fmt++;
247+
248+ if (conv == 'q' || conv == 'Q' || conv == 'w') {
249+ const char *arg = va_arg(ap, const char *);
250+ if (conv == 'Q' && !arg) {
251+ if (buf_append(&b, "NULL", 4) < 0) { free(b.p); return NULL; }
252+ } else {
253+ char qchar = (conv == 'w') ? '"' : '\'';
254+ if (conv == 'Q' && buf_append_c(&b, qchar) < 0) { free(b.p); return NULL; }
255+ if (arg && buf_append_quoted(&b, arg, qchar) < 0) { free(b.p); return NULL; }
256+ if (conv == 'Q' && buf_append_c(&b, qchar) < 0) { free(b.p); return NULL; }
257+ }
258+ continue;
259+ }
260+
261+ // Standard conversion — rebuild the spec and call libc snprintf
262+ // for the single specifier, then append the result.
263+ size_t speclen = (size_t)(fmt - spec_start);
264+ char spec[64];
265+ if (speclen + 1 > sizeof spec) speclen = sizeof spec - 1;
266+ memcpy(spec, spec_start, speclen);
267+ spec[speclen] = '\0';
268+
269+ char tmp[128];
270+ char *out = tmp;
271+ int n = 0;
272+ if (conv == 's' || conv == 'z') {
273+ const char *a = va_arg(ap, const char *);
274+ n = snprintf(tmp, sizeof tmp, spec, a ? a : "(null)");
275+ if (n >= (int)sizeof tmp) {
276+ out = (char *)malloc((size_t)n + 1);
277+ if (!out) { free(b.p); return NULL; }
278+ snprintf(out, (size_t)n + 1, spec, a ? a : "(null)");
279+ }
280+ if (conv == 'z' && a) free((void *)a);
281+ } else if (conv == 'c') {
282+ int a = va_arg(ap, int);
283+ n = snprintf(tmp, sizeof tmp, spec, a);
284+ } else if (conv == 'd' || conv == 'i' || conv == 'u' || conv == 'x' ||
285+ conv == 'X' || conv == 'o') {
286+ if (is_long_long) {
287+ long long a = va_arg(ap, long long);
288+ n = snprintf(tmp, sizeof tmp, spec, a);
289+ } else if (is_long) {
290+ long a = va_arg(ap, long);
291+ n = snprintf(tmp, sizeof tmp, spec, a);
292+ } else {
293+ int a = va_arg(ap, int);
294+ n = snprintf(tmp, sizeof tmp, spec, a);
295+ }
296+ } else if (conv == 'e' || conv == 'E' || conv == 'f' || conv == 'F' ||
297+ conv == 'g' || conv == 'G') {
298+ double a = va_arg(ap, double);
299+ n = snprintf(tmp, sizeof tmp, spec, a);
300+ } else if (conv == 'p') {
301+ void *a = va_arg(ap, void *);
302+ n = snprintf(tmp, sizeof tmp, spec, a);
303+ } else if (conv == '%') {
304+ tmp[0] = '%'; tmp[1] = '\0'; n = 1;
305+ } else {
306+ // Unknown specifier — pass through literally.
307+ memcpy(tmp, spec_start, speclen);
308+ tmp[speclen] = '\0';
309+ n = (int)speclen;
310+ }
311+
312+ if (n > 0 && buf_append(&b, out, (size_t)n) < 0) {
313+ if (out != tmp) free(out);
314+ free(b.p);
315+ return NULL;
316+ }
317+ if (out != tmp) free(out);
318+ }
319+ if (!b.p) {
320+ b.p = (char *)malloc(1);
321+ if (b.p) b.p[0] = '\0';
322+ }
323+ return b.p;
202324 }
203325
204326 char *sqlite3_vmprintf(const char *fmt, va_list ap) {
205- va_list ap2;
206- va_copy(ap2, ap);
207- int len = vsnprintf(NULL, 0, fmt, ap2);
208- va_end(ap2);
209- if (len < 0) return NULL;
210- char *buf = (char *)malloc((size_t)len + 1);
211- if (!buf) return NULL;
212- vsnprintf(buf, (size_t)len + 1, fmt, ap);
213- return buf;
327+ return vmprintf_impl(fmt, ap);
214328 }
215329
216330 char *sqlite3_mprintf(const char *fmt, ...) {
217331 va_list ap;
218332 va_start(ap, fmt);
219- char *s = sqlite3_vmprintf (fmt, ap);
333+ char *s = vmprintf_impl (fmt, ap);
220334 va_end(ap);
221335 return s;
222336 }
337+
338+ char *sqlite3_vsnprintf(int n, char *dst, const char *fmt, va_list ap) {
339+ if (!dst || n <= 0) return dst;
340+ char *s = vmprintf_impl(fmt, ap);
341+ if (!s) { dst[0] = '\0'; return dst; }
342+ size_t copy = strlen(s);
343+ if (copy > (size_t)(n - 1)) copy = (size_t)(n - 1);
344+ memcpy(dst, s, copy);
345+ dst[copy] = '\0';
346+ free(s);
347+ return dst;
348+ }
349+
350+ char *sqlite3_snprintf(int n, char *dst, const char *fmt, ...) {
351+ va_list ap;
352+ va_start(ap, fmt);
353+ sqlite3_vsnprintf(n, dst, fmt, ap);
354+ va_end(ap);
355+ return dst;
356+ }
223357 C
224358
225359 SHIM=/tmp/libturso-compat-shim.so
0 commit comments