625 lines
27 KiB
HTML
625 lines
27 KiB
HTML
<!DOCTYPE html>
|
|
<html><head>
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
|
|
<link href="sqlite.css" rel="stylesheet">
|
|
<title>The Session Extension</title>
|
|
<!-- path= -->
|
|
</head>
|
|
<body>
|
|
<div class=nosearch>
|
|
<a href="index.html">
|
|
<img class="logo" src="images/sqlite370_banner.gif" alt="SQLite" border="0">
|
|
</a>
|
|
<div><!-- IE hack to prevent disappearing logo --></div>
|
|
<div class="tagline desktoponly">
|
|
Small. Fast. Reliable.<br>Choose any three.
|
|
</div>
|
|
<div class="menu mainmenu">
|
|
<ul>
|
|
<li><a href="index.html">Home</a>
|
|
<li class='mobileonly'><a href="javascript:void(0)" onclick='toggle_div("submenu")'>Menu</a>
|
|
<li class='wideonly'><a href='about.html'>About</a>
|
|
<li class='desktoponly'><a href="docs.html">Documentation</a>
|
|
<li class='desktoponly'><a href="download.html">Download</a>
|
|
<li class='wideonly'><a href='copyright.html'>License</a>
|
|
<li class='desktoponly'><a href="support.html">Support</a>
|
|
<li class='desktoponly'><a href="prosupport.html">Purchase</a>
|
|
<li class='search' id='search_menubutton'>
|
|
<a href="javascript:void(0)" onclick='toggle_search()'>Search</a>
|
|
</ul>
|
|
</div>
|
|
<div class="menu submenu" id="submenu">
|
|
<ul>
|
|
<li><a href='about.html'>About</a>
|
|
<li><a href='docs.html'>Documentation</a>
|
|
<li><a href='download.html'>Download</a>
|
|
<li><a href='support.html'>Support</a>
|
|
<li><a href='prosupport.html'>Purchase</a>
|
|
</ul>
|
|
</div>
|
|
<div class="searchmenu" id="searchmenu">
|
|
<form method="GET" action="search">
|
|
<select name="s" id="searchtype">
|
|
<option value="d">Search Documentation</option>
|
|
<option value="c">Search Changelog</option>
|
|
</select>
|
|
<input type="text" name="q" id="searchbox" value="">
|
|
<input type="submit" value="Go">
|
|
</form>
|
|
</div>
|
|
</div>
|
|
<script>
|
|
function toggle_div(nm) {
|
|
var w = document.getElementById(nm);
|
|
if( w.style.display=="block" ){
|
|
w.style.display = "none";
|
|
}else{
|
|
w.style.display = "block";
|
|
}
|
|
}
|
|
function toggle_search() {
|
|
var w = document.getElementById("searchmenu");
|
|
if( w.style.display=="block" ){
|
|
w.style.display = "none";
|
|
} else {
|
|
w.style.display = "block";
|
|
setTimeout(function(){
|
|
document.getElementById("searchbox").focus()
|
|
}, 30);
|
|
}
|
|
}
|
|
function div_off(nm){document.getElementById(nm).style.display="none";}
|
|
window.onbeforeunload = function(e){div_off("submenu");}
|
|
/* Disable the Search feature if we are not operating from CGI, since */
|
|
/* Search is accomplished using CGI and will not work without it. */
|
|
if( !location.origin || !location.origin.match || !location.origin.match(/http/) ){
|
|
document.getElementById("search_menubutton").style.display = "none";
|
|
}
|
|
/* Used by the Hide/Show button beside syntax diagrams, to toggle the */
|
|
function hideorshow(btn,obj){
|
|
var x = document.getElementById(obj);
|
|
var b = document.getElementById(btn);
|
|
if( x.style.display!='none' ){
|
|
x.style.display = 'none';
|
|
b.innerHTML='show';
|
|
}else{
|
|
x.style.display = '';
|
|
b.innerHTML='hide';
|
|
}
|
|
return false;
|
|
}
|
|
</script>
|
|
</div>
|
|
<div class=fancy>
|
|
<div class=nosearch>
|
|
<div class="fancy_title">
|
|
The Session Extension
|
|
</div>
|
|
<div class="fancy_toc">
|
|
<a onclick="toggle_toc()">
|
|
<span class="fancy_toc_mark" id="toc_mk">►</span>
|
|
Table Of Contents
|
|
</a>
|
|
<div id="toc_sub"><div class="fancy-toc1"><a href="#introduction">1. Introduction</a></div>
|
|
<div class="fancy-toc2"><a href="#typical_use_case">1.1. Typical Use Case</a></div>
|
|
<div class="fancy-toc2"><a href="#obtaining_the_session_extension">1.2. Obtaining the Session Extension</a></div>
|
|
<div class="fancy-toc2"><a href="#limitations">1.3. Limitations</a></div>
|
|
<div class="fancy-toc1"><a href="#concepts">2. Concepts</a></div>
|
|
<div class="fancy-toc2"><a href="#changesets_and_patchsets">2.1. Changesets and Patchsets</a></div>
|
|
<div class="fancy-toc2"><a href="#conflicts">2.2. Conflicts</a></div>
|
|
<div class="fancy-toc2"><a href="#changeset_construction">2.3. Changeset Construction</a></div>
|
|
<div class="fancy-toc1"><a href="#using_the_session_extension">3. Using The Session Extension</a></div>
|
|
<div class="fancy-toc2"><a href="#capturing_a_changeset">3.1. Capturing a Changeset</a></div>
|
|
<div class="fancy-toc2"><a href="#applying_a_changeset_to_a_database">3.2. Applying a Changeset to a Database</a></div>
|
|
<div class="fancy-toc2"><a href="#inspecting_the_contents_of_a_changeset">3.3. Inspecting the Contents of a Changeset</a></div>
|
|
<div class="fancy-toc1"><a href="#extended_functionality">4. Extended Functionality</a></div>
|
|
</div>
|
|
</div>
|
|
<script>
|
|
function toggle_toc(){
|
|
var sub = document.getElementById("toc_sub")
|
|
var mk = document.getElementById("toc_mk")
|
|
if( sub.style.display!="block" ){
|
|
sub.style.display = "block";
|
|
mk.innerHTML = "▼";
|
|
} else {
|
|
sub.style.display = "none";
|
|
mk.innerHTML = "►";
|
|
}
|
|
}
|
|
</script>
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<h1 id="introduction"><span>1. </span>Introduction</h1>
|
|
|
|
<p>The session extension provide a mechanism for recording changes
|
|
to some or all of the <a href="rowidtable.html">rowid tables</a> in an SQLite database, and packaging
|
|
those changes into a "changeset" or "patchset" file that can later
|
|
be used to apply the same set of changes to another database with
|
|
the same schema and compatible starting data. A "changeset" may
|
|
also be inverted and used to "undo" a session.
|
|
|
|
</p><p>This document is an introduction to the session extension.
|
|
The details of the interface are in the separate
|
|
<a href="session/intro.html">Session Extension C-language Interface</a> document.
|
|
|
|
</p><h2 id="typical_use_case"><span>1.1. </span>Typical Use Case</h2>
|
|
|
|
<p>Suppose SQLite is used as the <a href="appfileformat.html">application file format</a> for a
|
|
particular design application. Two users, Alice and Bob, each start
|
|
with a baseline design that is about a gigabyte in size. They work
|
|
all day, in parallel, each making their own customizations and tweaks
|
|
to the design. At the end of the day, they would like to merge their
|
|
changes together into a single unified design.
|
|
|
|
</p><p>The session extension facilitates this by recording all changes to
|
|
both Alice's and Bob's databases and writing those changes into
|
|
changeset or patchset files. At the end of the day, Alice can send her
|
|
changeset to Bob and Bob can "apply" it to his database. The result (assuming
|
|
there are no conflicts) is that Bob's database then contains both his
|
|
changes and Alice's changes. Likewise, Bob can send a changeset of
|
|
his work over to Alice and she can apply his changes to her database.
|
|
|
|
</p><p>In other words, the session extension provides a facility for
|
|
SQLite database files that is similar to the unix
|
|
<a href="https://en.wikipedia.org/wiki/Patch_(Unix)">patch</a> utility program,
|
|
or to the "merge" capabilities of version control systems such
|
|
as <a href="https://www.fossil-scm.org/">Fossil</a>, <a href="https://git-scm.com">Git</a>,
|
|
or <a href="http://www.mercurial-scm.org/">Mercurial</a>.
|
|
|
|
</p><h2 id="obtaining_the_session_extension"><span>1.2. </span>Obtaining the Session Extension</h2>
|
|
|
|
<p> Since <a href="releaselog/3_13_0.html">version 3.13.0</a> (2016-05-18),
|
|
the session extension has been included in the SQLite
|
|
<a href="amalgamation.html">amalgamation</a> source distribution. By default, the session extension is
|
|
disabled. To enable it, build with the following compiler switches:
|
|
|
|
</p><div class="codeblock"><pre>-DSQLITE_ENABLE_SESSION -DSQLITE_ENABLE_PREUPDATE_HOOK
|
|
</pre></div>
|
|
|
|
<p> Or, if using the autoconf build system, pass the --enable-session option to the configure script.
|
|
|
|
</p><h2 id="limitations"><span>1.3. </span>Limitations</h2>
|
|
|
|
<ul>
|
|
|
|
<li><p> Prior to SQLite version 3.17.0, the session extension only worked with
|
|
<a href="rowidtable.html">rowid tables</a>, not <a href="withoutrowid.html">WITHOUT ROWID</a> tables. As of 3.17.0, both
|
|
rowid and WITHOUT ROWID tables are supported.
|
|
|
|
</p></li><li><p> There is no support for <a href="vtab.html">virtual tables</a>. Changes to virtual tables are
|
|
not captured.
|
|
|
|
</p></li><li><p> The session extension only works with tables that have a declared
|
|
PRIMARY KEY. The PRIMARY KEY of a table may be an INTEGER PRIMARY KEY
|
|
(rowid alias) or an external PRIMARY KEY.
|
|
|
|
</p></li><li><p> SQLite allows <a href="nulls.html">NULL values</a> to be stored in
|
|
PRIMARY KEY columns. However, the session extension ignores all
|
|
such rows. No changes affecting rows with one or more NULL values
|
|
in PRIMARY KEY columns are recorded by the sessions module.
|
|
</p></li></ul>
|
|
|
|
<h1 id="concepts"><span>2. </span>Concepts</h1>
|
|
|
|
<a name="changeset"></a>
|
|
|
|
<h2 id="changesets_and_patchsets"><span>2.1. </span>Changesets and Patchsets</h2>
|
|
<p> The sessions module revolves around creating and manipulating
|
|
changesets. A changeset is a blob of data that encodes a series of
|
|
changes to a database. Each change in a changeset is one of the
|
|
following:
|
|
|
|
</p><ul>
|
|
<li> <p>An <b>INSERT</b>. An INSERT change contains a single row to add to
|
|
a database table. The payload of the INSERT change consists of the
|
|
values for each field of the new row.
|
|
|
|
</p></li><li> <p>A <b>DELETE</b>. A DELETE change represents a row, identified by
|
|
its primary key values, to remove from a database table. The payload
|
|
of a DELETE change consists of the values for all fields of the
|
|
deleted row.
|
|
|
|
</p></li><li> <p>An <b>UPDATE</b>. An UPDATE change represents the modification of
|
|
one or more non-PRIMARY KEY fields of a single row within a database
|
|
table, identified by its PRIMARY KEY fields. The payload for an UPDATE
|
|
change consists of:
|
|
</p><ul>
|
|
<li> The PRIMARY KEY values identifying the modified row,
|
|
</li><li> The new values for each modified field of the row, and
|
|
</li><li> The original values for each modified field of the row.
|
|
</li></ul>
|
|
<p> An UPDATE change does not contain any information regarding
|
|
non-PRIMARY KEY fields that are not modified by the change. It is not
|
|
possible for an UPDATE change to specify modifications to PRIMARY
|
|
KEY fields.
|
|
</p></li></ul>
|
|
|
|
<p> A single changeset may contain changes that apply to more than one
|
|
database table. For each table that the changeset includes at least one change
|
|
for, it also encodes the following data:
|
|
|
|
</p><ul>
|
|
<li> The name of the database table,
|
|
</li><li> The number of columns the table has, and
|
|
</li><li> Which of those columns are PRIMARY KEY columns.
|
|
</li></ul>
|
|
|
|
<p> Changesets may only be applied to databases that contain tables
|
|
matching the above three criteria as stored in the changeset.
|
|
|
|
</p><p> A patchset is similar to a changeset. It is slightly more compact than
|
|
a changeset, but provides more limited conflict detection and resolution
|
|
options (see the next section for details). The differences between a
|
|
patchset and a changeset are that:
|
|
|
|
</p><ul>
|
|
<li><p> For a <b>DELETE</b> change, the payload consists of the PRIMARY KEY
|
|
fields only. The original values of other fields are not stored as
|
|
part of a patchset.
|
|
|
|
</p></li><li><p> For an <b>UPDATE</b> change, the payload consists of the PRIMARY KEY
|
|
fields and the new values of modified fields only. The original
|
|
values of modified fields are not stored as part of a patchset.
|
|
</p></li></ul>
|
|
|
|
<h2 id="conflicts"><span>2.2. </span>Conflicts</h2>
|
|
|
|
<p> When a changeset or patchset is applied to a database, an attempt is
|
|
made to insert a new row for each INSERT change, remove a row for each
|
|
DELETE change and modify a row for each UPDATE change. If the target
|
|
database is in the same state as the original database that the changeset
|
|
was recorded on, this is a simple matter. However, if the contents of the
|
|
target database is not in exactly this state, conflicts can occur when
|
|
applying the changeset or patchset.
|
|
|
|
</p><p>When processing an <b>INSERT</b> change, the following conflicts can
|
|
occur:
|
|
|
|
</p><ul>
|
|
<li> The target database may already contain a row with the same PRIMARY
|
|
KEY values as specified by the INSERT change.
|
|
|
|
</li><li> Some other database constraint, for example a UNIQUE or CHECK
|
|
constraint, may be violated when the new row is inserted.
|
|
</li></ul>
|
|
|
|
<p>When processing a <b>DELETE</b> change, the following conflicts may be
|
|
detected:
|
|
|
|
</p><ul>
|
|
<li> The target database may contain no row with the specified PRIMARY
|
|
KEY values to delete.
|
|
|
|
</li><li> The target database may contain a row with the specified PRIMARY
|
|
KEY values, but the other fields may contain values that do not
|
|
match those stored as part of the changeset. This type of conflict
|
|
is not detected when using a patchset.
|
|
</li></ul>
|
|
|
|
<p>When processing an <b>UPDATE</b> change, the following conflicts may be
|
|
detected:
|
|
|
|
</p><ul>
|
|
<li> The target database may contain no row with the specified PRIMARY
|
|
KEY values to modify.
|
|
|
|
</li><li> The target database may contain a row with the specified PRIMARY
|
|
KEY values, but the current values of the fields that will be modified
|
|
by the change may not match the original values stored within the
|
|
changeset. This type of conflict is not detected when using a patchset.
|
|
|
|
</li><li> Some other database constraint, for example a UNIQUE or CHECK
|
|
constraint, may be violated when the row is updated.
|
|
</li></ul>
|
|
|
|
<p> Depending on the type of conflict, a sessions application has a variety
|
|
of configurable options for dealing with conflicts, ranging from omitting the
|
|
conflicting change, aborting the entire changeset application or applying
|
|
the change despite the conflict. For details, refer to the documentation for
|
|
the <a href="session/sqlite3changeset_apply.html">sqlite3changeset_apply()</a> API.
|
|
|
|
</p><h2 id="changeset_construction"><span>2.3. </span>Changeset Construction</h2>
|
|
|
|
<p> After a session object has been configured, it begins monitoring for
|
|
changes to its configured tables. However, it does not record an entire
|
|
change each time a row within the database is modified. Instead, it records
|
|
just the PRIMARY KEY fields for each inserted row, and just the PRIMARY KEY
|
|
and all original row values for any updated or deleted rows. If a row is
|
|
modified more than once by a single session, no new information is recorded.
|
|
|
|
</p><p> The other information required to create a changeset or patchset is
|
|
read from the database file when <a href="session/sqlite3session_changeset.html">sqlite3session_changeset()</a> or
|
|
<a href="session/sqlite3session_patchset.html">sqlite3session_patchset()</a> is called. Specifically,
|
|
|
|
</p><ul>
|
|
<li> <p>For each primary key recorded as a result of an INSERT operation,
|
|
the sessions module checks if there is a row with a matching primary
|
|
key still in the table. If so, an INSERT change is added to the
|
|
changeset.
|
|
|
|
</p></li><li> <p>For each primary key recorded as a result of an UPDATE or DELETE
|
|
operation, the sessions module also checks for a row with a matching
|
|
primary key within the table. If one can be found, but one or more
|
|
of the non-PRIMARY KEY fields does not match the original recorded
|
|
value, an UPDATE is added to the changeset. Or, if there is no row
|
|
at all with the specified primary key, a DELETE is added to the
|
|
changeset. If the row does exist but none of the non-PRIMARY KEY
|
|
fields have been modified, no change is added to the changeset.
|
|
</p></li></ul>
|
|
|
|
<p> One implication of the above is that if a change is made and then
|
|
unmade within a single session (for example if a row is inserted and then
|
|
deleted again), the sessions module does not report any change at all. Or
|
|
if a row is updated multiple times within the same session, all updates
|
|
are coalesced into a single update within any changeset or patchset blob.
|
|
|
|
</p><h1 id="using_the_session_extension"><span>3. </span>Using The Session Extension</h1>
|
|
|
|
<p> This section provides examples that demonstrate how to use the sessions
|
|
extension.
|
|
|
|
</p><h2 id="capturing_a_changeset"><span>3.1. </span>Capturing a Changeset</h2>
|
|
|
|
<p> The example code below demonstrates the steps involved in capturing a
|
|
changeset while executing SQL commands. In summary:
|
|
|
|
</p><ol>
|
|
<li> <p>A session object (type sqlite3_session*) is created by making a
|
|
call to the <a href="session/sqlite3session_create.html">sqlite3session_create()</a> API function.
|
|
|
|
</p><p>A single session object monitors changes made to a single database
|
|
(i.e. "main", "temp" or an attached database) via a single
|
|
sqlite3* database handle.
|
|
|
|
</p></li><li> <p>The session object is configured with a set of tables to monitor
|
|
changes on.
|
|
|
|
</p><p> By default a session object does not monitor changes on any
|
|
database table. Before it does so it must be configured. There
|
|
are three ways to configure the set of tables to monitor changes
|
|
on:
|
|
</p><ul>
|
|
<li> By explicitly specifying tables using one call to
|
|
<a href="session/sqlite3session_attach.html">sqlite3session_attach()</a> for each table, or
|
|
|
|
</li><li> By specifying that all tables in the database should be monitored
|
|
for changes using a call to <a href="session/sqlite3session_attach.html">sqlite3session_attach()</a> with a
|
|
NULL argument, or
|
|
|
|
</li><li> By configuring a callback to be invoked the first time each table
|
|
is written to that indicates to the session module whether or
|
|
not changes on the table should be monitored.
|
|
</li></ul>
|
|
<p> The example code below uses the second of the methods enumerated
|
|
above - it monitors for changes on all database tables.
|
|
|
|
</p></li><li> <p> Changes are made to the database by executing SQL statements. The
|
|
session object records these changes.
|
|
|
|
</p></li><li> <p> A changeset blob is extracted from the session object using a call
|
|
to <a href="session/sqlite3session_changeset.html">sqlite3session_changeset()</a> (or, if using patchsets, a call to
|
|
the <a href="session/sqlite3session_patchset.html">sqlite3session_patchset()</a> function).
|
|
|
|
</p></li><li> <p> The session object is deleted using a call to the
|
|
<a href="session/sqlite3session_delete.html">sqlite3session_delete()</a> API function.
|
|
|
|
</p><p> It is not necessary to delete a session object after extracting
|
|
a changeset or patchset from it. It can be left attached to the
|
|
database handle and will continue monitoring for changes on the
|
|
configured tables as before. However, if
|
|
<a href="session/sqlite3session_changeset.html">sqlite3session_changeset()</a> or <a href="session/sqlite3session_patchset.html">sqlite3session_patchset()</a> is
|
|
called a second time on a session object, the changeset or patchset
|
|
will contain <em>all</em> changes that have taken place on the connection
|
|
since the session was created. In other words,
|
|
a session object is not reset or
|
|
zeroed by a call to sqlite3session_changeset() or
|
|
sqlite3session_patchset().
|
|
</p></li></ol>
|
|
|
|
<div class="codeblock"><pre><i>/*
|
|
** Argument zSql points to a buffer containing an SQL script to execute
|
|
** against the database handle passed as the first argument. As well as
|
|
** executing the SQL script, this function collects a changeset recording
|
|
** all changes made to the "main" database file. Assuming no error occurs,
|
|
** output variables (*ppChangeset) and (*pnChangeset) are set to point
|
|
** to a buffer containing the changeset and the size of the changeset in
|
|
** bytes before returning SQLITE_OK. In this case it is the responsibility
|
|
** of the caller to eventually free the changeset blob by passing it to
|
|
** the sqlite3_free function.
|
|
**
|
|
** Or, if an error does occur, return an SQLite error code. The final
|
|
** value of (*pChangeset) and (*pnChangeset) are undefined in this case.
|
|
*/</i>
|
|
int sql_exec_changeset(
|
|
sqlite3 *db, <i>/* Database handle */</i>
|
|
const char *zSql, <i>/* SQL script to execute */</i>
|
|
int *pnChangeset, <i>/* OUT: Size of changeset blob in bytes */</i>
|
|
void **ppChangeset <i>/* OUT: Pointer to changeset blob */</i>
|
|
){
|
|
sqlite3_session *pSession = 0;
|
|
int rc;
|
|
|
|
<i>/* Create a new session object */</i>
|
|
rc = sqlite3session_create(db, "main", &pSession);
|
|
|
|
<i>/* Configure the session object to record changes to all tables */</i>
|
|
if( rc==SQLITE_OK ) rc = sqlite3session_attach(pSession, NULL);
|
|
|
|
<i>/* Execute the SQL script */</i>
|
|
if( rc==SQLITE_OK ) rc = sqlite3_exec(db, zSql, 0, 0, 0);
|
|
|
|
<i>/* Collect the changeset */</i>
|
|
if( rc==SQLITE_OK ){
|
|
rc = sqlite3session_changeset(pSession, pnChangeset, ppChangeset);
|
|
}
|
|
|
|
<i>/* Delete the session object */</i>
|
|
sqlite3session_delete(pSession);
|
|
|
|
return rc;
|
|
}
|
|
</pre></div>
|
|
|
|
<h2 id="applying_a_changeset_to_a_database"><span>3.2. </span>Applying a Changeset to a Database</h2>
|
|
|
|
<p> Applying a changeset to a database is simpler than capturing a changeset.
|
|
Usually, a single call to <a href="session/sqlite3changeset_apply.html">sqlite3changeset_apply()</a>, as depicted in the
|
|
example code below, suffices.
|
|
|
|
</p><p> In cases where it is complicated, the complications in applying a
|
|
changeset lie in conflict resolution. Refer to the API documentation linked
|
|
above for details.
|
|
|
|
</p><div class="codeblock"><pre><i>/*
|
|
** Conflict handler callback used by apply_changeset(). See below.
|
|
*/</i>
|
|
static int xConflict(void *pCtx, int eConflict, sqlite3_changset_iter *pIter){
|
|
int ret = (int)pCtx;
|
|
return ret;
|
|
}
|
|
|
|
<i>/*
|
|
** Apply the changeset contained in blob pChangeset, size nChangeset bytes,
|
|
** to the main database of the database handle passed as the first argument.
|
|
** Return SQLITE_OK if successful, or an SQLite error code if an error
|
|
** occurs.
|
|
**
|
|
** If parameter bIgnoreConflicts is true, then any conflicting changes
|
|
** within the changeset are simply ignored. Or, if bIgnoreConflicts is
|
|
** false, then this call fails with an SQLTIE_ABORT error if a changeset
|
|
** conflict is encountered.
|
|
*/</i>
|
|
int apply_changeset(
|
|
sqlite3 *db, <i>/* Database handle */</i>
|
|
int bIgnoreConflicts, <i>/* True to ignore conflicting changes */</i>
|
|
int nChangeset, <i>/* Size of changeset in bytes */</i>
|
|
void *pChangeset <i>/* Pointer to changeset blob */</i>
|
|
){
|
|
return sqlite3changeset_apply(
|
|
db,
|
|
nChangeset, pChangeset,
|
|
0, xConflict,
|
|
(void*)bIgnoreConflicts
|
|
);
|
|
}
|
|
</pre></div>
|
|
|
|
<h2 id="inspecting_the_contents_of_a_changeset"><span>3.3. </span>Inspecting the Contents of a Changeset</h2>
|
|
|
|
<p> The example code below demonstrates the techniques used to iterate
|
|
through and extract the data related to all changes in a changeset. To
|
|
summarize:
|
|
|
|
</p><ol>
|
|
<li><p> The <a href="session/sqlite3changeset_start.html">sqlite3changeset_start()</a> API is called to create and
|
|
initialize an iterator to iterate through the contents of a
|
|
changeset. Initially, the iterator points to no element at all.
|
|
|
|
</p></li><li><p> The first call to <a href="session/sqlite3changeset_next.html">sqlite3changeset_next()</a> on the iterator moves
|
|
it to point to the first change in the changeset (or to EOF, if
|
|
the changeset is completely empty). sqlite3changeset_next() returns
|
|
SQLITE_ROW if it moves the iterator to point to a valid entry,
|
|
SQLITE_DONE if it moves the iterator to EOF, or an SQLite error
|
|
code if an error occurs.
|
|
|
|
</p></li><li><p> If the iterator points to a valid entry, the <a href="session/sqlite3changeset_op.html">sqlite3changeset_op()</a>
|
|
API may be used to determine the type of change (INSERT, UPDATE or
|
|
DELETE) that the iterator points to. Additionally, the same API
|
|
can be used to obtain the name of the table the change applies to
|
|
and its expected number of columns and primary key columns.
|
|
|
|
</p></li><li><p> If the iterator points to a valid INSERT or UPDATE entry, the
|
|
<a href="session/sqlite3changeset_new.html">sqlite3changeset_new()</a> API may be used to obtain the new.* values
|
|
within the change payload.
|
|
|
|
</p></li><li><p> If the iterator points to a valid DELETE or UPDATE entry, the
|
|
<a href="session/sqlite3changeset_old.html">sqlite3changeset_old()</a> API may be used to obtain the old.* values
|
|
within the change payload.
|
|
|
|
</p></li><li><p> An iterator is deleted using a call to the
|
|
<a href="session/sqlite3changeset_finalize.html">sqlite3changeset_finalize()</a> API. If an error occured while
|
|
iterating, an SQLite error code is returned (even if the same error
|
|
code has already been returned by sqlite3changeset_next()). Or,
|
|
if no error has occurred, SQLITE_OK is returned.
|
|
</p></li></ol>
|
|
|
|
<div class="codeblock"><pre><i>/*
|
|
** Print the contents of the changeset to stdout.
|
|
*/</i>
|
|
static int print_changeset(void *pChangeset, int nChangeset){
|
|
int rc;
|
|
sqlite3_changeset_iter *pIter = 0;
|
|
|
|
<i>/* Create an iterator to iterate through the changeset */</i>
|
|
rc = sqlite3changeset_start(&pIter, nChangeset, pChangeset);
|
|
if( rc!=SQLITE_OK ) return rc;
|
|
|
|
<i>/* This loop runs once for each change in the changeset */</i>
|
|
while( SQLITE_ROW==sqlite3changeset_next(pIter) ){
|
|
const char *zTab; <i>/* Table change applies to */</i>
|
|
int nCol; <i>/* Number of columns in table zTab */</i>
|
|
int op; <i>/* SQLITE_INSERT, UPDATE or DELETE */</i>
|
|
sqlite3_value *pVal;
|
|
|
|
<i>/* Print the type of operation and the table it is on */</i>
|
|
rc = sqlite3changeset_op(pIter, &zTab, &nCol, &op, 0);
|
|
if( rc!=SQLITE_OK ) goto exit_print_changeset;
|
|
printf("%s on table %s\n",
|
|
op==SQLITE_INSERT?"INSERT" : op==SQLITE_UPDATE?"UPDATE" : "DELETE",
|
|
zTab
|
|
);
|
|
|
|
<i>/* If this is an UPDATE or DELETE, print the old.* values */</i>
|
|
if( op==SQLITE_UPDATE || op==SQLITE_DELETE ){
|
|
printf("Old values:");
|
|
for(i=0; i<nCol; i++){
|
|
rc = sqlite3changeset_old(pIter, i, &pVal);
|
|
if( rc!=SQLITE_OK ) goto exit_print_changeset;
|
|
printf(" %s", pVal ? sqlite3_value_text(pVal) : "-");
|
|
}
|
|
printf("\n");
|
|
}
|
|
|
|
<i>/* If this is an UPDATE or INSERT, print the new.* values */</i>
|
|
if( op==SQLITE_UPDATE || op==SQLITE_INSERT ){
|
|
printf("New values:");
|
|
for(i=0; i<nCol; i++){
|
|
rc = sqlite3changeset_new(pIter, i, &pVal);
|
|
if( rc!=SQLITE_OK ) goto exit_print_changeset;
|
|
printf(" %s", pVal ? sqlite3_value_text(pVal) : "-");
|
|
}
|
|
printf("\n");
|
|
}
|
|
}
|
|
|
|
<i>/* Clean up the changeset and return an error code (or SQLITE_OK) */</i>
|
|
exit_print_changeset:
|
|
rc2 = sqlite3changeset_finalize(pIter);
|
|
if( rc==SQLITE_OK ) rc = rc2;
|
|
return rc;
|
|
}
|
|
</pre></div>
|
|
|
|
<h1 id="extended_functionality"><span>4. </span>Extended Functionality</h1>
|
|
|
|
<p> Most applications will only use the session module functionality described
|
|
in the previous section. However, the following additional functionality is
|
|
available for the use and manipulation of changeset and patchset blobs:
|
|
|
|
</p><ul>
|
|
<li> <p>Two or more changeset/patchsets may be combined using the
|
|
<a href="session/sqlite3changeset_concat.html">sqlite3changeset_concat()</a> or <a href="session/changegroup.html">sqlite3_changegroup</a> interfaces.
|
|
|
|
</p></li><li> <p>A changeset may be "inverted" using the <a href="session/sqlite3changeset_invert.html">sqlite3changeset_invert()</a>
|
|
API function. An inverted changeset undoes the changes made by the
|
|
original. If changeset C<sup>+</sup> is the inverse of changeset C, then
|
|
applying C and then C<sup>+</sup> to a database should leave
|
|
the database unchanged.
|
|
</p></li></ul>
|