Skip to content

Commit c866b26

Browse files
committed
Start porting Rust code back to C - Phase 1
This commit begins reverting the Rust rewrite back to clean C code. The Rust FFI overhead, memory allocator hacks, and complexity are being replaced with straightforward SQLite C extension code. Completed in this phase: 1. **triggers.c/h** (98 Rust lines → 182 C lines) - Port trigger SQL generation for INSERT/UPDATE/DELETE - Clean string formatting with sqlite3_mprintf - No more format! macro overhead 2. **str_util.c/h** (New utility module) - String escaping for SQL identifiers and values - Identifier list building (e.g., "col1", "col2") - Simple C string manipulation, no String allocations 3. **bootstrap.c/h** (235 Rust lines → 349 C lines) - UUID v4 generation using sqlite3_randomness - Site ID initialization and persistence - Database version migration logic - Clock table creation - No Result<T> wrappers, just SQLITE_OK/ERROR 4. **tableinfo.h** (Data structures) - Define crsql_ColumnInfo and crsql_TableInfo structs - Foundation for table metadata management 5. **consts.h** (Updated) - Add version constants (CRSQLITE_VERSION, etc.) - Fix TBL_SITE_ID to match current schema All code is pure C with no FFI overhead. Memory management uses sqlite3_malloc/sqlite3_free. Error handling uses standard SQLite codes. Next phases will port: db_version, tableinfo, changes_vtab, local_writes, and fractional indexing modules. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent 01aad51 commit c866b26

File tree

8 files changed

+745
-2
lines changed

8 files changed

+745
-2
lines changed

core/src/bootstrap.c

