git-sqlite-vfs 0.0.1 → 0.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +67 -32
- package/bin/cli.js +53 -0
- package/c/Makefile +31 -16
- package/c/download-sqlite.cjs +17 -0
- package/c/git-merge-sqlitevfs.c +210 -186
- package/c/gitvfs.c +69 -21
- package/c/output/git-merge-sqlitevfs.exe +0 -0
- package/c/output/gitvfs.dll +0 -0
- package/c/output/gitvfs.o +0 -0
- package/c/output/gitvfs_test.exe +0 -0
- package/c/output/main.o +0 -0
- package/c/output/sqlite3.o +0 -0
- package/c/sqlite-autoconf-3450200/INSTALL +370 -0
- package/c/sqlite-autoconf-3450200/Makefile.am +20 -0
- package/c/sqlite-autoconf-3450200/Makefile.fallback +19 -0
- package/c/sqlite-autoconf-3450200/Makefile.in +1050 -0
- package/c/sqlite-autoconf-3450200/Makefile.msc +1069 -0
- package/c/sqlite-autoconf-3450200/README.txt +113 -0
- package/c/sqlite-autoconf-3450200/Replace.cs +223 -0
- package/c/sqlite-autoconf-3450200/aclocal.m4 +10204 -0
- package/c/sqlite-autoconf-3450200/compile +348 -0
- package/c/sqlite-autoconf-3450200/config.guess +1754 -0
- package/c/sqlite-autoconf-3450200/config.sub +1890 -0
- package/c/sqlite-autoconf-3450200/configure +16887 -0
- package/c/sqlite-autoconf-3450200/configure.ac +270 -0
- package/c/sqlite-autoconf-3450200/depcomp +791 -0
- package/c/sqlite-autoconf-3450200/install-sh +541 -0
- package/c/sqlite-autoconf-3450200/ltmain.sh +11251 -0
- package/c/sqlite-autoconf-3450200/missing +215 -0
- package/c/sqlite-autoconf-3450200/shell.c +29659 -0
- package/c/sqlite-autoconf-3450200/sqlite3.1 +161 -0
- package/c/sqlite-autoconf-3450200/sqlite3.c +255811 -0
- package/c/sqlite-autoconf-3450200/sqlite3.h +13357 -0
- package/c/sqlite-autoconf-3450200/sqlite3.pc.in +13 -0
- package/c/sqlite-autoconf-3450200/sqlite3.rc +83 -0
- package/c/sqlite-autoconf-3450200/sqlite3ext.h +719 -0
- package/c/sqlite-autoconf-3450200/sqlite3rc.h +3 -0
- package/c/sqlite-autoconf-3450200/tea/Makefile.in +475 -0
- package/c/sqlite-autoconf-3450200/tea/README +36 -0
- package/c/sqlite-autoconf-3450200/tea/aclocal.m4 +9 -0
- package/c/sqlite-autoconf-3450200/tea/configure +10179 -0
- package/c/sqlite-autoconf-3450200/tea/configure.ac +227 -0
- package/c/sqlite-autoconf-3450200/tea/doc/sqlite3.n +15 -0
- package/c/sqlite-autoconf-3450200/tea/generic/tclsqlite3.c +4080 -0
- package/c/sqlite-autoconf-3450200/tea/license.terms +6 -0
- package/c/sqlite-autoconf-3450200/tea/pkgIndex.tcl.in +10 -0
- package/c/sqlite-autoconf-3450200/tea/tclconfig/install-sh +528 -0
- package/c/sqlite-autoconf-3450200/tea/tclconfig/tcl.m4 +4067 -0
- package/c/sqlite-autoconf-3450200/tea/win/makefile.vc +430 -0
- package/c/sqlite-autoconf-3450200/tea/win/nmakehlp.c +815 -0
- package/c/sqlite-autoconf-3450200/tea/win/rules.vc +711 -0
- package/c/sqlite-autoconf-3450200.tar.gz +0 -0
- package/c/sqlite3.c +255811 -0
- package/c/sqlite3.h +13357 -0
- package/c/sqlite3ext.h +719 -0
- package/downloader.js +63 -0
- package/index.d.ts +14 -0
- package/index.js +103 -51
- package/install.js +13 -49
- package/package.json +22 -3
- package/c/output/git-merge-sqlitevfs +0 -0
- package/c/output/gitvfs.so +0 -0
- package/c/output/gitvfs_test +0 -0
- package/test.js +0 -209
package/c/git-merge-sqlitevfs.c
CHANGED
|
@@ -6,225 +6,249 @@
|
|
|
6
6
|
#include "gitvfs.h"
|
|
7
7
|
|
|
8
8
|
int main(int argc, char *argv[]) {
|
|
9
|
-
// A Git
|
|
9
|
+
// A Git Driver receives: %O %A %B %P
|
|
10
10
|
if (argc < 5) {
|
|
11
|
-
fprintf(stderr, "Usage: %s
|
|
11
|
+
fprintf(stderr, "Usage: %s %%O %%A %%B %%P\n", argv[0]);
|
|
12
12
|
return 1;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
const char *
|
|
16
|
-
const char *
|
|
15
|
+
const char *path_out = argv[2]; // %A
|
|
16
|
+
const char *path_repo = argv[4]; // %P
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
int ret_other = system(cmd_other);
|
|
26
|
-
if (ret_other != 0) {
|
|
27
|
-
fprintf(stderr, "Failed to extract other database. Command: %s\n", cmd_other);
|
|
18
|
+
char base_dir[512];
|
|
19
|
+
strncpy(base_dir, path_repo, sizeof(base_dir) - 1);
|
|
20
|
+
base_dir[sizeof(base_dir) - 1] = '\0';
|
|
21
|
+
char *pages_ptr = strstr(base_dir, "/pages/");
|
|
22
|
+
if (!pages_ptr) {
|
|
23
|
+
fprintf(stderr, "Failed to parse base_dir from %s\n", path_repo);
|
|
24
|
+
return 1;
|
|
28
25
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
26
|
+
*pages_ptr = '\0';
|
|
27
|
+
|
|
28
|
+
// We only need to run the logical merge once per merge operation.
|
|
29
|
+
// We can use a lock file.
|
|
30
|
+
char lock_file[512];
|
|
31
|
+
snprintf(lock_file, sizeof(lock_file), "/tmp/gitvfs_merge_%s.lock", base_dir);
|
|
32
|
+
for (size_t i=0; i<sizeof(lock_file); i++) {
|
|
33
|
+
if (lock_file[i] == '/') lock_file[i] = '_';
|
|
36
34
|
}
|
|
35
|
+
|
|
36
|
+
if (access(lock_file, F_OK) != 0) {
|
|
37
|
+
// Create lock
|
|
38
|
+
FILE *lf = fopen(lock_file, "w");
|
|
39
|
+
if (lf) fclose(lf);
|
|
40
|
+
|
|
41
|
+
printf("VFS Merge Driver Invoked: Whole-DB Logical Merge Initiated for %s...\n", base_dir);
|
|
42
|
+
|
|
43
|
+
system("env > /tmp/gitvfs_env.txt");
|
|
44
|
+
system("ls -la .git > /tmp/gitvfs_ls.txt");
|
|
45
|
+
|
|
46
|
+
char merge_head_hash[64] = "";
|
|
47
|
+
extern char **environ;
|
|
48
|
+
for (char **env = environ; *env != 0; env++) {
|
|
49
|
+
if (strncmp(*env, "GITHEAD_", 8) == 0) {
|
|
50
|
+
strncpy(merge_head_hash, *env + 8, 40);
|
|
51
|
+
merge_head_hash[40] = '\0';
|
|
52
|
+
break;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (strlen(merge_head_hash) == 0) {
|
|
57
|
+
fprintf(stderr, "Could not find GITHEAD_ environment variable to determine other commit.\n");
|
|
58
|
+
return 1;
|
|
59
|
+
}
|
|
37
60
|
|
|
38
|
-
|
|
39
|
-
|
|
61
|
+
char cmd_other[1024];
|
|
62
|
+
snprintf(cmd_other, sizeof(cmd_other), "git archive %s \"%s/pages/\" | tar -x -C /tmp/gitvfs_other_db", merge_head_hash, base_dir);
|
|
63
|
+
if (system(cmd_other) != 0) {
|
|
64
|
+
fprintf(stderr, "Failed to extract other database. Command: %s\n", cmd_other);
|
|
65
|
+
}
|
|
40
66
|
|
|
41
|
-
|
|
42
|
-
|
|
67
|
+
system("rm -rf /tmp/gitvfs_ancestor_db && mkdir -p /tmp/gitvfs_ancestor_db");
|
|
68
|
+
char cmd_ancestor[1024];
|
|
69
|
+
snprintf(cmd_ancestor, sizeof(cmd_ancestor), "git archive $(git merge-base HEAD %s) \"%s/pages/\" | tar -x -C /tmp/gitvfs_ancestor_db", merge_head_hash, base_dir);
|
|
70
|
+
if (system(cmd_ancestor) != 0) {
|
|
71
|
+
fprintf(stderr, "Failed to extract ancestor database. Command: %s\n", cmd_ancestor);
|
|
72
|
+
}
|
|
43
73
|
|
|
44
|
-
|
|
45
|
-
|
|
74
|
+
// The Logical Merge via ATTACH
|
|
75
|
+
sqlite3 *db_local;
|
|
76
|
+
sqlite3_gitvfs_init_impl(NULL);
|
|
77
|
+
|
|
78
|
+
// Ensure VFS env var is set for gitvfs_Open
|
|
79
|
+
#ifdef _WIN32
|
|
80
|
+
char env_str[1024];
|
|
81
|
+
snprintf(env_str, sizeof(env_str), "GIT_SQLITE_VFS_DIR=%s", base_dir);
|
|
82
|
+
_putenv(env_str);
|
|
83
|
+
#else
|
|
84
|
+
setenv("GIT_SQLITE_VFS_DIR", base_dir, 1);
|
|
85
|
+
#endif
|
|
86
|
+
|
|
87
|
+
if (sqlite3_open_v2(base_dir, &db_local, SQLITE_OPEN_READWRITE, "gitvfs") != SQLITE_OK) {
|
|
88
|
+
fprintf(stderr, "Failed to open local DB: %s\n", sqlite3_errmsg(db_local));
|
|
89
|
+
}
|
|
46
90
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
91
|
+
char attach_other[1024];
|
|
92
|
+
snprintf(attach_other, sizeof(attach_other), "ATTACH DATABASE '/tmp/gitvfs_other_db/%s' AS other;", base_dir);
|
|
93
|
+
sqlite3_exec(db_local, attach_other, NULL, 0, NULL);
|
|
94
|
+
|
|
95
|
+
char attach_ancestor[1024];
|
|
96
|
+
snprintf(attach_ancestor, sizeof(attach_ancestor), "ATTACH DATABASE '/tmp/gitvfs_ancestor_db/%s' AS ancestor;", base_dir);
|
|
97
|
+
sqlite3_exec(db_local, attach_ancestor, NULL, 0, NULL);
|
|
98
|
+
|
|
99
|
+
// Phase 1 (Propagate Drops)
|
|
100
|
+
sqlite3_exec(db_local, "CREATE TEMP TABLE drops AS SELECT type, name FROM main.sqlite_schema WHERE name IN (SELECT name FROM ancestor.sqlite_schema EXCEPT SELECT name FROM other.sqlite_schema) AND name NOT LIKE 'sqlite_%';", NULL, 0, NULL);
|
|
101
|
+
|
|
102
|
+
sqlite3_stmt *drop_stmt;
|
|
103
|
+
char *drops_to_execute = calloc(1, 1024 * 1024); // 1MB buffer
|
|
104
|
+
if (sqlite3_prepare_v2(db_local, "SELECT type, name FROM drops;", -1, &drop_stmt, NULL) == SQLITE_OK) {
|
|
105
|
+
while (sqlite3_step(drop_stmt) == SQLITE_ROW) {
|
|
106
|
+
const char *type = (const char *)sqlite3_column_text(drop_stmt, 0);
|
|
107
|
+
const char *name = (const char *)sqlite3_column_text(drop_stmt, 1);
|
|
108
|
+
if (type && name) {
|
|
109
|
+
char drop_sql[512];
|
|
110
|
+
snprintf(drop_sql, sizeof(drop_sql), "DROP %s IF EXISTS \"%s\";\n", type, name);
|
|
111
|
+
strcat(drops_to_execute, drop_sql);
|
|
112
|
+
}
|
|
61
113
|
}
|
|
114
|
+
sqlite3_finalize(drop_stmt);
|
|
62
115
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
if (strlen(drops_to_execute) > 0) {
|
|
66
|
-
printf("Propagating Drops:\n%s", drops_to_execute);
|
|
67
|
-
char *err_msg = NULL;
|
|
68
|
-
if (sqlite3_exec(db_local, drops_to_execute, NULL, 0, &err_msg) != SQLITE_OK) {
|
|
69
|
-
fprintf(stderr, "Error executing drop: %s\n", err_msg);
|
|
70
|
-
sqlite3_free(err_msg);
|
|
116
|
+
if (strlen(drops_to_execute) > 0) {
|
|
117
|
+
sqlite3_exec(db_local, drops_to_execute, NULL, 0, NULL);
|
|
71
118
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
119
|
+
free(drops_to_execute);
|
|
120
|
+
sqlite3_exec(db_local, "DROP TABLE drops;", NULL, 0, NULL);
|
|
121
|
+
|
|
122
|
+
// Phase 2 (Propagate Additions)
|
|
123
|
+
sqlite3_exec(db_local, "CREATE TEMP TABLE adds AS SELECT sql FROM other.sqlite_schema WHERE sql IS NOT NULL AND name IN (SELECT name FROM other.sqlite_schema EXCEPT SELECT name FROM ancestor.sqlite_schema) AND name NOT IN (SELECT name FROM main.sqlite_schema) ORDER BY CASE WHEN type='table' THEN 1 ELSE 2 END;", NULL, 0, NULL);
|
|
124
|
+
|
|
125
|
+
sqlite3_stmt *add_stmt;
|
|
126
|
+
char *adds_to_execute = calloc(1, 1024 * 1024); // 1MB buffer
|
|
127
|
+
if (sqlite3_prepare_v2(db_local, "SELECT sql FROM adds;", -1, &add_stmt, NULL) == SQLITE_OK) {
|
|
128
|
+
while (sqlite3_step(add_stmt) == SQLITE_ROW) {
|
|
129
|
+
const char *sql = (const char *)sqlite3_column_text(add_stmt, 0);
|
|
130
|
+
if (sql) {
|
|
131
|
+
strcat(adds_to_execute, sql);
|
|
132
|
+
strcat(adds_to_execute, ";\n");
|
|
133
|
+
}
|
|
87
134
|
}
|
|
135
|
+
sqlite3_finalize(add_stmt);
|
|
88
136
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
if (strlen(adds_to_execute) > 0) {
|
|
92
|
-
printf("Propagating Additions:\n%s", adds_to_execute);
|
|
93
|
-
char *err_msg = NULL;
|
|
94
|
-
if (sqlite3_exec(db_local, adds_to_execute, NULL, 0, &err_msg) != SQLITE_OK) {
|
|
95
|
-
fprintf(stderr, "Error executing addition: %s\n", err_msg);
|
|
96
|
-
sqlite3_free(err_msg);
|
|
137
|
+
if (strlen(adds_to_execute) > 0) {
|
|
138
|
+
sqlite3_exec(db_local, adds_to_execute, NULL, 0, NULL);
|
|
97
139
|
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
has_pk = 1;
|
|
126
|
-
break;
|
|
140
|
+
free(adds_to_execute);
|
|
141
|
+
sqlite3_exec(db_local, "DROP TABLE adds;", NULL, 0, NULL);
|
|
142
|
+
|
|
143
|
+
// Dynamically discover and merge tables
|
|
144
|
+
sqlite3_stmt *stmt;
|
|
145
|
+
const char *query_schema = "SELECT name FROM main.sqlite_schema WHERE type='table' AND name NOT LIKE 'sqlite_%';";
|
|
146
|
+
|
|
147
|
+
if (sqlite3_prepare_v2(db_local, query_schema, -1, &stmt, NULL) == SQLITE_OK) {
|
|
148
|
+
while (sqlite3_step(stmt) == SQLITE_ROW) {
|
|
149
|
+
const char *table_name = (const char *)sqlite3_column_text(stmt, 0);
|
|
150
|
+
if (table_name) {
|
|
151
|
+
char pragma_query[512];
|
|
152
|
+
snprintf(pragma_query, sizeof(pragma_query), "PRAGMA main.table_info(\"%s\");", table_name);
|
|
153
|
+
sqlite3_stmt *pragma_stmt;
|
|
154
|
+
char pk_col[256] = {0};
|
|
155
|
+
int has_pk = 0;
|
|
156
|
+
|
|
157
|
+
if (sqlite3_prepare_v2(db_local, pragma_query, -1, &pragma_stmt, NULL) == SQLITE_OK) {
|
|
158
|
+
while (sqlite3_step(pragma_stmt) == SQLITE_ROW) {
|
|
159
|
+
int pk = sqlite3_column_int(pragma_stmt, 5);
|
|
160
|
+
if (pk > 0) {
|
|
161
|
+
const char *col_name = (const char *)sqlite3_column_text(pragma_stmt, 1);
|
|
162
|
+
if (col_name) {
|
|
163
|
+
strncpy(pk_col, col_name, sizeof(pk_col) - 1);
|
|
164
|
+
has_pk = 1;
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
127
167
|
}
|
|
128
168
|
}
|
|
169
|
+
sqlite3_finalize(pragma_stmt);
|
|
129
170
|
}
|
|
130
|
-
sqlite3_finalize(pragma_stmt);
|
|
131
|
-
} else {
|
|
132
|
-
fprintf(stderr, "Failed to prepare PRAGMA query for table %s: %s\n", table_name, sqlite3_errmsg(db_local));
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
if (has_pk) {
|
|
136
|
-
int exists_in_ancestor = 0;
|
|
137
|
-
char check_anc[256];
|
|
138
|
-
snprintf(check_anc, sizeof(check_anc), "SELECT 1 FROM ancestor.sqlite_schema WHERE type='table' AND name='%s';", table_name);
|
|
139
|
-
sqlite3_stmt *anc_stmt;
|
|
140
|
-
if (sqlite3_prepare_v2(db_local, check_anc, -1, &anc_stmt, NULL) == SQLITE_OK) {
|
|
141
|
-
if (sqlite3_step(anc_stmt) == SQLITE_ROW) exists_in_ancestor = 1;
|
|
142
|
-
sqlite3_finalize(anc_stmt);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
if (exists_in_ancestor) {
|
|
146
|
-
char drop_conflict[256];
|
|
147
|
-
snprintf(drop_conflict, sizeof(drop_conflict), "DROP TABLE IF EXISTS temp.\"conflicted_pks_%s\";", table_name);
|
|
148
|
-
sqlite3_exec(db_local, drop_conflict, NULL, 0, NULL);
|
|
149
|
-
|
|
150
|
-
char conflict_query[1024];
|
|
151
|
-
snprintf(conflict_query, sizeof(conflict_query),
|
|
152
|
-
"CREATE TEMP TABLE \"conflicted_pks_%s\" AS "
|
|
153
|
-
"SELECT \"%s\" FROM (SELECT * FROM main.\"%s\" EXCEPT SELECT * FROM ancestor.\"%s\") "
|
|
154
|
-
"INTERSECT "
|
|
155
|
-
"SELECT \"%s\" FROM (SELECT * FROM other.\"%s\" EXCEPT SELECT * FROM ancestor.\"%s\");",
|
|
156
|
-
table_name,
|
|
157
|
-
pk_col, table_name, table_name,
|
|
158
|
-
pk_col, table_name, table_name);
|
|
159
|
-
|
|
160
|
-
char *err_msg = NULL;
|
|
161
|
-
if (sqlite3_exec(db_local, conflict_query, NULL, 0, &err_msg) != SQLITE_OK) {
|
|
162
|
-
fprintf(stderr, "Error executing conflict query: %s\n", err_msg);
|
|
163
|
-
sqlite3_free(err_msg);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
// Identify Row-Level Clashes
|
|
167
|
-
sqlite3_stmt *conflict_stmt;
|
|
168
|
-
char select_conflict[256];
|
|
169
|
-
snprintf(select_conflict, sizeof(select_conflict), "SELECT * FROM \"conflicted_pks_%s\";", table_name);
|
|
170
|
-
if (sqlite3_prepare_v2(db_local, select_conflict, -1, &conflict_stmt, NULL) == SQLITE_OK) {
|
|
171
|
-
while (sqlite3_step(conflict_stmt) == SQLITE_ROW) {
|
|
172
|
-
const char *conflicted_pk_val = (const char *)sqlite3_column_text(conflict_stmt, 0);
|
|
173
|
-
printf("WARNING: True Row Conflict detected on table '%s', PK '%s'. Preserving HEAD state.\n", table_name, conflicted_pk_val ? conflicted_pk_val : "NULL");
|
|
174
|
-
}
|
|
175
|
-
sqlite3_finalize(conflict_stmt);
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
char q1[1024];
|
|
179
|
-
char q2[1024];
|
|
180
171
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
172
|
+
if (has_pk) {
|
|
173
|
+
int exists_in_ancestor = 0;
|
|
174
|
+
char check_anc[256];
|
|
175
|
+
snprintf(check_anc, sizeof(check_anc), "SELECT 1 FROM ancestor.sqlite_schema WHERE type='table' AND name='%s';", table_name);
|
|
176
|
+
sqlite3_stmt *anc_stmt;
|
|
177
|
+
if (sqlite3_prepare_v2(db_local, check_anc, -1, &anc_stmt, NULL) == SQLITE_OK) {
|
|
178
|
+
if (sqlite3_step(anc_stmt) == SQLITE_ROW) exists_in_ancestor = 1;
|
|
179
|
+
sqlite3_finalize(anc_stmt);
|
|
188
180
|
}
|
|
189
181
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
182
|
+
if (exists_in_ancestor) {
|
|
183
|
+
char drop_conflict[256];
|
|
184
|
+
snprintf(drop_conflict, sizeof(drop_conflict), "DROP TABLE IF EXISTS temp.\"conflicted_pks_%s\";", table_name);
|
|
185
|
+
sqlite3_exec(db_local, drop_conflict, NULL, 0, NULL);
|
|
186
|
+
|
|
187
|
+
char conflict_query[1024];
|
|
188
|
+
snprintf(conflict_query, sizeof(conflict_query),
|
|
189
|
+
"CREATE TEMP TABLE \"conflicted_pks_%s\" AS "
|
|
190
|
+
"SELECT \"%s\" FROM (SELECT * FROM main.\"%s\" EXCEPT SELECT * FROM ancestor.\"%s\") "
|
|
191
|
+
"INTERSECT "
|
|
192
|
+
"SELECT \"%s\" FROM (SELECT * FROM other.\"%s\" EXCEPT SELECT * FROM ancestor.\"%s\");",
|
|
193
|
+
table_name,
|
|
194
|
+
pk_col, table_name, table_name,
|
|
195
|
+
pk_col, table_name, table_name);
|
|
196
|
+
|
|
197
|
+
sqlite3_exec(db_local, conflict_query, NULL, 0, NULL);
|
|
198
|
+
|
|
199
|
+
char q1[1024];
|
|
200
|
+
char q2[1024];
|
|
201
|
+
|
|
202
|
+
// Query 1: Propagate Deletions from MERGE_HEAD
|
|
203
|
+
snprintf(q1, sizeof(q1),
|
|
204
|
+
"DELETE FROM main.\"%s\" WHERE \"%s\" IN (SELECT \"%s\" FROM ancestor.\"%s\" EXCEPT SELECT \"%s\" FROM other.\"%s\");",
|
|
205
|
+
table_name, pk_col, pk_col, table_name, pk_col, table_name);
|
|
206
|
+
sqlite3_exec(db_local, q1, NULL, 0, NULL);
|
|
207
|
+
|
|
208
|
+
// Query 2: Propagate Inserts & Updates from MERGE_HEAD, omitting row conflicts
|
|
209
|
+
snprintf(q2, sizeof(q2),
|
|
210
|
+
"REPLACE INTO main.\"%s\" SELECT * FROM other.\"%s\" WHERE \"%s\" IN (SELECT \"%s\" FROM (SELECT * FROM other.\"%s\" EXCEPT SELECT * FROM ancestor.\"%s\")) "
|
|
211
|
+
"AND \"%s\" NOT IN (SELECT \"%s\" FROM \"conflicted_pks_%s\");",
|
|
212
|
+
table_name, table_name, pk_col, pk_col, table_name, table_name, pk_col, pk_col, table_name);
|
|
213
|
+
sqlite3_exec(db_local, q2, NULL, 0, NULL);
|
|
214
|
+
|
|
215
|
+
sqlite3_exec(db_local, drop_conflict, NULL, 0, NULL);
|
|
216
|
+
} else {
|
|
217
|
+
char merge_query[512];
|
|
218
|
+
snprintf(merge_query, sizeof(merge_query), "INSERT OR IGNORE INTO main.\"%s\" SELECT * FROM other.\"%s\";", table_name, table_name);
|
|
219
|
+
sqlite3_exec(db_local, merge_query, NULL, 0, NULL);
|
|
198
220
|
}
|
|
199
|
-
|
|
200
|
-
sqlite3_exec(db_local, drop_conflict, NULL, 0, NULL);
|
|
201
221
|
} else {
|
|
202
|
-
// Fallback to naive 2-way append (table is new)
|
|
203
222
|
char merge_query[512];
|
|
204
|
-
snprintf(merge_query, sizeof(merge_query),
|
|
205
|
-
"INSERT OR IGNORE INTO main.\"%s\" SELECT * FROM other.\"%s\";",
|
|
206
|
-
table_name, table_name);
|
|
223
|
+
snprintf(merge_query, sizeof(merge_query), "INSERT OR IGNORE INTO main.\"%s\" SELECT * FROM other.\"%s\";", table_name, table_name);
|
|
207
224
|
sqlite3_exec(db_local, merge_query, NULL, 0, NULL);
|
|
208
225
|
}
|
|
209
|
-
} else {
|
|
210
|
-
char merge_query[512];
|
|
211
|
-
snprintf(merge_query, sizeof(merge_query),
|
|
212
|
-
"INSERT OR IGNORE INTO main.\"%s\" SELECT * FROM other.\"%s\";",
|
|
213
|
-
table_name, table_name);
|
|
214
|
-
sqlite3_exec(db_local, merge_query, NULL, 0, NULL);
|
|
215
226
|
}
|
|
216
227
|
}
|
|
228
|
+
sqlite3_finalize(stmt);
|
|
217
229
|
}
|
|
218
|
-
sqlite3_finalize(stmt);
|
|
219
|
-
} else {
|
|
220
|
-
fprintf(stderr, "Failed to prepare schema query: %s\n", sqlite3_errmsg(db_local));
|
|
221
|
-
}
|
|
222
230
|
|
|
223
|
-
|
|
231
|
+
sqlite3_close(db_local);
|
|
232
|
+
|
|
233
|
+
char git_add_cmd[1024];
|
|
234
|
+
snprintf(git_add_cmd, sizeof(git_add_cmd), "git add -A -f \"%s/pages/\"", base_dir);
|
|
235
|
+
if (system(git_add_cmd) != 0) {}
|
|
236
|
+
|
|
237
|
+
printf("Logical Merge Complete! VFS physical pages reconciled.\n");
|
|
238
|
+
}
|
|
224
239
|
|
|
225
|
-
|
|
226
|
-
|
|
240
|
+
// Now, copy the resulting file from the working tree to path_out (%A) so Git considers this file resolved!
|
|
241
|
+
// If the file doesn't exist anymore (deleted), we can just create an empty file or remove it.
|
|
242
|
+
char cp_cmd[1024];
|
|
243
|
+
if (access(path_repo, F_OK) == 0) {
|
|
244
|
+
snprintf(cp_cmd, sizeof(cp_cmd), "cp \"%s\" \"%s\"", path_repo, path_out);
|
|
245
|
+
if (system(cp_cmd) != 0) {
|
|
246
|
+
fprintf(stderr, "Failed to copy %s to %s\n", path_repo, path_out);
|
|
247
|
+
}
|
|
248
|
+
} else {
|
|
249
|
+
snprintf(cp_cmd, sizeof(cp_cmd), "rm -f \"%s\"", path_out);
|
|
250
|
+
if (system(cp_cmd) != 0) {}
|
|
251
|
+
}
|
|
227
252
|
|
|
228
|
-
printf("Logical Merge Complete! VFS physical pages reconciled.\n");
|
|
229
253
|
return 0;
|
|
230
254
|
}
|
package/c/gitvfs.c
CHANGED
|
@@ -29,11 +29,34 @@ typedef struct gitvfs_file {
|
|
|
29
29
|
int flat_fd; /* POSIX file descriptor for temp/journal files */
|
|
30
30
|
} gitvfs_file;
|
|
31
31
|
|
|
32
|
+
#ifdef _WIN32
|
|
33
|
+
#include <direct.h>
|
|
34
|
+
#include <io.h>
|
|
35
|
+
#define MKDIR(p, m) _mkdir(p)
|
|
36
|
+
|
|
37
|
+
ssize_t pread(int fd, void *buf, size_t count, off_t offset) {
|
|
38
|
+
off_t current = lseek(fd, 0, SEEK_CUR);
|
|
39
|
+
if (lseek(fd, offset, SEEK_SET) == (off_t)-1) return -1;
|
|
40
|
+
ssize_t ret = read(fd, buf, count);
|
|
41
|
+
lseek(fd, current, SEEK_SET);
|
|
42
|
+
return ret;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
ssize_t pwrite(int fd, const void *buf, size_t count, off_t offset) {
|
|
46
|
+
off_t current = lseek(fd, 0, SEEK_CUR);
|
|
47
|
+
if (lseek(fd, offset, SEEK_SET) == (off_t)-1) return -1;
|
|
48
|
+
ssize_t ret = write(fd, buf, count);
|
|
49
|
+
lseek(fd, current, SEEK_SET);
|
|
50
|
+
return ret;
|
|
51
|
+
}
|
|
52
|
+
#else
|
|
53
|
+
#define MKDIR(p, m) mkdir(p, m)
|
|
54
|
+
#endif
|
|
32
55
|
/*
|
|
33
|
-
* Utility: Recursively create directories (mkdir -p behavior)
|
|
34
56
|
* Helps ensure our nested sharded directory structure exists before writing.
|
|
35
57
|
*/
|
|
36
58
|
static int mkdir_p(const char *path, mode_t mode) {
|
|
59
|
+
(void)mode;
|
|
37
60
|
char tmp[GITVFS_MAX_PATH];
|
|
38
61
|
char *p = NULL;
|
|
39
62
|
size_t len;
|
|
@@ -47,13 +70,13 @@ static int mkdir_p(const char *path, mode_t mode) {
|
|
|
47
70
|
for (p = tmp + 1; *p; p++) {
|
|
48
71
|
if (*p == '/') {
|
|
49
72
|
*p = 0;
|
|
50
|
-
if (
|
|
73
|
+
if (MKDIR(tmp, mode) != 0 && errno != EEXIST) {
|
|
51
74
|
return -1;
|
|
52
75
|
}
|
|
53
76
|
*p = '/';
|
|
54
77
|
}
|
|
55
78
|
}
|
|
56
|
-
if (
|
|
79
|
+
if (MKDIR(tmp, mode) != 0 && errno != EEXIST) {
|
|
57
80
|
return -1;
|
|
58
81
|
}
|
|
59
82
|
return 0;
|
|
@@ -108,7 +131,7 @@ static void generate_gitattributes(const char *base_dir) {
|
|
|
108
131
|
// Create the .gitattributes file
|
|
109
132
|
FILE *f = fopen(attr_path, "w");
|
|
110
133
|
if (f) {
|
|
111
|
-
fprintf(f, "*.bin
|
|
134
|
+
fprintf(f, "*.bin -text -diff merge=sqlitevfs\nsize.meta -text -diff merge=sqlitevfs\n");
|
|
112
135
|
fclose(f);
|
|
113
136
|
}
|
|
114
137
|
}
|
|
@@ -271,7 +294,7 @@ static int gitvfs_Truncate(sqlite3_file *pFile, sqlite3_int64 size) {
|
|
|
271
294
|
snprintf(meta_path, sizeof(meta_path), "%s/pages/size.meta", p->base_dir);
|
|
272
295
|
|
|
273
296
|
if (new_max_page == -1) {
|
|
274
|
-
|
|
297
|
+
remove(meta_path); // DB is completely empty
|
|
275
298
|
} else {
|
|
276
299
|
FILE *f = fopen(meta_path, "w");
|
|
277
300
|
if (f) {
|
|
@@ -285,6 +308,7 @@ static int gitvfs_Truncate(sqlite3_file *pFile, sqlite3_int64 size) {
|
|
|
285
308
|
}
|
|
286
309
|
|
|
287
310
|
static int gitvfs_Sync(sqlite3_file *pFile, int flags) {
|
|
311
|
+
(void)pFile; (void)flags;
|
|
288
312
|
// Single-writer MVP relying on standard POSIX disk flushes.
|
|
289
313
|
// For temp files we could call fsync(p->flat_fd).
|
|
290
314
|
// Returning SQLITE_OK satisfies SQLite's expectation.
|
|
@@ -309,6 +333,7 @@ static int gitvfs_FileSize(sqlite3_file *pFile, sqlite3_int64 *pSize) {
|
|
|
309
333
|
}
|
|
310
334
|
|
|
311
335
|
static int gitvfs_Lock(sqlite3_file *pFile, int eLock) {
|
|
336
|
+
(void)pFile; (void)eLock;
|
|
312
337
|
return SQLITE_OK; // SQLite requires lock functions to succeed
|
|
313
338
|
}
|
|
314
339
|
|
|
@@ -318,19 +343,23 @@ static int gitvfs_Unlock(sqlite3_file *pFile, int eLock) {
|
|
|
318
343
|
}
|
|
319
344
|
|
|
320
345
|
static int gitvfs_CheckReservedLock(sqlite3_file *pFile, int *pResOut) {
|
|
346
|
+
(void)pFile;
|
|
321
347
|
*pResOut = 0;
|
|
322
348
|
return SQLITE_OK;
|
|
323
349
|
}
|
|
324
350
|
|
|
325
351
|
static int gitvfs_FileControl(sqlite3_file *pFile, int op, void *pArg) {
|
|
352
|
+
(void)pFile; (void)op; (void)pArg;
|
|
326
353
|
return SQLITE_NOTFOUND;
|
|
327
354
|
}
|
|
328
355
|
|
|
329
356
|
static int gitvfs_SectorSize(sqlite3_file *pFile) {
|
|
357
|
+
(void)pFile;
|
|
330
358
|
return GITVFS_PAGE_SIZE;
|
|
331
359
|
}
|
|
332
360
|
|
|
333
361
|
static int gitvfs_DeviceCharacteristics(sqlite3_file *pFile) {
|
|
362
|
+
(void)pFile;
|
|
334
363
|
return 0; // Standard characteristics
|
|
335
364
|
}
|
|
336
365
|
|
|
@@ -363,8 +392,13 @@ static const sqlite3_io_methods gitvfs_io_methods = {
|
|
|
363
392
|
static sqlite3_vfs *orig_vfs = NULL;
|
|
364
393
|
|
|
365
394
|
static int gitvfs_Open(sqlite3_vfs *pVfs, const char *zName, sqlite3_file *pFile, int flags, int *pOutFlags) {
|
|
395
|
+
(void)pVfs;
|
|
366
396
|
if (!orig_vfs) orig_vfs = sqlite3_vfs_find(NULL);
|
|
367
|
-
|
|
397
|
+
const char *vfs_dir = getenv("GIT_SQLITE_VFS_DIR");
|
|
398
|
+
if (!vfs_dir) {
|
|
399
|
+
vfs_dir = ".db";
|
|
400
|
+
}
|
|
401
|
+
if (!zName || strstr(zName, vfs_dir) == NULL) {
|
|
368
402
|
return orig_vfs->xOpen(orig_vfs, zName, pFile, flags, pOutFlags);
|
|
369
403
|
}
|
|
370
404
|
gitvfs_file *p = (gitvfs_file*)pFile;
|
|
@@ -376,7 +410,7 @@ static int gitvfs_Open(sqlite3_vfs *pVfs, const char *zName, sqlite3_file *pFile
|
|
|
376
410
|
if (flags & SQLITE_OPEN_MAIN_DB) {
|
|
377
411
|
p->is_main_db = 1;
|
|
378
412
|
|
|
379
|
-
const char *base = (zName != NULL) ? zName :
|
|
413
|
+
const char *base = (zName != NULL) ? zName : vfs_dir;
|
|
380
414
|
|
|
381
415
|
// Strip URI parameters if they exist
|
|
382
416
|
char clean_base[GITVFS_MAX_PATH];
|
|
@@ -441,36 +475,39 @@ static int gitvfs_Open(sqlite3_vfs *pVfs, const char *zName, sqlite3_file *pFile
|
|
|
441
475
|
}
|
|
442
476
|
|
|
443
477
|
static int gitvfs_Delete(sqlite3_vfs *pVfs, const char *zName, int syncDir) {
|
|
478
|
+
(void)pVfs; (void)syncDir;
|
|
444
479
|
// Standard file deletion, primarily used for clearing out old journals
|
|
445
480
|
unlink(zName);
|
|
446
481
|
return SQLITE_OK;
|
|
447
482
|
}
|
|
448
483
|
|
|
449
484
|
static int gitvfs_Access(sqlite3_vfs *pVfs, const char *zName, int flags, int *pResOut) {
|
|
485
|
+
(void)pVfs; (void)flags;
|
|
450
486
|
// Check if the directory or file is accessible
|
|
451
487
|
*pResOut = (access(zName, F_OK) == 0) ? 1 : 0;
|
|
452
488
|
return SQLITE_OK;
|
|
453
489
|
}
|
|
454
490
|
|
|
455
491
|
static int gitvfs_FullPathname(sqlite3_vfs *pVfs, const char *zName, int nOut, char *zOut) {
|
|
492
|
+
(void)pVfs;
|
|
456
493
|
snprintf(zOut, nOut, "%s", zName);
|
|
457
494
|
return SQLITE_OK;
|
|
458
495
|
}
|
|
459
496
|
|
|
460
497
|
/* System calls to load extensions (stubbed) */
|
|
461
|
-
static void *gitvfs_DlOpen(sqlite3_vfs *pVfs, const char *zFilename) { return orig_vfs->xDlOpen(orig_vfs, zFilename); }
|
|
462
|
-
static void gitvfs_DlError(sqlite3_vfs *pVfs, int nByte, char *zErrMsg) { orig_vfs->xDlError(orig_vfs, nByte, zErrMsg); }
|
|
463
|
-
static void (*gitvfs_DlSym(sqlite3_vfs *pVfs, void *p, const char*zSymbol))(void) { return orig_vfs->xDlSym(orig_vfs, p, zSymbol); }
|
|
464
|
-
static void gitvfs_DlClose(sqlite3_vfs *pVfs, void *pHandle) { orig_vfs->xDlClose(orig_vfs, pHandle); }
|
|
465
|
-
static int gitvfs_Randomness(sqlite3_vfs *pVfs, int nByte, char *zOut) { return orig_vfs->xRandomness(orig_vfs, nByte, zOut); }
|
|
466
|
-
static int gitvfs_Sleep(sqlite3_vfs *pVfs, int microseconds) { return orig_vfs->xSleep(orig_vfs, microseconds); }
|
|
467
|
-
static int gitvfs_CurrentTime(sqlite3_vfs *pVfs, double *prNow) { return orig_vfs->xCurrentTime(orig_vfs, prNow); }
|
|
468
|
-
|
|
469
|
-
static int gitvfs_GetLastError(sqlite3_vfs *pVfs, int a, char *b) { return orig_vfs->xGetLastError ? orig_vfs->xGetLastError(orig_vfs, a, b) : 0; }
|
|
470
|
-
static int gitvfs_CurrentTimeInt64(sqlite3_vfs *pVfs, sqlite3_int64 *p) { return orig_vfs->xCurrentTimeInt64 ? orig_vfs->xCurrentTimeInt64(orig_vfs, p) : 0; }
|
|
471
|
-
static int gitvfs_SetSystemCall(sqlite3_vfs *pVfs, const char *zName, sqlite3_syscall_ptr pNew) { return orig_vfs->xSetSystemCall ? orig_vfs->xSetSystemCall(orig_vfs, zName, pNew) : SQLITE_ERROR; }
|
|
472
|
-
static sqlite3_syscall_ptr gitvfs_GetSystemCall(sqlite3_vfs *pVfs, const char *zName) { return orig_vfs->xGetSystemCall ? orig_vfs->xGetSystemCall(orig_vfs, zName) : NULL; }
|
|
473
|
-
static const char *gitvfs_NextSystemCall(sqlite3_vfs *pVfs, const char *zName) { return orig_vfs->xNextSystemCall ? orig_vfs->xNextSystemCall(orig_vfs, zName) : NULL; }
|
|
498
|
+
static void *gitvfs_DlOpen(sqlite3_vfs *pVfs, const char *zFilename) { (void)pVfs; return orig_vfs->xDlOpen(orig_vfs, zFilename); }
|
|
499
|
+
static void gitvfs_DlError(sqlite3_vfs *pVfs, int nByte, char *zErrMsg) { (void)pVfs; orig_vfs->xDlError(orig_vfs, nByte, zErrMsg); }
|
|
500
|
+
static void (*gitvfs_DlSym(sqlite3_vfs *pVfs, void *p, const char*zSymbol))(void) { (void)pVfs; return orig_vfs->xDlSym(orig_vfs, p, zSymbol); }
|
|
501
|
+
static void gitvfs_DlClose(sqlite3_vfs *pVfs, void *pHandle) { (void)pVfs; orig_vfs->xDlClose(orig_vfs, pHandle); }
|
|
502
|
+
static int gitvfs_Randomness(sqlite3_vfs *pVfs, int nByte, char *zOut) { (void)pVfs; return orig_vfs->xRandomness(orig_vfs, nByte, zOut); }
|
|
503
|
+
static int gitvfs_Sleep(sqlite3_vfs *pVfs, int microseconds) { (void)pVfs; return orig_vfs->xSleep(orig_vfs, microseconds); }
|
|
504
|
+
static int gitvfs_CurrentTime(sqlite3_vfs *pVfs, double *prNow) { (void)pVfs; return orig_vfs->xCurrentTime(orig_vfs, prNow); }
|
|
505
|
+
|
|
506
|
+
static int gitvfs_GetLastError(sqlite3_vfs *pVfs, int a, char *b) { (void)pVfs; return orig_vfs->xGetLastError ? orig_vfs->xGetLastError(orig_vfs, a, b) : 0; }
|
|
507
|
+
static int gitvfs_CurrentTimeInt64(sqlite3_vfs *pVfs, sqlite3_int64 *p) { (void)pVfs; return orig_vfs->xCurrentTimeInt64 ? orig_vfs->xCurrentTimeInt64(orig_vfs, p) : 0; }
|
|
508
|
+
static int gitvfs_SetSystemCall(sqlite3_vfs *pVfs, const char *zName, sqlite3_syscall_ptr pNew) { (void)pVfs; return orig_vfs->xSetSystemCall ? orig_vfs->xSetSystemCall(orig_vfs, zName, pNew) : SQLITE_ERROR; }
|
|
509
|
+
static sqlite3_syscall_ptr gitvfs_GetSystemCall(sqlite3_vfs *pVfs, const char *zName) { (void)pVfs; return orig_vfs->xGetSystemCall ? orig_vfs->xGetSystemCall(orig_vfs, zName) : NULL; }
|
|
510
|
+
static const char *gitvfs_NextSystemCall(sqlite3_vfs *pVfs, const char *zName) { (void)pVfs; return orig_vfs->xNextSystemCall ? orig_vfs->xNextSystemCall(orig_vfs, zName) : NULL; }
|
|
474
511
|
|
|
475
512
|
/*
|
|
476
513
|
* Entry point to register our Git VFS.
|
|
@@ -516,11 +553,22 @@ int sqlite3_gitvfs_init_impl(const char *base_dir) {
|
|
|
516
553
|
#ifdef _WIN32
|
|
517
554
|
__declspec(dllexport)
|
|
518
555
|
#endif
|
|
519
|
-
int
|
|
556
|
+
int sqlite3_extension_init(sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi) {
|
|
520
557
|
(void)db; (void)pzErrMsg;
|
|
521
558
|
SQLITE_EXTENSION_INIT2(pApi);
|
|
522
559
|
int rc = sqlite3_gitvfs_init_impl(".db");
|
|
523
560
|
return (rc == SQLITE_OK) ? SQLITE_OK_LOAD_PERMANENTLY : rc;
|
|
524
561
|
}
|
|
562
|
+
|
|
563
|
+
#ifdef _WIN32
|
|
564
|
+
__declspec(dllexport)
|
|
565
|
+
#endif
|
|
566
|
+
int sqlite3_gitvfs_init(sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi) {
|
|
567
|
+
return sqlite3_extension_init(db, pzErrMsg, pApi);
|
|
568
|
+
}
|
|
525
569
|
#endif
|
|
526
570
|
|
|
571
|
+
|
|
572
|
+
|
|
573
|
+
|
|
574
|
+
|