LuaSQLite3

Check-in [bb4d13eba2]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Add commit_hook and rollback_hook to complement update_hook.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: bb4d13eba22750f8e99f6a9255622f8e42d7b037
User & Date: e 2013-04-01 18:02:05
Context
2013-04-05
17:20
Added tests and example contributed by Dmitry Pashkevich check-in: 29a180ad4e user: e tags: fsl_9, trunk
2013-04-01
18:02
Add commit_hook and rollback_hook to complement update_hook. check-in: bb4d13eba2 user: e tags: trunk
2013-03-31
13:01
Update documentation. Add unit tests for update_hook. Include extra fikes for deprecated make. check-in: 6337de362e user: e tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to doc/lsqlite3.wiki.

25
26
27
28
29
30
31

32
33
34
35
36
37
38
39
40
41
42
43

44
45
46
47
48
49
50
...
268
269
270
271
272
273
274










275
276
277
278
279
280
281
...
493
494
495
496
497
498
499









500
501
502
503
504
505
506
...
551
552
553
554
555
556
557

558
559
560
561
562
563
564
	<ul>

		<li><a href="#db_busy_handler">db:busy_handler</a></li>
		<li><a href="#db_busy_timeout">db:busy_timeout</a></li>
		<li><a href="#db_changes">db:changes</a></li>
		<li><a href="#db_close">db:close</a></li>
		<li><a href="#db_close_vm">db:close_vm</a></li>

		<li><a href="#db_create_aggregate">db:create_aggregate</a></li>
		<li><a href="#db_create_collation">db:create_collation</a></li>
		<li><a href="#db_create_function">db:create_function</a></li>
		<li><a href="#db_errcode">db:errcode</a></li>
		<li><a href="#db_errmsg">db:errmsg</a></li>
		<li><a href="#db_exec">db:exec</a></li>
		<li><a href="#db_interrupt">db:interrupt</a></li>
		<li><a href="#db_isopen">db:isopen</a></li>
		<li><a href="#db_last_insert_rowid">db:last_insert_rowid</a></li>
		<li><a href="#db_nrows">db:nrows</a></li>
		<li><a href="#db_prepare">db:prepare</a></li>
		<li><a href="#db_progress_handler">db:progress_handler</a></li>

		<li><a href="#db_rows">db:rows</a></li>
		<li><a href="#db_total_changes">db:total_changes</a></li>
        <li><a href="#db_trace">db:trace</a></li>
        <li><a href="#db_update_hook">db:update_hook</a></li>
		<li><a href="#db_urows">db:urows</a></li>
	</ul>

................................................................................
<h2><a name="db_close_vm">db:close_vm</a></h2>
<pre>
        db:close_vm(temponly)</pre>
<p>Finalizes all statements that have not been explicitly finalized. If
<code>temponly</code> is true, only internal, temporary statements are finalized.
This function returns nothing.</p>
<p>










</p>
<h2><a name="db_create_aggregate">db:create_aggregate</a></h2>
<pre>
        db:create_aggregate(name,nargs,step,final)</pre>
<p>This function creates an aggregate callback function. Aggregates perform
an operation over all rows in a query. <code>name</code> is a string with the name 
of the aggregate function as given in an SQL statement; <code>nargs</code> is the 
................................................................................
as the second argument.</p>
<p>If the progress callback returns a result other than 0, then the current
query is immediately terminated, any database changes are rolled back
and the containing <code>db:exec()</code> or <code>stmt:step()</code> call returns
<code>sqlite3.INTERRUPT</code>. This feature can be used to cancel long-running
queries.</p>
<p>









</p>
<h2><a name="db_rows">db:rows</a></h2>
<pre>
        db:rows(sql)</pre>
