extralite 2.5 → 2.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/CHANGELOG.md +34 -13
- data/Gemfile +4 -0
- data/Gemfile-bundle +1 -1
- data/LICENSE +1 -1
- data/README.md +1059 -247
- data/Rakefile +18 -0
- data/TODO.md +0 -7
- data/examples/kv_store.rb +49 -0
- data/examples/multi_fiber.rb +16 -0
- data/examples/on_progress.rb +9 -0
- data/examples/pubsub_store_polyphony.rb +194 -0
- data/examples/pubsub_store_threads.rb +204 -0
- data/ext/extralite/changeset.c +463 -0
- data/ext/extralite/common.c +177 -91
- data/ext/extralite/database.c +745 -276
- data/ext/extralite/extconf-bundle.rb +10 -4
- data/ext/extralite/extconf.rb +34 -34
- data/ext/extralite/extralite.h +104 -47
- data/ext/extralite/extralite_ext.c +6 -0
- data/ext/extralite/iterator.c +14 -86
- data/ext/extralite/query.c +171 -264
- data/extralite-bundle.gemspec +1 -1
- data/extralite.gemspec +1 -1
- data/gemspec.rb +10 -11
- data/lib/extralite/version.rb +1 -1
- data/lib/extralite.rb +69 -10
- data/lib/sequel/adapters/extralite.rb +1 -1
- data/test/helper.rb +9 -1
- data/test/perf_argv_transform.rb +74 -0
- data/test/perf_ary.rb +14 -12
- data/test/perf_hash.rb +17 -15
- data/test/perf_hash_prepared.rb +58 -0
- data/test/perf_hash_transform.rb +66 -0
- data/test/perf_polyphony.rb +74 -0
- data/test/test_changeset.rb +161 -0
- data/test/test_database.rb +720 -104
- data/test/test_extralite.rb +2 -2
- data/test/test_iterator.rb +28 -13
- data/test/test_query.rb +352 -110
- data/test/test_sequel.rb +4 -4
- metadata +24 -16
- data/Gemfile.lock +0 -37
- data/test/perf_prepared.rb +0 -64
@@ -0,0 +1,463 @@
|
|
1
|
+
#ifdef EXTRALITE_ENABLE_CHANGESET
|
2
|
+
#include <stdio.h>
|
3
|
+
#include "extralite.h"
|
4
|
+
|
5
|
+
/*
|
6
|
+
* Document-class: Extralite::Changeset
|
7
|
+
*
|
8
|
+
* This class implements a Changeset for tracking changes to the database.
|
9
|
+
*/
|
10
|
+
|
11
|
+
VALUE cChangeset;
|
12
|
+
|
13
|
+
VALUE SYM_delete;
|
14
|
+
VALUE SYM_insert;
|
15
|
+
VALUE SYM_update;
|
16
|
+
|
17
|
+
static size_t Changeset_size(const void *ptr) {
|
18
|
+
return sizeof(Changeset_t);
|
19
|
+
}
|
20
|
+
|
21
|
+
static void Changeset_free(void *ptr) {
|
22
|
+
Changeset_t *changeset = ptr;
|
23
|
+
if (changeset->changeset_ptr)
|
24
|
+
sqlite3_free(changeset->changeset_ptr);
|
25
|
+
free(ptr);
|
26
|
+
}
|
27
|
+
|
28
|
+
static const rb_data_type_t Changeset_type = {
|
29
|
+
"Changeset",
|
30
|
+
{0, Changeset_free, Changeset_size,},
|
31
|
+
0, 0, RUBY_TYPED_FREE_IMMEDIATELY
|
32
|
+
};
|
33
|
+
|
34
|
+
static VALUE Changeset_allocate(VALUE klass) {
|
35
|
+
Changeset_t *changeset = ALLOC(Changeset_t);
|
36
|
+
changeset->changeset_len = 0;
|
37
|
+
changeset->changeset_ptr = NULL;
|
38
|
+
return TypedData_Wrap_Struct(klass, &Changeset_type, changeset);
|
39
|
+
}
|
40
|
+
|
41
|
+
static inline Changeset_t *self_to_changeset(VALUE obj) {
|
42
|
+
Changeset_t *changeset;
|
43
|
+
TypedData_Get_Struct((obj), Changeset_t, &Changeset_type, (changeset));
|
44
|
+
return changeset;
|
45
|
+
}
|
46
|
+
|
47
|
+
/* Initializes an empty changeset.
|
48
|
+
*
|
49
|
+
* @return [void]
|
50
|
+
*/
|
51
|
+
VALUE Changeset_initialize(VALUE self) {
|
52
|
+
Changeset_t *changeset = self_to_changeset(self);
|
53
|
+
changeset->changeset_len = 0;
|
54
|
+
changeset->changeset_ptr = NULL;
|
55
|
+
return Qnil;
|
56
|
+
}
|
57
|
+
|
58
|
+
static inline VALUE tbl_str(VALUE tbl) {
|
59
|
+
switch (TYPE(tbl)) {
|
60
|
+
case T_NIL:
|
61
|
+
case T_STRING:
|
62
|
+
return tbl;
|
63
|
+
default:
|
64
|
+
return rb_funcall(tbl, ID_to_s, 0);
|
65
|
+
}
|
66
|
+
}
|
67
|
+
|
68
|
+
static inline void Changeset_track_attach(sqlite3 *db, struct sqlite3_session *session, VALUE tables) {
|
69
|
+
long len = RARRAY_LEN(tables);
|
70
|
+
VALUE name = Qnil;
|
71
|
+
for (long i = 0; i < len; i++) {
|
72
|
+
name = tbl_str(RARRAY_AREF(tables, i));
|
73
|
+
int rc = sqlite3session_attach(session, NIL_P(name) ? NULL : StringValueCStr(name));
|
74
|
+
if (rc != SQLITE_OK)
|
75
|
+
rb_raise(cError, "Error while attaching session tables: %s", sqlite3_errstr(rc));
|
76
|
+
}
|
77
|
+
RB_GC_GUARD(name);
|
78
|
+
}
|
79
|
+
|
80
|
+
struct track_ctx {
|
81
|
+
Changeset_t *changeset;
|
82
|
+
sqlite3 *sqlite3_db;
|
83
|
+
sqlite3_session *session;
|
84
|
+
VALUE db;
|
85
|
+
VALUE tables;
|
86
|
+
};
|
87
|
+
|
88
|
+
VALUE safe_track(struct track_ctx *ctx) {
|
89
|
+
int rc;
|
90
|
+
|
91
|
+
if (!NIL_P(ctx->tables))
|
92
|
+
Changeset_track_attach(ctx->sqlite3_db, ctx->session, ctx->tables);
|
93
|
+
else {
|
94
|
+
rc = sqlite3session_attach(ctx->session, NULL);
|
95
|
+
if (rc != SQLITE_OK)
|
96
|
+
rb_raise(cError, "Error while attaching all tables: %s", sqlite3_errstr(rc));
|
97
|
+
}
|
98
|
+
|
99
|
+
rb_yield(ctx->db);
|
100
|
+
|
101
|
+
rc = sqlite3session_changeset(
|
102
|
+
ctx->session,
|
103
|
+
&ctx->changeset->changeset_len,
|
104
|
+
&ctx->changeset->changeset_ptr
|
105
|
+
);
|
106
|
+
if (rc != SQLITE_OK)
|
107
|
+
rb_raise(cError, "Error while collecting changeset from session: %s", sqlite3_errstr(rc));
|
108
|
+
|
109
|
+
return Qnil;
|
110
|
+
}
|
111
|
+
|
112
|
+
VALUE cleanup_track(struct track_ctx *ctx) {
|
113
|
+
sqlite3session_delete(ctx->session);
|
114
|
+
return Qnil;
|
115
|
+
}
|
116
|
+
|
117
|
+
/* Tracks changes in the given block and collects them into the changeset.
|
118
|
+
* Changes are tracked only for the given tables. If nil is supplied as the
|
119
|
+
* given tables, changes are tracked for all tables.
|
120
|
+
*
|
121
|
+
* # track changes for the foo and bar tables
|
122
|
+
* changeset.track(db, [:foo, :bar]) do
|
123
|
+
* run_some_queries
|
124
|
+
* end
|
125
|
+
* store_changes(changeset.to_blob)
|
126
|
+
*
|
127
|
+
* @param db [Extralite::Database] database to track
|
128
|
+
* @param tables [Array<String, Symbol>, nil] tables to track (or nil for all tables)
|
129
|
+
* @return [Extralite::Changeset] changeset
|
130
|
+
*/
|
131
|
+
VALUE Changeset_track(VALUE self, VALUE db, VALUE tables) {
|
132
|
+
Changeset_t *changeset = self_to_changeset(self);
|
133
|
+
Database_t *db_struct = self_to_database(db);
|
134
|
+
sqlite3 *sqlite3_db = db_struct->sqlite3_db;
|
135
|
+
|
136
|
+
if (changeset->changeset_ptr) {
|
137
|
+
sqlite3_free(changeset->changeset_ptr);
|
138
|
+
changeset->changeset_len = 0;
|
139
|
+
changeset->changeset_ptr = NULL;
|
140
|
+
}
|
141
|
+
|
142
|
+
struct track_ctx ctx = {
|
143
|
+
.changeset = changeset,
|
144
|
+
.sqlite3_db = sqlite3_db,
|
145
|
+
.session = NULL,
|
146
|
+
.db = db,
|
147
|
+
.tables = tables
|
148
|
+
};
|
149
|
+
int rc = sqlite3session_create(sqlite3_db, "main", &ctx.session);
|
150
|
+
if (rc != SQLITE_OK)
|
151
|
+
rb_raise(cError, "Error while creating session: %s", sqlite3_errstr(rc));
|
152
|
+
|
153
|
+
rb_ensure(SAFE(safe_track), (VALUE)&ctx, SAFE(cleanup_track), (VALUE)&ctx);
|
154
|
+
|
155
|
+
return self;
|
156
|
+
}
|
157
|
+
|
158
|
+
struct each_ctx {
|
159
|
+
sqlite3_changeset_iter *iter;
|
160
|
+
};
|
161
|
+
|
162
|
+
static inline VALUE op_symbol(int op) {
|
163
|
+
switch (op) {
|
164
|
+
case SQLITE_DELETE:
|
165
|
+
return SYM_delete;
|
166
|
+
case SQLITE_INSERT:
|
167
|
+
return SYM_insert;
|
168
|
+
case SQLITE_UPDATE:
|
169
|
+
return SYM_update;
|
170
|
+
default:
|
171
|
+
rb_raise(cError, "Invalid changeset op code %d", op);
|
172
|
+
}
|
173
|
+
}
|
174
|
+
|
175
|
+
static inline VALUE convert_value(sqlite3_value *value) {
|
176
|
+
if (!value) return Qnil;
|
177
|
+
|
178
|
+
int type = sqlite3_value_type(value);
|
179
|
+
switch (type) {
|
180
|
+
case SQLITE_INTEGER:
|
181
|
+
return LL2NUM(sqlite3_value_int64(value));
|
182
|
+
case SQLITE_FLOAT:
|
183
|
+
return DBL2NUM(sqlite3_value_double(value));
|
184
|
+
case SQLITE_NULL:
|
185
|
+
return Qnil;
|
186
|
+
case SQLITE_BLOB:
|
187
|
+
{
|
188
|
+
int len = sqlite3_value_bytes(value);
|
189
|
+
const void *blob = sqlite3_value_blob(value);
|
190
|
+
return rb_str_new(blob, len);
|
191
|
+
}
|
192
|
+
case SQLITE_TEXT:
|
193
|
+
{
|
194
|
+
int len = sqlite3_value_bytes(value);
|
195
|
+
const void *text = sqlite3_value_text(value);
|
196
|
+
return rb_enc_str_new(text, len, UTF8_ENCODING);
|
197
|
+
}
|
198
|
+
default:
|
199
|
+
rb_raise(cError, "Invalid value type: %d\n", type);
|
200
|
+
}
|
201
|
+
}
|
202
|
+
|
203
|
+
VALUE changeset_iter_info(sqlite3_changeset_iter *iter) {
|
204
|
+
VALUE op = Qnil;
|
205
|
+
VALUE tbl = Qnil;
|
206
|
+
VALUE old_values = Qnil;
|
207
|
+
VALUE new_values = Qnil;
|
208
|
+
VALUE converted = Qnil;
|
209
|
+
VALUE row = rb_ary_new2(4);
|
210
|
+
|
211
|
+
const char *tbl_name;
|
212
|
+
int column_count;
|
213
|
+
int op_int;
|
214
|
+
|
215
|
+
int rc = sqlite3changeset_op(iter, &tbl_name, &column_count, &op_int, NULL);
|
216
|
+
if (rc != SQLITE_OK)
|
217
|
+
rb_raise(cError, "Error while iterating (sqlite3changeset_op): %s", sqlite3_errstr(rc));
|
218
|
+
|
219
|
+
op = op_symbol(op_int);
|
220
|
+
tbl = rb_str_new_cstr(tbl_name);
|
221
|
+
|
222
|
+
if (op_int == SQLITE_UPDATE || op_int == SQLITE_DELETE) {
|
223
|
+
sqlite3_value *value = NULL;
|
224
|
+
old_values = rb_ary_new2(column_count);
|
225
|
+
for (int i = 0; i < column_count; i++) {
|
226
|
+
rc = sqlite3changeset_old(iter, i, &value);
|
227
|
+
if (rc != SQLITE_OK)
|
228
|
+
rb_raise(cError, "Error while iterating (sqlite3changeset_old): %s", sqlite3_errstr(rc));
|
229
|
+
converted = convert_value(value);
|
230
|
+
rb_ary_push(old_values, converted);
|
231
|
+
}
|
232
|
+
}
|
233
|
+
|
234
|
+
if (op_int == SQLITE_UPDATE || op_int == SQLITE_INSERT) {
|
235
|
+
sqlite3_value *value = NULL;
|
236
|
+
new_values = rb_ary_new2(column_count);
|
237
|
+
for (int i = 0; i < column_count; i++) {
|
238
|
+
rc = sqlite3changeset_new(iter, i, &value);
|
239
|
+
if (rc != SQLITE_OK)
|
240
|
+
rb_raise(cError, "Error while iterating (sqlite3changeset_new): %s", sqlite3_errstr(rc));
|
241
|
+
converted = convert_value(value);
|
242
|
+
rb_ary_push(new_values, converted);
|
243
|
+
}
|
244
|
+
}
|
245
|
+
|
246
|
+
rb_ary_push(row, op);
|
247
|
+
rb_ary_push(row, tbl);
|
248
|
+
rb_ary_push(row, old_values);
|
249
|
+
rb_ary_push(row, new_values);
|
250
|
+
|
251
|
+
RB_GC_GUARD(op);
|
252
|
+
RB_GC_GUARD(tbl);
|
253
|
+
RB_GC_GUARD(old_values);
|
254
|
+
RB_GC_GUARD(new_values);
|
255
|
+
RB_GC_GUARD(converted);
|
256
|
+
|
257
|
+
return row;
|
258
|
+
}
|
259
|
+
|
260
|
+
VALUE safe_each(struct each_ctx *ctx) {
|
261
|
+
VALUE row = Qnil;
|
262
|
+
while (sqlite3changeset_next(ctx->iter) == SQLITE_ROW) {
|
263
|
+
row = changeset_iter_info(ctx->iter);
|
264
|
+
rb_yield_splat(row);
|
265
|
+
}
|
266
|
+
|
267
|
+
RB_GC_GUARD(row);
|
268
|
+
return Qnil;
|
269
|
+
}
|
270
|
+
|
271
|
+
VALUE safe_to_a(struct each_ctx *ctx) {
|
272
|
+
VALUE row = Qnil;
|
273
|
+
VALUE array = rb_ary_new();
|
274
|
+
while (sqlite3changeset_next(ctx->iter) == SQLITE_ROW) {
|
275
|
+
row = changeset_iter_info(ctx->iter);
|
276
|
+
rb_ary_push(array, row);
|
277
|
+
}
|
278
|
+
|
279
|
+
RB_GC_GUARD(row);
|
280
|
+
RB_GC_GUARD(array);
|
281
|
+
return array;
|
282
|
+
}
|
283
|
+
|
284
|
+
VALUE cleanup_iter(struct each_ctx *ctx) {
|
285
|
+
int rc = sqlite3changeset_finalize(ctx->iter);
|
286
|
+
if (rc != SQLITE_OK)
|
287
|
+
rb_raise(cError, "Error while finalizing changeset iterator: %s", sqlite3_errstr(rc));
|
288
|
+
|
289
|
+
return Qnil;
|
290
|
+
}
|
291
|
+
|
292
|
+
inline void verify_changeset(Changeset_t *changeset) {
|
293
|
+
if (!changeset->changeset_ptr)
|
294
|
+
rb_raise(cError, "Changeset not available");
|
295
|
+
}
|
296
|
+
|
297
|
+
/* Iterates through the changeset, providing each change to the given block.
|
298
|
+
* Each change entry is an array containing the operation (:insert / :update /
|
299
|
+
* :delete), the table name, an array containing the old values, and an array
|
300
|
+
* containing the new values.
|
301
|
+
*
|
302
|
+
* changeset.each do |(op, table, old_values, new_values)|
|
303
|
+
* ...
|
304
|
+
* end
|
305
|
+
*
|
306
|
+
* @return [Extralite::Changeset] changeset
|
307
|
+
*/
|
308
|
+
VALUE Changeset_each(VALUE self) {
|
309
|
+
Changeset_t *changeset = self_to_changeset(self);
|
310
|
+
verify_changeset(changeset);
|
311
|
+
|
312
|
+
struct each_ctx ctx = { .iter = NULL };
|
313
|
+
int rc = sqlite3changeset_start(&ctx.iter, changeset->changeset_len, changeset->changeset_ptr);
|
314
|
+
if (rc!=SQLITE_OK)
|
315
|
+
rb_raise(cError, "Error while starting iterator: %s", sqlite3_errstr(rc));
|
316
|
+
|
317
|
+
rb_ensure(SAFE(safe_each), (VALUE)&ctx, SAFE(cleanup_iter), (VALUE)&ctx);
|
318
|
+
return self;
|
319
|
+
}
|
320
|
+
|
321
|
+
/* Returns an array containing all changes in the changeset. Each change entry
|
322
|
+
* is an array containing the operation (:insert / :update / :delete), the table
|
323
|
+
* name, an array containing the old values, and an array containing the new
|
324
|
+
* values.
|
325
|
+
*
|
326
|
+
* @return [Array<Array>] changes in the changeset
|
327
|
+
*/
|
328
|
+
VALUE Changeset_to_a(VALUE self) {
|
329
|
+
Changeset_t *changeset = self_to_changeset(self);
|
330
|
+
verify_changeset(changeset);
|
331
|
+
|
332
|
+
struct each_ctx ctx = { .iter = NULL };
|
333
|
+
int rc = sqlite3changeset_start(&ctx.iter, changeset->changeset_len, changeset->changeset_ptr);
|
334
|
+
if (rc!=SQLITE_OK)
|
335
|
+
rb_raise(cError, "Error while starting iterator: %s", sqlite3_errstr(rc));
|
336
|
+
|
337
|
+
return rb_ensure(SAFE(safe_to_a), (VALUE)&ctx, SAFE(cleanup_iter), (VALUE)&ctx);
|
338
|
+
}
|
339
|
+
|
340
|
+
// copied from: https://sqlite.org/sessionintro.html
|
341
|
+
static int xConflict(void *pCtx, int eConflict, sqlite3_changeset_iter *pIter){
|
342
|
+
int ret = (int)pCtx;
|
343
|
+
return ret;
|
344
|
+
}
|
345
|
+
|
346
|
+
/* Applies the changeset to the given database.
|
347
|
+
*
|
348
|
+
* @param db [Extralite::Database] database to apply changes to
|
349
|
+
* @return [Extralite::Changeset] changeset
|
350
|
+
*/
|
351
|
+
VALUE Changeset_apply(VALUE self, VALUE db) {
|
352
|
+
Changeset_t *changeset = self_to_changeset(self);
|
353
|
+
verify_changeset(changeset);
|
354
|
+
|
355
|
+
Database_t *db_struct = self_to_database(db);
|
356
|
+
sqlite3 *sqlite3_db = db_struct->sqlite3_db;
|
357
|
+
|
358
|
+
int rc = sqlite3changeset_apply(
|
359
|
+
sqlite3_db,
|
360
|
+
changeset->changeset_len,
|
361
|
+
changeset->changeset_ptr,
|
362
|
+
NULL,
|
363
|
+
xConflict,
|
364
|
+
(void*)1
|
365
|
+
);
|
366
|
+
if (rc != SQLITE_OK)
|
367
|
+
rb_raise(cError, "Error while applying changeset: %s", sqlite3_errstr(rc));
|
368
|
+
|
369
|
+
return self;
|
370
|
+
}
|
371
|
+
|
372
|
+
/* Returns an inverted changeset. The inverted changeset can be used to undo the
|
373
|
+
* changes in the original changeset.
|
374
|
+
*
|
375
|
+
* # undo changes
|
376
|
+
* changeset.invert.apply(db)
|
377
|
+
*
|
378
|
+
* @return [Extralite::Changeset] inverted changeset
|
379
|
+
*/
|
380
|
+
VALUE Changeset_invert(VALUE self) {
|
381
|
+
Changeset_t *changeset = self_to_changeset(self);
|
382
|
+
verify_changeset(changeset);
|
383
|
+
|
384
|
+
VALUE inverted = rb_funcall(cChangeset, ID_new, 0);
|
385
|
+
Changeset_t *inverted_changeset = self_to_changeset(inverted);
|
386
|
+
|
387
|
+
int rc = sqlite3changeset_invert(
|
388
|
+
changeset->changeset_len, changeset->changeset_ptr,
|
389
|
+
&inverted_changeset->changeset_len, &inverted_changeset->changeset_ptr
|
390
|
+
);
|
391
|
+
if (rc != SQLITE_OK)
|
392
|
+
rb_raise(cError, "Error while inverting changeset: %s", sqlite3_errstr(rc));
|
393
|
+
|
394
|
+
RB_GC_GUARD(inverted);
|
395
|
+
return inverted;
|
396
|
+
}
|
397
|
+
|
398
|
+
/* Returns a string BLOB containing the changeset in serialized form. The
|
399
|
+
* changeset BLOB can be stored to file for later retrieval.
|
400
|
+
*
|
401
|
+
* File.open('my.changes', 'w+') { |f| f << changeset.to_blob }
|
402
|
+
*
|
403
|
+
* @return [String] changeset BLOB
|
404
|
+
*/
|
405
|
+
VALUE Changeset_to_blob(VALUE self) {
|
406
|
+
Changeset_t *changeset = self_to_changeset(self);
|
407
|
+
|
408
|
+
if (changeset->changeset_ptr)
|
409
|
+
return rb_str_new(changeset->changeset_ptr, changeset->changeset_len);
|
410
|
+
else
|
411
|
+
return rb_str_new("", 0);
|
412
|
+
}
|
413
|
+
|
414
|
+
/* Loads a changeset from the given string. This method can be used to load a
|
415
|
+
* changeset from a file in order to apply it to a database.
|
416
|
+
*
|
417
|
+
* changeset = Extralite::Changeset.new
|
418
|
+
* changeset.load(IO.read('my.changes'))
|
419
|
+
* changeset.apply(db)
|
420
|
+
*
|
421
|
+
* @param blob [String] changeset BLOB
|
422
|
+
* @return [Extralite::Changeset] changeset
|
423
|
+
*/
|
424
|
+
VALUE Changeset_load(VALUE self, VALUE blob) {
|
425
|
+
Changeset_t *changeset = self_to_changeset(self);
|
426
|
+
if (changeset->changeset_ptr) {
|
427
|
+
sqlite3_free(changeset->changeset_ptr);
|
428
|
+
changeset->changeset_ptr = NULL;
|
429
|
+
changeset->changeset_len = 0;
|
430
|
+
}
|
431
|
+
|
432
|
+
changeset->changeset_len = RSTRING_LEN(blob);
|
433
|
+
changeset->changeset_ptr = sqlite3_malloc(changeset->changeset_len);
|
434
|
+
memcpy(changeset->changeset_ptr, RSTRING_PTR(blob), changeset->changeset_len);
|
435
|
+
|
436
|
+
return self;
|
437
|
+
}
|
438
|
+
|
439
|
+
void Init_ExtraliteChangeset(void) {
|
440
|
+
VALUE mExtralite = rb_define_module("Extralite");
|
441
|
+
|
442
|
+
cChangeset = rb_define_class_under(mExtralite, "Changeset", rb_cObject);
|
443
|
+
rb_define_alloc_func(cChangeset, Changeset_allocate);
|
444
|
+
|
445
|
+
rb_define_method(cChangeset, "initialize", Changeset_initialize, 0);
|
446
|
+
|
447
|
+
rb_define_method(cChangeset, "apply", Changeset_apply, 1);
|
448
|
+
rb_define_method(cChangeset, "each", Changeset_each, 0);
|
449
|
+
rb_define_method(cChangeset, "invert", Changeset_invert, 0);
|
450
|
+
rb_define_method(cChangeset, "load", Changeset_load, 1);
|
451
|
+
rb_define_method(cChangeset, "to_a", Changeset_to_a, 0);
|
452
|
+
rb_define_method(cChangeset, "to_blob", Changeset_to_blob, 0);
|
453
|
+
rb_define_method(cChangeset, "track", Changeset_track, 2);
|
454
|
+
|
455
|
+
SYM_delete = ID2SYM(rb_intern("delete"));
|
456
|
+
SYM_insert = ID2SYM(rb_intern("insert"));
|
457
|
+
SYM_update = ID2SYM(rb_intern("update"));
|
458
|
+
|
459
|
+
rb_gc_register_mark_object(SYM_delete);
|
460
|
+
rb_gc_register_mark_object(SYM_insert);
|
461
|
+
rb_gc_register_mark_object(SYM_update);
|
462
|
+
}
|
463
|
+
#endif
|