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>
 |