<p>Creates an iterator that returns the successive rows selected by the SQL
statement given in string <code>sql</code>. Each call to the iterator returns a table
in which the numerical indices 1 to n correspond to the selected columns
................................................................................
<code>func</code> is a Lua function that is invoked by SQLite3 whenever a row is updated, 
inserted or deleted. This callback receives five arguments: the first is the 
<code>udata</code> argument used when the callback was installed; the second is an 
integer indicating the operation that caused the callback to be invoked (one of <code>sqlite3.UPDATE</code>,
<code>sqlite3.INSERT</code>, or <code>sqlite3.DELETE</code>). The third and fourth arguments 
are the database and table name containing the affected row. The final callback parameter is 
the rowid of the row. In the case of an update, this is the rowid after the update takes place.</p>

<p>
</p>
<h2><a name="db_urows">db:urows</a></h2>
<pre>
        db:urows(sql)</pre>
<p>Creates an iterator that returns the successive rows selected by the SQL
statement given in string <code>sql</code>. Each call to the iterator returns the







>












>







 







>
>
>
>
>
>
>
>
>
>







 







>
>
>
>
>
>
>
>
>







 







>







25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
...
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
...
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
...
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
	<ul>

		<li><a href="#db_busy_handler">db:busy_handler</a></li>
		<li><a href="#db_busy_timeout">db:busy_timeout</a></li>
		<li><a href="#db_changes">db:changes</a></li>
		<li><a href="#db_close">db:close</a></li>
		<li><a href="#db_close_vm">db:close_vm</a></li>
        <li><a href="#db_commit_hook">db:commit_hook</a></li>
		<li><a href="#db_create_aggregate">db:create_aggregate</a></li>
		<li><a href="#db_create_collation">db:create_collation</a></li>
		<li><a href="#db_create_function">db:create_function</a></li>
		<li><a href="#db_errcode">db:errcode</a></li>
		<li><a href="#db_errmsg">db:errmsg</a></li>
		<li><a href="#db_exec">db:exec</a></li>
		<li><a href="#db_interrupt">db:interrupt</a></li>
		<li><a href="#db_isopen">db:isopen</a></li>
		<li><a href="#db_last_insert_rowid">db:last_insert_rowid</a></li>
		<li><a href="#db_nrows">db:nrows</a></li>
		<li><a href="#db_prepare">db:prepare</a></li>
		<li><a href="#db_progress_handler">db:progress_handler</a></li>
        <li><a href="#db_rollback_hook">db:rollback_hook</a></li>
		<li><a href="#db_rows">db:rows</a></li>
		<li><a href="#db_total_changes">db:total_changes</a></li>
        <li><a href="#db_trace">db:trace</a></li>
        <li><a href="#db_update_hook">db:update_hook</a></li>
		<li><a href="#db_urows">db:urows</a></li>
	</ul>

................................................................................
<h2><a name="db_close_vm">db:close_vm</a></h2>
<pre>
        db:close_vm(temponly)</pre>
<p>Finalizes all statements that have not been explicitly finalized. If
<code>temponly</code> is true, only internal, temporary statements are finalized.
This function returns nothing.</p>
<p>
</p>
<h2><a name="db_commit_hook">db:commit_hook</a></h2>
<pre>
        db:commit_hook(func,udata)</pre>
<p>This function installs a commit_hook callback handler. <code>func</code> is a Lua function 
that is invoked by SQLite3 whenever a transaction is commited. This callback receives one argument: 
the <code>udata</code> argument used when the callback was installed. If <code>func</code> returns <code>false</code>
or <code>nil</code> the COMMIT is allowed to prodeed, otherwise the COMMIT is converted to a ROLLBACK.</p> 
<p>See: <a href="#db_rollback_hook">db:rollback_hook</a> and <a href="#db_update_hook">db:update_hook</a></p>
<p>
</p>
<h2><a name="db_create_aggregate">db:create_aggregate</a></h2>
<pre>
        db:create_aggregate(name,nargs,step,final)</pre>
