summaryrefslogtreecommitdiffstats
path: root/www/sessionintro.html
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--www/sessionintro.html649
1 files changed, 649 insertions, 0 deletions
diff --git a/www/sessionintro.html b/www/sessionintro.html
new file mode 100644
index 0000000..a2994d5
--- /dev/null
+++ b/www/sessionintro.html
@@ -0,0 +1,649 @@
+<!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;
+}
+var antiRobot = 0;
+function antiRobotGo(){
+if( antiRobot!=3 ) return;
+antiRobot = 7;
+var j = document.getElementById("mtimelink");
+if(j && j.hasAttribute("data-href")) j.href=j.getAttribute("data-href");
+}
+function antiRobotDefense(){
+document.body.onmousedown=function(){
+antiRobot |= 2;
+antiRobotGo();
+document.body.onmousedown=null;
+}
+document.body.onmousemove=function(){
+antiRobot |= 2;
+antiRobotGo();
+document.body.onmousemove=null;
+}
+setTimeout(function(){
+antiRobot |= 1;
+antiRobotGo();
+}, 100)
+antiRobotGo();
+}
+antiRobotDefense();
+</script>
+<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">&#x25ba;</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 = "&#x25bc;";
+} else {
+sub.style.display = "none";
+mk.innerHTML = "&#x25ba;";
+}
+}
+</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&lt;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&lt;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><p align="center"><small><i>This page last modified on <a href="https://sqlite.org/docsrc/honeypot" id="mtimelink" data-href="https://sqlite.org/docsrc/finfo/pages/sessionintro.in?m=007ada577d1408286">2018-02-28 22:21:53</a> UTC </small></i></p>
+