diff options
Diffstat (limited to 'www/sessionintro.html')
-rw-r--r-- | www/sessionintro.html | 649 |
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">►</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><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> + |