<p>This function creates an aggregate callback function. Aggregates perform
an operation over all rows in a query. <code>name</code> is a string with the name 
of the aggregate function as given in an SQL statement; <code>nargs</code> is the 
................................................................................
as the second argument.</p>
<p>If the progress callback returns a result other than 0, then the current
query is immediately terminated, any database changes are rolled back
and the containing <code>db:exec()</code> or <code>stmt:step()</code> call returns
<code>sqlite3.INTERRUPT</code>. This feature can be used to cancel long-running
queries.</p>
<p>
</p>
<h2><a name="db_rollback_hook">db:rollback_hook</a></h2>
<pre>
        db:rollback_hook(func,udata)</pre>
<p>This function installs a rollback_hook callback handler. <code>func</code> is a Lua function 
that is invoked by SQLite3 whenever a transaction is rolled back. This callback receives one argument: 
the <code>udata</code> argument used when the callback was installed.</p> 
<p>See: <a href="#db_commit_hook">db:commit_hook</a> and <a href="#db_update_hook">db:update_hook</a></p>
<p>
</p>
<h2><a name="db_rows">db:rows</a></h2>
<pre>
        db:rows(sql)</pre>
<p>Creates an iterator that returns the successive rows selected by the SQL
statement given in string <code>sql</code>. Each call to the iterator returns a table
in which the numerical indices 1 to n correspond to the selected columns
................................................................................
<code>func</code> is a Lua function that is invoked by SQLite3 whenever a row is updated, 
inserted or deleted. This callback receives five arguments: the first is the 
<code>udata</code> argument used when the callback was installed; the second is an 
integer indicating the operation that caused the callback to be invoked (one of <code>sqlite3.UPDATE</code>,
<code>sqlite3.INSERT</code>, or <code>sqlite3.DELETE</code>). The third and fourth arguments 
are the database and table name containing the affected row. The final callback parameter is 
the rowid of the row. In the case of an update, this is the rowid after the update takes place.</p>
<p>See: <a href="#db_commit_hook">db:commit_hook</a> and <a href="#db_rollback_hook">db:rollback_hook</a></p>
<p>
</p>
<h2><a name="db_urows">db:urows</a></h2>
<pre>
        db:urows(sql)</pre>
<p>Creates an iterator that returns the successive rows selected by the SQL
statement given in string <code>sql</code>. Each call to the iterator returns the

Changes to lsqlite3.c.

49
50
51
52
53
54
55



56
57
58
59
60
61
62
..
87
88
89
90
91
92
93


94
95








96
97
98
99
100
101
102
...
592
593
594
595
596
597
598

599
600






601
602
603
604
605
606
607
...
641
642
643
644
645
646
647

648
649





650
651
652
653
654
655
656
....
1218
1219
1220
1221
1222
1223
1224


1225
1226
1227
1228
1229
1230
1231
....
1282
1283
1284
1285
1286
1287
1288




















































































































1289
1290
1291
1292
1293
1294
1295
....
1859
1860
1861
1862
1863
1864
1865

1866



1867
1868
1869
1870
1871
1872
1873

#include "sqlite3.h"

/* compile time features */
#if !defined(SQLITE_OMIT_PROGRESS_CALLBACK)
    #define SQLITE_OMIT_PROGRESS_CALLBACK 0
#endif




typedef struct sdb sdb;
typedef struct sdb_vm sdb_vm;
typedef struct sdb_func sdb_func;

/* to use as C user data so i know what function sqlite is calling */
struct sdb_func {
................................................................................

    int progress_cb;    /* progress handler */
    int progress_udata;

    int trace_cb;       /* trace callback */
    int trace_udata;



    int update_hook_cb; /* update_hook callback */
    int update_hook_udata;








};

static const char *sqlite_meta      = ":sqlite3";
static const char *sqlite_vm_meta   = ":sqlite3:vm";
static const char *sqlite_ctx_meta  = ":sqlite3:ctx";
static int sqlite_ctx_meta_ref;