Lines changed: 364 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,364 @@
1+
#include "bootstrap.h"
2+
#include "consts.h"
3+
#include "str_util.h"
4+
#include <string.h>
5+
6+
// Helper to check if a table exists
7+
static int has_table(sqlite3 *db, const char *table_name, int *exists) {
8+
sqlite3_stmt *stmt = NULL;
9+
int rc = sqlite3_prepare_v2(db,
10+
"SELECT 1 FROM sqlite_master WHERE type='table' AND tbl_name=?",
11+
-1, &stmt, NULL);
12+
13+
if (rc != SQLITE_OK) return rc;
14+
15+
rc = sqlite3_bind_text(stmt, 1, table_name, -1, SQLITE_STATIC);
16+
if (rc != SQLITE_OK) {
17+
sqlite3_finalize(stmt);
18+
return rc;
19+
}
20+
21+
rc = sqlite3_step(stmt);
22+
*exists = (rc == SQLITE_ROW);
23+
sqlite3_finalize(stmt);
24+
25+
return (rc == SQLITE_ROW || rc == SQLITE_DONE) ? SQLITE_OK : rc;
26+
}
27+
28+
// Generate UUID v4
29+
void crsql_gen_uuid(unsigned char *blob) {
30+
sqlite3_randomness(16, blob);
31+
// Set version bits (v4)
32+
blob[6] = (blob[6] & 0x0f) | 0x40;
33+
// Set variant bits (RFC 4122)
34+
blob[8] = (blob[8] & 0x3f) | 0x80;
35+
}
36+
37+
// Insert a new site_id
38+
static int insert_site_id(sqlite3 *db, unsigned char *site_id) {
39+
sqlite3_stmt *stmt = NULL;
40+
char *sql = sqlite3_mprintf("INSERT INTO \"%w\" (site_id, ordinal) VALUES (?, 0)", TBL_SITE_ID);
41+
if (!sql) return SQLITE_NOMEM;
42+
43+
int rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
44+
sqlite3_free(sql);
45+
if (rc != SQLITE_OK) return rc;
46+
47+
crsql_gen_uuid(site_id);
48+
rc = sqlite3_bind_blob(stmt, 1, site_id, SITE_ID_LEN, SQLITE_STATIC);
49+
if (rc != SQLITE_OK) {
50+
sqlite3_finalize(stmt);
51+
return rc;
52+
}
53+
54+
rc = sqlite3_step(stmt);
55+
sqlite3_finalize(stmt);
56+
57+
return (rc == SQLITE_DONE) ? SQLITE_OK : rc;
58+
}
59+
60+
// Create site_id table and generate site_id
61+
static int create_site_id_and_site_id_table(sqlite3 *db, unsigned char *site_id) {
62+
char *sql = sqlite3_mprintf(
63+
"CREATE TABLE \"%w\" (site_id BLOB NOT NULL, ordinal INTEGER PRIMARY KEY);"
64+
"CREATE UNIQUE INDEX %w_site_id ON \"%w\" (site_id);",
65+
TBL_SITE_ID, TBL_SITE_ID, TBL_SITE_ID
66+
);
67+
if (!sql) return SQLITE_NOMEM;
68+
69+
char *err = NULL;
70+
int rc = sqlite3_exec(db, sql, NULL, NULL, &err);
71+
sqlite3_free(sql);
72+
73+
if (rc != SQLITE_OK) {
74+
if (err) sqlite3_free(err);
75+
return rc;
76+
}
77+
78+
return insert_site_id(db, site_id);
79+
}
80+
81+
// Initialize or load site_id
82+
int crsql_init_site_id(sqlite3 *db, unsigned char *ret) {
83+
int exists = 0;
84+
int rc = has_table(db, TBL_SITE_ID, &exists);
85+
if (rc != SQLITE_OK) return rc;
86+
87+
if (!exists) {
88+
return create_site_id_and_site_id_table(db, ret);
89+
}
90+
91+
// Table exists, try to load site_id
92+
sqlite3_stmt *stmt = NULL;
93+
char *sql = sqlite3_mprintf("SELECT site_id FROM \"%w\" WHERE ordinal = 0", TBL_SITE_ID);
94+
if (!sql) return SQLITE_NOMEM;
95+
96+
rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
97+
sqlite3_free(sql);
98+
if (rc != SQLITE_OK) return rc;
99+
100+
rc = sqlite3_step(stmt);
101+
102+
if (rc == SQLITE_DONE) {
103+
// No site_id exists, create one
104+
sqlite3_finalize(stmt);
105+
return insert_site_id(db, ret);
106+
} else if (rc == SQLITE_ROW) {
107+
// Load existing site_id
108+
const void *blob = sqlite3_column_blob(stmt, 0);
109+
int blob_len = sqlite3_column_bytes(stmt, 0);
110+
111+
if (blob_len == SITE_ID_LEN) {
112+
memcpy(ret, blob, SITE_ID_LEN);
113+
sqlite3_finalize(stmt);
114+
return SQLITE_OK;
115+
} else {
116+
sqlite3_finalize(stmt);
117+
return SQLITE_ERROR;
118+
}
119+
}
120+
121+
sqlite3_finalize(stmt);
122+
return rc;
123+
}
124+
125+
// Create peer tracking table
126+
int crsql_init_peer_tracking_table(sqlite3 *db) {
127+
char *err = NULL;
128+
int rc = sqlite3_exec(db,
129+
"CREATE TABLE IF NOT EXISTS crsql_tracked_peers ("
130+
" \"site_id\" BLOB NOT NULL,"
131+
" \"version\" INTEGER NOT NULL,"
132+
" \"seq\" INTEGER DEFAULT 0,"
133+
" \"tag\" INTEGER,"
134+
" \"event\" INTEGER,"
135+
" PRIMARY KEY (\"site_id\", \"tag\", \"event\")"
136+
") STRICT;",
137+
NULL, NULL, &err);
138+
139+
if (err) sqlite3_free(err);
140+
return rc;
141+
}
142+
143+
// Create schema table
144+
int crsql_create_schema_table_if_not_exists(sqlite3 *db) {
145+
char *err = NULL;
146+
int rc = sqlite3_exec(db, "SAVEPOINT crsql_create_schema_table;", NULL, NULL, NULL);
147+
if (rc != SQLITE_OK) return rc;
148+
149+
char *sql = sqlite3_mprintf(
150+
"CREATE TABLE IF NOT EXISTS \"%w\" (\"key\" TEXT PRIMARY KEY, \"value\" ANY);",
151+
TBL_SCHEMA
152+
);
153+
if (!sql) {
154+
sqlite3_exec(db, "ROLLBACK;", NULL, NULL, NULL);
155+
return SQLITE_NOMEM;
156+
}
157+
158+
rc = sqlite3_exec(db, sql, NULL, NULL, &err);
159+
sqlite3_free(sql);
160+
161+
if (rc != SQLITE_OK) {
162+
if (err) sqlite3_free(err);
163+
sqlite3_exec(db, "ROLLBACK;", NULL, NULL, NULL);
164+
return rc;
165+
}
166+
167+
return sqlite3_exec(db, "RELEASE crsql_create_schema_table;", NULL, NULL, NULL);
168+
}
169+
170+
// Check and migrate database version
171+
int crsql_maybe_update_db(sqlite3 *db, char **err_msg) {
172+
int exists = 0;
173+
int rc = has_table(db, TBL_SCHEMA, &exists);
174+
if (rc != SQLITE_OK) return rc;
175+
176+
int is_blank_slate = !exists;
177+
178+
// Create schema table if needed
179+
rc = crsql_create_schema_table_if_not_exists(db);
180+
if (rc != SQLITE_OK) return rc;
181+
182+
// Start transaction
183+
rc = sqlite3_exec(db, "SAVEPOINT crsql_maybe_update_db;", NULL, NULL, NULL);
184+
if (rc != SQLITE_OK) return rc;
185+
186+
// Get recorded version
187+
int recorded_version = 0;
188+
189+
if (is_blank_slate) {
190+
recorded_version = CRSQLITE_VERSION;
191+
} else {
192+
sqlite3_stmt *stmt = NULL;
193+
rc = sqlite3_prepare_v2(db,
194+
"SELECT value FROM crsql_master WHERE key = 'crsqlite_version'",
195+
-1, &stmt, NULL);
196+
197+
if (rc != SQLITE_OK) {
198+
sqlite3_exec(db, "ROLLBACK;", NULL, NULL, NULL);
199+
return rc;
200+
}
201+
202+
rc = sqlite3_step(stmt);
203+
if (rc == SQLITE_ROW) {
204+
recorded_version = sqlite3_column_int(stmt, 0);
205+
}
206+
sqlite3_finalize(stmt);
207+
}
208+
209+
// Check version compatibility
210+
if (recorded_version < CRSQLITE_VERSION_0_15_0 && !is_blank_slate) {
211+
if (err_msg) {
212+
*err_msg = sqlite3_mprintf(
213+
"Opening a db created with cr-sqlite version %d is not supported. "
214+
"Version 0.15.0 is a breaking change.",
215+
recorded_version
216+
);
217+
}
218+
sqlite3_exec(db, "ROLLBACK;", NULL, NULL, NULL);
219+
return SQLITE_ERROR;
220+
}
221+
222+
// TODO: Add migration functions here if we need to migrate from older versions
223+
// if (recorded_version < CRSQLITE_VERSION_0_15_0) {
224+
// update_to_0_15_0(db);
225+
// }
226+
227+
// Write version if upgraded or new database
228+
if (recorded_version < CRSQLITE_VERSION || is_blank_slate) {
229+
sqlite3_stmt *stmt = NULL;
230+
rc = sqlite3_prepare_v2(db,
231+
"INSERT OR REPLACE INTO crsql_master VALUES ('crsqlite_version', ?)",
232+
-1, &stmt, NULL);
233+
234+
if (rc != SQLITE_OK) {
235+
sqlite3_exec(db, "ROLLBACK;", NULL, NULL, NULL);
236+
return rc;
237+
}
238+
239+
rc = sqlite3_bind_int(stmt, 1, CRSQLITE_VERSION);
240+
if (rc != SQLITE_OK) {
241+
sqlite3_finalize(stmt);
242+
sqlite3_exec(db, "ROLLBACK;", NULL, NULL, NULL);
243+
return rc;
244+
}
245+
246+
rc = sqlite3_step(stmt);
247+
sqlite3_finalize(stmt);
248+
249+
if (rc != SQLITE_DONE) {
250+
sqlite3_exec(db, "ROLLBACK;", NULL, NULL, NULL);
251+
return rc;
252+
}
253+
}
254+
255+
return sqlite3_exec(db, "RELEASE crsql_maybe_update_db;", NULL, NULL, NULL);
256+
}
257+
258+
// Create clock tables for a CRR table
259+
int crsql_create_clock_table(sqlite3 *db, crsql_TableInfo *table_info, char **err) {
260+
char *escaped_tbl = crsql_escape_ident(table_info->tbl_name);
261+
char *pk_list = crsql_as_identifier_list(table_info->pks, table_info->pks_len, NULL);
262+
263+
if (!escaped_tbl || !pk_list) {
264+
sqlite3_free(escaped_tbl);
265+
sqlite3_free(pk_list);
266+
if (err) *err = sqlite3_mprintf("Out of memory creating clock table");
267+
return SQLITE_NOMEM;
268+
}
269+
270+
// Create clock table
271+
char *sql = sqlite3_mprintf(
272+
"CREATE TABLE IF NOT EXISTS \"%s__crsql_clock\" (\n"
273+
" key INTEGER NOT NULL,\n"
274+
" col_name TEXT NOT NULL,\n"
275+
" col_version INTEGER NOT NULL,\n"
276+
" db_version INTEGER NOT NULL,\n"
277+
" site_id INTEGER NOT NULL DEFAULT 0,\n"
278+
" seq INTEGER NOT NULL,\n"
279+
" PRIMARY KEY (key, col_name)\n"
280+
") WITHOUT ROWID, STRICT",
281+
escaped_tbl
282+
);
283+
284+
if (!sql) {
285+
sqlite3_free(escaped_tbl);
286+
sqlite3_free(pk_list);
287+
if (err) *err = sqlite3_mprintf("Out of memory");
288+
return SQLITE_NOMEM;
289+
}
290+
291+
int rc = sqlite3_exec(db, sql, NULL, NULL, err);
292+
sqlite3_free(sql);
293+
294+
if (rc != SQLITE_OK) {
295+
sqlite3_free(escaped_tbl);
296+
sqlite3_free(pk_list);
297+
return rc;
298+
}
299+
300+
// Create db_version index on clock table
301+
sql = sqlite3_mprintf(
302+
"CREATE INDEX IF NOT EXISTS \"%s__crsql_clock_dbv_idx\" "
303+
"ON \"%s__crsql_clock\" (\"db_version\")",
304+
escaped_tbl, escaped_tbl
305+
);
306+
307+
if (!sql) {
308+
sqlite3_free(escaped_tbl);
309+
sqlite3_free(pk_list);
310+
if (err) *err = sqlite3_mprintf("Out of memory");
311+
return SQLITE_NOMEM;
312+
}
313+
314+
rc = sqlite3_exec(db, sql, NULL, NULL, err);
315+
sqlite3_free(sql);
316+
317+
if (rc != SQLITE_OK) {
318+
sqlite3_free(escaped_tbl);
319+
sqlite3_free(pk_list);
320+
return rc;
321+
}
322+
323+
// Create primary key table
324+
sql = sqlite3_mprintf(
325+
"CREATE TABLE IF NOT EXISTS \"%s__crsql_pks\" (__crsql_key INTEGER PRIMARY KEY, %s)",
326+
table_info->tbl_name, pk_list
327+
);
328+
329+
if (!sql) {
330+
sqlite3_free(escaped_tbl);
331+
sqlite3_free(pk_list);
332+
if (err) *err = sqlite3_mprintf("Out of memory");
333+
return SQLITE_NOMEM;
334+
}
335+
336+
rc = sqlite3_exec(db, sql, NULL, NULL, err);
337+
sqlite3_free(sql);
338+
339+
if (rc != SQLITE_OK) {
340+
sqlite3_free(escaped_tbl);
341+
sqlite3_free(pk_list);
342+
return rc;
343+
}
344+
345+
// Create unique index on PKs
346+
sql = sqlite3_mprintf(
347+
"CREATE UNIQUE INDEX IF NOT EXISTS \"%s__crsql_pks_pks\" "
348+
"ON \"%s__crsql_pks\" (%s)",
349+
table_info->tbl_name, table_info->tbl_name, pk_list
350+
);
351+
352+
sqlite3_free(escaped_tbl);
353+
sqlite3_free(pk_list);
354+
355+
if (!sql) {
356+
if (err) *err = sqlite3_mprintf("Out of memory");
357+
return SQLITE_NOMEM;
358+
}
359+
360+
rc = sqlite3_exec(db, sql, NULL, NULL, err);
361+
sqlite3_free(sql);
362+
363+
return rc;
364+
}

core/src/bootstrap.h

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#ifndef CRSQLITE_BOOTSTRAP_H
2+
#define CRSQLITE_BOOTSTRAP_H
3+
4+
#include "sqlite3ext.h"
5+
#include "tableinfo.h"
6+
7+
// Generate a UUID v4 for site_id
8+
void crsql_gen_uuid(unsigned char *blob);
9+
10+
// Initialize or load site_id for this database
11+
int crsql_init_site_id(sqlite3 *db, unsigned char *ret);
12+
13+
// Create the peer tracking table
14+
int crsql_init_peer_tracking_table(sqlite3 *db);
15+
16+
// Create the schema/master table
17+
int crsql_create_schema_table_if_not_exists(sqlite3 *db);
18+
19+
// Check if database needs migration and perform it
20+
int crsql_maybe_update_db(sqlite3 *db, char **err_msg);
21+
22+
// Create clock tables for a CRR table
23+
int crsql_create_clock_table(sqlite3 *db, crsql_TableInfo *table_info, char **err);
24+
25+
#endif

0 commit comments

Comments
 (0)