................................................................................

    db->busy_cb =
    db->busy_udata =
    db->progress_cb =
    db->progress_udata =
    db->trace_cb =
    db->trace_udata = 

    db->update_hook_cb =
    db->update_hook_udata = LUA_NOREF;







    luaL_getmetatable(L, sqlite_meta);
    lua_setmetatable(L, -2);        /* set metatable */

    /* to keep track of 'open' virtual machines */
    lua_pushlightuserdata(L, db);
    lua_newtable(L);
................................................................................
    /* 'free' all references */
    luaL_unref(L, LUA_REGISTRYINDEX, db->busy_cb);
    luaL_unref(L, LUA_REGISTRYINDEX, db->busy_udata);
    luaL_unref(L, LUA_REGISTRYINDEX, db->progress_cb);
    luaL_unref(L, LUA_REGISTRYINDEX, db->progress_udata);
    luaL_unref(L, LUA_REGISTRYINDEX, db->trace_cb);
    luaL_unref(L, LUA_REGISTRYINDEX, db->trace_udata);

    luaL_unref(L, LUA_REGISTRYINDEX, db->update_hook_cb);
    luaL_unref(L, LUA_REGISTRYINDEX, db->update_hook_udata);






    /* close database */
    result = sqlite3_close(db->db);
    db->db = NULL;

    /* free associated memory with created functions */
    func = db->func;
................................................................................
        /* set trace handler */
        sqlite3_trace(db->db, db_trace_callback, db);
    }

    return 0;
}



/*
** update_hook callback:
** Params: database, callback function, userdata
**
** callback function:
** Params: userdata, {one of SQLITE_INSERT, SQLITE_DELETE, or SQLITE_UPDATE}, 
**          database name, table name (containing the affected row), rowid of the row
................................................................................
        /* set update_hook handler */
        sqlite3_update_hook(db->db, db_update_hook_callback, db);
    }

    return 0;
}





















































































































#if !defined(SQLITE_OMIT_PROGRESS_CALLBACK) || !SQLITE_OMIT_PROGRESS_CALLBACK

/*
** progress handler:
** Params: database, number of opcodes, callback function, userdata
**
** callback function:
................................................................................
    {"create_aggregate",    db_create_aggregate     },
    {"create_collation",    db_create_collation     },

    {"trace",               db_trace                },
    {"progress_handler",    db_progress_handler     },
    {"busy_timeout",        db_busy_timeout         },
    {"busy_handler",        db_busy_handler         },

    {"update_hook",         db_update_hook          },




    {"prepare",             db_prepare              },
    {"rows",                db_rows                 },
    {"urows",               db_urows                },
    {"nrows",               db_nrows                },

    {"exec",                db_exec                 },







>
>
>







 







>
>


>
>
>
>
>
>
>
>







 







>

|
>
>
>
>
>
>







 







>


>
>
>
>
>







 







>
>







 







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







 







>

>
>
>







49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
..
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
...
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
...
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
....
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
....
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
....
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021

#include "sqlite3.h"

/* compile time features */
#if !defined(SQLITE_OMIT_PROGRESS_CALLBACK)
    #define SQLITE_OMIT_PROGRESS_CALLBACK 0
#endif
#if !defined(LSQLITE_OMIT_UPDATE_HOOK)
    #define LSQLITE_OMIT_UPDATE_HOOK 0
#endif

typedef struct sdb sdb;
typedef struct sdb_vm sdb_vm;
typedef struct sdb_func sdb_func;

/* to use as C user data so i know what function sqlite is calling */
struct sdb_func {
................................................................................

    int progress_cb;    /* progress handler */
    int progress_udata;

    int trace_cb;       /* trace callback */
    int trace_udata;

#if !defined(LSQLITE_OMIT_UPDATE_HOOK) || !LSQLITE_OMIT_UPDATE_HOOK

    int update_hook_cb; /* update_hook callback */
    int update_hook_udata;

    int commit_hook_cb; /* commit_hook callback */
    int commit_hook_udata;

    int rollback_hook_cb; /* rollback_hook callback */
    int rollback_hook_udata;

#endif
};

static const char *sqlite_meta      = ":sqlite3";
static const char *sqlite_vm_meta   = ":sqlite3:vm";
static const char *sqlite_ctx_meta  = ":sqlite3:ctx";
static int sqlite_ctx_meta_ref;

................................................................................

    db->busy_cb =
    db->busy_udata =
    db->progress_cb =
    db->progress_udata =
    db->trace_cb =
    db->trace_udata = 
#if !defined(LSQLITE_OMIT_UPDATE_HOOK) || !LSQLITE_OMIT_UPDATE_HOOK
    db->update_hook_cb =
    db->update_hook_udata =
    db->commit_hook_cb =
    db->commit_hook_udata =
    db->rollback_hook_cb =
    db->rollback_hook_udata =
#endif
     LUA_NOREF;

    luaL_getmetatable(L, sqlite_meta);
    lua_setmetatable(L, -2);        /* set metatable */

    /* to keep track of 'open' virtual machines */
    lua_pushlightuserdata(L, db);
    lua_newtable(L);
................................................................................
    /* 'free' all references */
    luaL_unref(L, LUA_REGISTRYINDEX, db->busy_cb);
    luaL_unref(L, LUA_REGISTRYINDEX, db->busy_udata);
    luaL_unref(L, LUA_REGISTRYINDEX, db->progress_cb);
    luaL_unref(L, LUA_REGISTRYINDEX, db->progress_udata);
    luaL_unref(L, LUA_REGISTRYINDEX, db->trace_cb);
    luaL_unref(L, LUA_REGISTRYINDEX, db->trace_udata);
#if !defined(LSQLITE_OMIT_UPDATE_HOOK) || !LSQLITE_OMIT_UPDATE_HOOK
    luaL_unref(L, LUA_REGISTRYINDEX, db->update_hook_cb);
    luaL_unref(L, LUA_REGISTRYINDEX, db->update_hook_udata);
    luaL_unref(L, LUA_REGISTRYINDEX, db->commit_hook_cb);
    luaL_unref(L, LUA_REGISTRYINDEX, db->commit_hook_udata);
    luaL_unref(L, LUA_REGISTRYINDEX, db->rollback_hook_cb);
    luaL_unref(L, LUA_REGISTRYINDEX, db->rollback_hook_udata);
#endif

    /* close database */
    result = sqlite3_close(db->db);
    db->db = NULL;

    /* free associated memory with created functions */
    func = db->func;
................................................................................
        /* set trace handler */
        sqlite3_trace(db->db, db_trace_callback, db);
    }

    return 0;
}

#if !defined(LSQLITE_OMIT_UPDATE_HOOK) || !LSQLITE_OMIT_UPDATE_HOOK

/*
** update_hook callback:
** Params: database, callback function, userdata
**
** callback function:
** Params: userdata, {one of SQLITE_INSERT, SQLITE_DELETE, or SQLITE_UPDATE}, 
**          database name, table name (containing the affected row), rowid of the row
................................................................................
        /* set update_hook handler */
        sqlite3_update_hook(db->db, db_update_hook_callback, db);
    }

    return 0;
}

/*
** commit_hook callback:
** Params: database, callback function, userdata
**
** callback function:
** Params: userdata
** Returned value: Return false or nil to continue the COMMIT operation normally.
**  return true (non false, non nil), then the COMMIT is converted into a ROLLBACK. 
*/
static int db_commit_hook_callback(void *user) {
    sdb *db = (sdb*)user;
    lua_State *L = db->L;
    int top = lua_gettop(L);
    int rollback = 0;

    /* setup lua callback call */
    lua_rawgeti(L, LUA_REGISTRYINDEX, db->commit_hook_cb);    /* get callback */
    lua_rawgeti(L, LUA_REGISTRYINDEX, db->commit_hook_udata); /* get callback user data */

    /* call lua function */
    if (!lua_pcall(L, 1, 1, 0))
        rollback = lua_toboolean(L, -1); /* use result if there was no error */

    lua_settop(L, top);
    return rollback;
}

static int db_commit_hook(lua_State *L) {
    sdb *db = lsqlite_checkdb(L, 1);

    if (lua_gettop(L) < 2 || lua_isnil(L, 2)) {
        luaL_unref(L, LUA_REGISTRYINDEX, db->commit_hook_cb);
        luaL_unref(L, LUA_REGISTRYINDEX, db->commit_hook_udata);

        db->commit_hook_cb =
        db->commit_hook_udata = LUA_NOREF;

        /* clear commit_hook handler */
        sqlite3_commit_hook(db->db, NULL, NULL);
    }
    else {
        luaL_checktype(L, 2, LUA_TFUNCTION);

        /* make sure we have an userdata field (even if nil) */
        lua_settop(L, 3);

        luaL_unref(L, LUA_REGISTRYINDEX, db->commit_hook_cb);
        luaL_unref(L, LUA_REGISTRYINDEX, db->commit_hook_udata);

        db->commit_hook_udata = luaL_ref(L, LUA_REGISTRYINDEX);
        db->commit_hook_cb = luaL_ref(L, LUA_REGISTRYINDEX);

        /* set commit_hook handler */
        sqlite3_commit_hook(db->db, db_commit_hook_callback, db);
    }

    return 0;
}

/*
** rollback hook callback:
** Params: database, callback function, userdata
**
** callback function:
** Params: userdata
*/
static void db_rollback_hook_callback(void *user) {
    sdb *db = (sdb*)user;
    lua_State *L = db->L;
    int top = lua_gettop(L);

    /* setup lua callback call */
    lua_rawgeti(L, LUA_REGISTRYINDEX, db->rollback_hook_cb);    /* get callback */
    lua_rawgeti(L, LUA_REGISTRYINDEX, db->rollback_hook_udata); /* get callback user data */

    /* call lua function */
    lua_pcall(L, 1, 0, 0);
    /* ignore any error generated by this function */

    lua_settop(L, top);
}

static int db_rollback_hook(lua_State *L) {
    sdb *db = lsqlite_checkdb(L, 1);

    if (lua_gettop(L) < 2 || lua_isnil(L, 2)) {
        luaL_unref(L, LUA_REGISTRYINDEX, db->rollback_hook_cb);
        luaL_unref(L, LUA_REGISTRYINDEX, db->rollback_hook_udata);

        db->rollback_hook_cb =
        db->rollback_hook_udata = LUA_NOREF;

        /* clear rollback_hook handler */
        sqlite3_rollback_hook(db->db, NULL, NULL);
    }
    else {
        luaL_checktype(L, 2, LUA_TFUNCTION);

        /* make sure we have an userdata field (even if nil) */
        lua_settop(L, 3);

        luaL_unref(L, LUA_REGISTRYINDEX, db->rollback_hook_cb);
        luaL_unref(L, LUA_REGISTRYINDEX, db->rollback_hook_udata);

        db->rollback_hook_udata = luaL_ref(L, LUA_REGISTRYINDEX);
        db->rollback_hook_cb = luaL_ref(L, LUA_REGISTRYINDEX);

        /* set rollback_hook handler */
        sqlite3_rollback_hook(db->db, db_rollback_hook_callback, db);
    }

    return 0;
}

#endif /* #if !defined(LSQLITE_OMIT_UPDATE_HOOK) || !LSQLITE_OMIT_UPDATE_HOOK */

#if !defined(SQLITE_OMIT_PROGRESS_CALLBACK) || !SQLITE_OMIT_PROGRESS_CALLBACK

/*
** progress handler:
** Params: database, number of opcodes, callback function, userdata
**
** callback function:
................................................................................
    {"create_aggregate",    db_create_aggregate     },
    {"create_collation",    db_create_collation     },

    {"trace",               db_trace                },
    {"progress_handler",    db_progress_handler     },
    {"busy_timeout",        db_busy_timeout         },
    {"busy_handler",        db_busy_handler         },
#if !defined(LSQLITE_OMIT_UPDATE_HOOK) || !LSQLITE_OMIT_UPDATE_HOOK
    {"update_hook",         db_update_hook          },
    {"commit_hook",         db_commit_hook          },
    {"rollback_hook",       db_rollback_hook        },
#endif

    {"prepare",             db_prepare              },
    {"rows",                db_rows                 },
    {"urows",               db_urows                },
    {"nrows",               db_nrows                },

    {"exec",                db_exec                 },

Changes to test/tests-sqlite3.lua.

504
505
506
507
508
509
510

511
512
513
514
515
516
517
...
589
590
591
592
593
594
595































































596
597
598
599
600
601
602
----------------------------

uh = lunit_TestCase("Update Hook")

function uh.setup()
  uh.db = assert( sqlite3.open_memory() )
  uh.udtbl = {[sqlite3.INSERT]=0, [sqlite3.UPDATE]=0, [sqlite3.DELETE]=0}

  uh.uttblsz = function () local sz = 0; for _,_ in pairs(uh.udtbl) do sz = sz + 1 end return sz end
  assert_number( uh.db:exec("CREATE TABLE test ( id INTEGER PRIMARY KEY, content VARCHAR );") )
end

function uh.teardown()
  assert_number( uh.db:close() )
end
................................................................................
  assert_equal( 3, uh.udtbl[sqlite3.INSERT] )
  assert_equal( 1, uh.udtbl[sqlite3.UPDATE] )
  assert_equal( 1, uh.udtbl[sqlite3.DELETE] )
  assert_equal( 3, uh.uttblsz() )
end




































































--------------------------------------------
-- Tests loop break and statement reusage --
--------------------------------------------








>







 







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
...
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
----------------------------

uh = lunit_TestCase("Update Hook")

function uh.setup()
  uh.db = assert( sqlite3.open_memory() )
  uh.udtbl = {[sqlite3.INSERT]=0, [sqlite3.UPDATE]=0, [sqlite3.DELETE]=0}
  uh.crtbl = {0, 0}
  uh.uttblsz = function () local sz = 0; for _,_ in pairs(uh.udtbl) do sz = sz + 1 end return sz end
  assert_number( uh.db:exec("CREATE TABLE test ( id INTEGER PRIMARY KEY, content VARCHAR );") )
end

function uh.teardown()
  assert_number( uh.db:close() )
end
................................................................................
  assert_equal( 3, uh.udtbl[sqlite3.INSERT] )
  assert_equal( 1, uh.udtbl[sqlite3.UPDATE] )
  assert_equal( 1, uh.udtbl[sqlite3.DELETE] )
  assert_equal( 3, uh.uttblsz() )
end


function uh.test_xinsert3update1delete1()
  assert_nil(uh.db:update_hook( function(ud, op, dname, tname, rowid)
    ud[op] = ud[op] + 1
  end, uh.udtbl))
  assert_nil( uh.db:commit_hook(function (ud) ud[1] = ud[1] + 1; return false end, uh.crtbl))
  assert_nil( uh.db:rollback_hook(function (ud) ud[2] = ud[2] + 1 end, uh.crtbl))
  assert_equal( sqlite3.OK, uh.db:exec("BEGIN;") )
  assert_equal( sqlite3.OK, uh.db:exec("INSERT INTO test VALUES (NULL, 'Hello World');") )
  assert_equal( sqlite3.OK, uh.db:exec("INSERT INTO test VALUES (NULL, 'Hello Lua');") )
  assert_equal( sqlite3.OK, uh.db:exec("INSERT INTO test VALUES (NULL, 'Hello Sqlite3');") )
  assert_equal( sqlite3.OK, uh.db:exec("UPDATE test SET content = 'Hello Again World' WHERE id = 1;") )
  assert_equal( sqlite3.OK, uh.db:exec("DELETE FROM test WHERE id = 2;") )
  assert_equal( sqlite3.OK, uh.db:exec("COMMIT;") )
  --for k,v in pairs(uh.udtbl) do print(k,v) end
  assert_equal( 3, uh.udtbl[sqlite3.INSERT] )
  assert_equal( 1, uh.udtbl[sqlite3.UPDATE] )
  assert_equal( 1, uh.udtbl[sqlite3.DELETE] )
  assert_equal( 3, uh.uttblsz() )
  assert_equal( 1, uh.crtbl[1] )
  assert_equal( 0, uh.crtbl[2] )
end

function uh.test_yinsert3update1delete1()
  assert_nil(uh.db:update_hook( function(ud, op, dname, tname, rowid)
    ud[op] = ud[op] + 1
  end, uh.udtbl))
  assert_nil( uh.db:commit_hook(function (ud) ud[1] = ud[1] + 1; return false end, uh.crtbl))
  assert_nil( uh.db:rollback_hook(function (ud) ud[2] = ud[2] + 1 end, uh.crtbl))
  assert_equal( sqlite3.OK, uh.db:exec("BEGIN;") )
  assert_equal( sqlite3.OK, uh.db:exec("INSERT INTO test VALUES (NULL, 'Hello World');") )
  assert_equal( sqlite3.OK, uh.db:exec("INSERT INTO test VALUES (NULL, 'Hello Lua');") )
  assert_equal( sqlite3.OK, uh.db:exec("INSERT INTO test VALUES (NULL, 'Hello Sqlite3');") )
  assert_equal( sqlite3.OK, uh.db:exec("UPDATE test SET content = 'Hello Again World' WHERE id = 1;") )
  assert_equal( sqlite3.OK, uh.db:exec("DELETE FROM test WHERE id = 2;") )
  assert_equal( sqlite3.OK, uh.db:exec("ROLLBACK;") )
  --for k,v in pairs(uh.udtbl) do print(k,v) end
  assert_equal( 3, uh.udtbl[sqlite3.INSERT] )
  assert_equal( 1, uh.udtbl[sqlite3.UPDATE] )
  assert_equal( 1, uh.udtbl[sqlite3.DELETE] )
  assert_equal( 3, uh.uttblsz() )
  assert_equal( 0, uh.crtbl[1] )
  assert_equal( 1, uh.crtbl[2] )
end

function uh.test_zinsert3update1delete1()
  assert_nil(uh.db:update_hook( function(ud, op, dname, tname, rowid)
    ud[op] = ud[op] + 1
  end, uh.udtbl))
  assert_nil( uh.db:commit_hook(function (ud) ud[1] = ud[1] + 1; return false end, uh.crtbl))
  assert_nil( uh.db:rollback_hook(function (ud) ud[2] = ud[2] + 1 end, uh.crtbl))
  assert_equal( sqlite3.OK, uh.db:exec("INSERT INTO test VALUES (NULL, 'Hello World');") )
  assert_equal( sqlite3.OK, uh.db:exec("INSERT INTO test VALUES (NULL, 'Hello Lua');") )
  assert_equal( sqlite3.OK, uh.db:exec("INSERT INTO test VALUES (NULL, 'Hello Sqlite3');") )
  assert_equal( sqlite3.OK, uh.db:exec("UPDATE test SET content = 'Hello Again World' WHERE id = 1;") )
  assert_equal( sqlite3.OK, uh.db:exec("DELETE FROM test WHERE id = 2;") )
  --for k,v in pairs(uh.udtbl) do print(k,v) end
  assert_equal( 3, uh.udtbl[sqlite3.INSERT] )
  assert_equal( 1, uh.udtbl[sqlite3.UPDATE] )
  assert_equal( 1, uh.udtbl[sqlite3.DELETE] )
  assert_equal( 3, uh.uttblsz() )
  assert_equal( 5, uh.crtbl[1] )
  assert_equal( 0, uh.crtbl[2] )
end



--------------------------------------------
-- Tests loop break and statement reusage --
--------------------------------------------