それはどのように機能しますか?
アプリケーションは、LiteSyncコードを含むSQLiteライブラリの修正バージョンを使用して、データベースにアクセスします。
SQLiteライブラリの変更は内部で行われ、インターフェースは同じです。
LiteSyncライブラリは相互に通信し、トランザクションデータを交換します。

レプリケーション
アプリを初めて開いたときに、他のノードに接続し、データベースの新しいコピーをダウンロードします。
集中トポロジでは、プライマリノードがデータベースコピーをセカンダリノードに送信します。.
ダウンロードが完了すると、ノードは同期を開始します。
同期
ノードが同じデータベースベースを持つと、オフライン時に実行されるトランザクションが交換されます。
その後、オンラインモードに入り、新しいトランザクションがノードで実行されると、転送されて接続されたノードで実行されます。
ノードがオフラインの場合、トランザクションはローカルログに保存され、後で交換されます。
自分のアプリコードを変更する必要がありますか?
いくつかの手順がありますが、基本的には、データベースのURI文字列を次のように変更する必要があります:
"file:/path/to/app.db"
このようなものに:
"file:/path/to/app.db?node=secondary&connect=tcp://server.ip:1234"
朗報は、LiteSyncがネイティブのSQLite3インターフェイスを使用することです。つまり、別のAPIを使用する必要はありません。
接続
各ノードには2つのオプションがあります:
アドレスにバインドする
ピアアドレスに接続します
したがって、どちら側をもう一方に接続するかを選択できます。これは、片側がルーターまたはファイアウォールの背後にある場合に役立ちます。
サポートされるトポロジ
一元化された、スター型トポロジー

このトポロジでは、他のすべてのノードが接続されるノードがあるため、同期を実行するにはオンラインである必要があります。
ここにいくつかの設定例があります:
プライマリノードはアドレスにバインドでき、セカンダリノードはそのアドレスに接続できます。
プライマリノード:
"file:/home/user/app.db?node=primary&bind=tcp://0.0.0.0:1234"
セカンダリノード:(別のデバイス内)
"file:/home/user/app.db?node=secondary&connect=tcp://server:1234"
プライマリノードはセカンダリノードにも接続できます。
プライマリノード:
"file:/home/user/app.db?node=primary&connect=tcp://address1:port1,tcp://address2:port2"
セカンダリノード:(それぞれ別のデバイス上)
"file:/home/user/app.db?node=secondary&bind=tcp://0.0.0.0:1234"
これら2つのオプションを組み合わせて使用することもできます。
プライマリノード:
"file:/home/user/app.db?node=primary&bind=tcp://0.0.0.0:1234&connect=tcp://address1:port1"
セカンダリノード1:
"file:/home/user/app.db?node=secondary&connect=tcp://server:1234"
セカンダリノード2:
"file:/home/user/app.db?node=secondary&bind=tcp://0.0.0.0:1234"
ピアツーピアトポロジ

完全に接続されたピアツーピアネットワークは、プライマリノード間で作成されます。
ネットワーク上のノードの総数を各ノードで手動で通知する必要があります(今のところ)
接続の方向も通知する必要があります(どのノードがどのノードに接続するか)
これは、3つのノードを持つネットワークの例です。
ノード1:
"file:db1.db?node=primary&total_primary_nodes=3&bind=tcp://0.0.0.0:1201"
ノード2:
"file:db2.db?node=primary&total_primary_nodes=3&bind=tcp://0.0.0.0:1202& connect=tcp://127.0.0.1:1201"
ノード3:
"file:db3.db?node=primary&total_primary_nodes=3&bind=tcp://0.0.0.0:1203& connect=tcp://127.0.0.1:1201,tcp://127.0.0.1:1202"
混合トポロジー

このトポロジでは、複数のプライマリノードがピアとして接続され、多くのセカンダリノードがそれらに接続されています。
プライマリノードの構成は、ピアツーピアトポロジ(上記)と同じです。
各セカンダリノードは、特定の時間に1つのプライマリノードに接続されます。 多くのプライマリノードのアドレスを通知できるため、プライマリノードはランダムに1つを選択します。 プライマリノードへの接続が切断されると、別のノードに接続します。
セカンダリノードのURIの例を次に示します。
"file:db4.db?node=secondary&connect=tcp://127.0.0.1:1201,tcp://127.0.0.1:1202,tcp://127.0.0.1:1203"
同期ステータス
次のコマンドを使用して、同期ステータスを確認できます:
PRAGMA sync_status
JSON文字列を返します。
同期通知
リモートノードとの同期によりローカルデータベースが更新されると、アプリケーションに通知できます。 通知は、ユーザー定義関数を介して行われます。
言語を選択してください -->
static void on_db_update(sqlite3_context *context, int argc, sqlite3_value **argv){ char* changes = sqlite3_value_text(argv[0]); printf("更新を受信しました: %s\n", changes); } sqlite3_create_function(db, "update_notification", 1, SQLITE_UTF8, NULL, &on_db_update, NULL, NULL);
db.define("update_notification", [](std::string changes) { std::cout << "更新を受信しました: " << changes << std::endl; });
def on_db_update(changes): print("更新を受信しました:", changes) con.create_function("update_notification", 1, on_db_update)
// using better-sqlite3: db.on('sync', function(changes) { console.log('更新を受信しました: ' + changes); });
Function.create(conn, "update_notification", new Function() { protected void xFunc(changes) { System.out.println("更新を受信しました: " + changes); } });
// using SQLite.NET: db.OnSync((changes) => { // the db received an update. update the screen with new data UpdateScreen(db); }); // using Microsoft.Data.SQLite: db.CreateFunction("update_notification", (changes) => { // notification received on the worker thread // do not access the db connection here // transfer the notification to the main thread Console.WriteLine("更新を受信しました: " + changes); return 0; });
' Using SQLite.NET: db.OnSync(Function(changes) As Integer ' the db received an update. update the screen with new data UpdateScreen(db) End Function) ' Using Microsoft.Data.SQLite: db.CreateFunction("update_notification", Function(changes) As Integer ' notification received on the worker thread ' do not access the db connection here ' transfer the notification to the main thread Console.WriteLine("更新を受信しました: " & changes) End Function)
function on_db_update($changes) { echo '更新を受信しました: $changes'; } // with sqlite3: $db->createFunction('update_notification', 'on_db_update'); // with pdo_sqlite: $db->sqliteCreateFunction('update_notification', 'on_db_update', 1);
$dbh->sqlite_create_function( 'update_notification', 1, sub { my $changes = shift; # retrieve the argument passed to the function print "更新を受信しました: $changes"; });
db.create_function "update_notification", 1 do |func, changes| puts "更新を受信しました: #{changes}" func.result = null end
db.create(function: "update_notification", argc: 1) { args in let changes = args.first as! String println("更新を受信しました: \(changes)") return nil }
db:create_function('update_notification',1,function(ctx,changes) print('更新を受信しました: ' .. changes) ctx:result_null() end)
func on_db_update(changes string) int64 { print('更新を受信しました: ' + changes) return null } sql.Register("sqlite3_custom", &sqlite.SQLiteDriver{ ConnectHook: func(conn *sqlite.SQLiteConn) error { if err := conn.RegisterFunc("update_notification", on_db_update, true); err != nil { return err } return nil }, }) db, err := sql.Open("sqlite3_custom", "file:data.db?node=...")
重要: 通知関数はワーカースレッドによって呼び出されます。 アプリケーションは通知関数内でdb接続を使用してはならず、できるだけ早く戻る必要があります。 アプリケーションは、戻る前に通知をメインスレッドに転送できます。
データベースの準備ができているかどうかの確認
アプリがデバイスで初めて開かれている場合、別のノードからデータベースの新しいコピーをダウンロードできます。それが完了するまで、データベースにアクセスできません。
同期ステータスを取得し、db_is_ready変数を確認できます。
以下の基本的なアプリの例を確認してください。
自分のアプリでそれを使用するには?
3つのステップがあります:
1 SQLiteライブラリをLiteSyncを含むライブラリに置き換えます
2 URI接続文字列を変更する
3 データベースのレディステータスを確認します
CおよびC ++アプリをコンパイルするときは、アプリケーションをLiteSyncライブラリにリンクする必要があります。
他の言語では、適切なラッパーをインストールする必要があります。
プライマリノードの例
プライマリノードは通常のアプリケーションで、セカンダリノードとまったく同じアプリですが、異なるURIを使用できます。
または、プライマリノード専用のアプリを使用することもできます。
一元化されたdbノードを維持する目的でのみ使用される基本的なスタンドアロンアプリケーションは、次のようになります。
言語を選択してください -->
#include <sqlite3.h> char *uri = "file:/path/to/app.db?node=primary&bind=tcp://0.0.0.0:1234"; int main() { sqlite3 *db; sqlite3_open(uri, &db); /* open the database */ while(1) sleep(1); /* keep the app open */ }
#include <sqlite_modern_cpp.h> #include <thread> #include <chrono> #include <iostream> using namespace sqlite; int main() { try { // open the database database db("file:app.db?node=primary&bind=tcp://0.0.0.0:1234"); // keep the app open while(1) { std::this_thread::sleep_for(std::chrono::seconds(1)); } } catch (std::exception& e) { std::cerr << e.what() << std::endl; } }
import litesync as sqlite3 conn = sqlite3.connect('file:/path/to/app.db?node=primary&bind=tcp://0.0.0.0:1234') # keep the app open import time while True: time.sleep(60) # in seconds
const uri = 'file:app.db?node=primary&bind=tcp://0.0.0.0:1234'; const options = { verbose: console.log }; const db = require('better-sqlite3-litesync')(uri, options); // keep the app open setInterval(function(){}, 5000);
import java.sql.Connection; import java.sql.DriverManager; public class Sample { public static void main(String[] args) { String uri = "file:/path/to/app.db?node=primary&bind=tcp://0.0.0.0:1234"; Connection connection = DriverManager.getConnection("jdbc:sqlite:" + uri); // keep the app open while (true) { Thread.sleep(5000); } } }
using SQLite; public class Program { public static void Main() { // open the database var uri = "file:app.db?node=primary&bind=tcp://0.0.0.0:1234"; var db = new SQLiteConnection(uri); // keep the app open while(true) { System.Threading.Thread.Sleep(5000); } } }
Imports SQLite Public Class Program Public Shared Sub Main() ' open the database Dim db As New SQLiteConnection("file:app.db?node=primary&bind=tcp://0.0.0.0:1234") ' keep the app open Do System.Threading.Thread.Sleep(5000) Loop End Sub End Class
Option Explicit Declare Sub Sleep Lib "kernel32.dll" (ByVal dwMilliseconds As Long) Public Sub Main() Dim URI As String Dim Conn As New ADODB.Connection ' Open the connection URI = "file:C:\app\mydb.db?node=primary&bind=tcp://0.0.0.0:1234" Conn.Open "DRIVER=SQLite3 ODBC Driver;Database=" & URI ' Keep the app open Do: Sleep(5000): Loop End Sub
<?php // with sqlite3: $db = new SQLite3("file:/path/to/app.db?node=primary&bind=tcp://0.0.0.0:1234"); // with pdo_sqlite: $pdo = new PDO("sqlite:file:/path/to/app.db?node=primary&bind=tcp://0.0.0.0:1234"); // keep the app open - it should not be used with apache while(1) sleep(5); ?>
use DBI; my $dbh = DBI->connect("dbi:SQLite:uri=file:/path/to/app.db?node=primary&bind=tcp://0.0.0.0:1234"); // keep the app open - it should not be used with apache sleep;
require 'sqlite3' db = SQLite3::Database.new "file:/path/to/app.db?node=primary&bind=tcp://0.0.0.0:1234" # keep the app open loop do sleep(1) end
local sqlite3 = require("lsqlite3") local db = sqlite3.open('file:/path/to/app.db?node=primary&bind=tcp://0.0.0.0:1234') -- keep the app open local lsocket = require("lsocket") while true do lsocket.select(5000) end
package main import ( "database/sql" _ "github.com/litesync/go-sqlite3" "time" ) func main() { db, err := sql.Open("sqlite3", "file:/path/to/app.db?node=primary&bind=tcp://0.0.0.0:1234") // keep the app open for { time.Sleep(1000 * time.Millisecond) } }
基本的なアプリの例
ローカルデータベースに書き込む基本的なアプリは次のようになります:
言語を選択してください -->
#include <sqlite3.h> char *uri = "file:/path/to/app.db?node=secondary&connect=tcp://myserver.ddns.net:1234"; int main() { sqlite3 *db; /* open the database */ sqlite3_open(&db, uri); /* check if the db is ready */ while(1){ char *json_str = sqlite3_query_value_str(db, "PRAGMA sync_status", NULL); bool db_is_ready = strstr(json_str, "\"db_is_ready\": true") > 0; sqlite3_free(json_str); if (db_is_ready) break; sleep_ms(250); } /* access the database */ start_access(db); } char * sqlite3_query_value_str(sqlite3 *db, char *sql, char **ppErrMsg) { char *ptr = NULL; sqlite3_stmt *stmt; int rc; if (ppErrMsg) *ppErrMsg = NULL; rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); if (rc != SQLITE_OK) { if (ppErrMsg) *ppErrMsg = sqlite3_strdup(sqlite3_errmsg(db)); return NULL; } if (sqlite3_step(stmt) == SQLITE_ROW) { char *text = (char *)sqlite3_column_text(stmt, 0); if (text) { ptr = sqlite3_strdup(text); } } sqlite3_finalize(stmt); return ptr; }
#include <iostream> #include <sqlite_modern_cpp.h> #include <unistd.h> using namespace sqlite; using namespace std; int main() { try { // open the database database db("file:app.db?node=secondary&connect=tcp://myserver.ddns.net:1234"); // wait until the database is ready while(1) { string status; db << "pragma sync_status" >> status; cout << "status : " << status << endl; if (status.find("\"db_is_ready\": true") != string::npos) break; sleep(1); } // now the application can access the database // check examples here: // https://github.com/SqliteModernCpp/sqlite_modern_cpp ... } catch (exception& e) { cout << e.what() << endl; } }
import litesync as sqlite3 import json import time conn = sqlite3.connect('file:/path/to/app.db?node=secondary&connect=tcp://myserver.ddns.net:1234') # check if the db is ready while not conn.is_ready(): time.sleep(0.250) start_access(conn)
const uri = 'file:test.db?node=secondary&connect=tcp://127.0.0.1:1234'; const options = { verbose: console.log }; const db = require('better-sqlite3-litesync')(uri, options); db.on('ready', function() { // the database is ready to be accessed db.exec('CREATE TABLE IF NOT EXISTS users (name, email)'); ... });
import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.Statement; import org.json.*; public class Sample { public static void main(String[] args) { String uri = "file:/path/to/app.db?node=secondary&connect=tcp://myserver.ddns.net:1234"; Connection connection = DriverManager.getConnection("jdbc:sqlite:" + uri); Statement statement = connection.createStatement(); // check if the db is ready while (true) { ResultSet rs = statement.executeQuery("PRAGMA sync_status"); rs.next(); JSONObject obj = new JSONObject(rs.getString(1)); if (obj.getBoolean("db_is_ready")) break; Thread.sleep(250); } // now we can access the db start_access(connection); } }
using SQLite; public class Program { public static void Main() { // open the database var uri = "file:app.db?node=secondary&connect=tcp://server:port"; var db = new SQLiteConnection(uri); // wait until the db is ready while (!db.IsReady()) { System.Threading.Thread.Sleep(250); } // now we can use the database db.CreateTable<TodoItem>(CreateFlags.AutoIncPK); ... } }
Imports SQLite Public Class Program Public Shared Sub Main() ' open the database Dim db As New SQLiteConnection("file:app.db?node=secondary&connect=tcp://server:port") ' wait until the db is ready While Not db.IsReady() System.Threading.Thread.Sleep(250) End While ' now we can use the database db.CreateTable(Of TodoItem)(CreateFlags.AutoIncPK) ' ... End Sub End Class
Option Explicit Declare Sub Sleep Lib "kernel32.dll" (ByVal dwMilliseconds As Long) Public Sub Main() Dim Conn As New ADODB.Connection Dim Rst As ADODB.Recordset Dim URI As String URI = "file:C:\app\mydb.db?node=secondary&connect=tcp://myserver.ddns.net:1234" Conn.Open "DRIVER=SQLite3 ODBC Driver;Database=" & URI ' Check if the database is ready Do Set Rst = New ADODB.Recordset Rst.Open "PRAGMA sync_status", Conn, , , adCmdText If InStr(Rst!sync_status, """db_is_ready"": true") > 0 Then Exit Do Sleep 200 Loop ' Now we can access the db StartDbAccess(Conn) End Sub
<?php // with sqlite3: $db = new SQLite3("file:/path/to/app.db?node=secondary&connect=tcp://myserver.ddns.net:1234"); // with pdo_sqlite: $pdo = new PDO("sqlite:file:/path/to/app.db?node=secondary&connect=tcp://myserver.ddns.net:1234"); // check if the db is ready while(1) { $results = $db->query('PRAGMA sync_status'); $row = $results->fetchArray(); $status = json_decode($row[0], true); if ($status['db_is_ready'] == true) break; sleep(0.25); } // now we can access the db start_access($db); ?>
use DBI; use JSON qw( decode_json ); my $dbh = DBI->connect("dbi:SQLite:uri=file:/path/to/app.db?node=secondary&connect=tcp://myserver.ddns.net:1234"); // check if the db is ready - it should not be used with apache while (1) { my ($result) = $dbh->selectrow_array("PRAGMA sync_status"); my $status = decode_json($result); if ($status->{'db_is_ready'}) last; sleep; } // now we can access the db ...
require 'sqlite3' require 'json' db = SQLite3::Database.new "file:/path/to/app.db?node=secondary&connect=tcp://myserver.ddns.net:1234" # check if the db is ready loop do result = db.get_first_value "PRAGMA sync_status" status = JSON.parse(result) break if status["db_is_ready"] == true sleep 0.25 end # now we can access the db start_access(db)
local sqlite3 = require "lsqlite3" local json = require "json" local db = sqlite3.open('file:/path/to/app.db?node=secondary&connect=tcp://myserver.ddns.net:1234') -- check if the db is ready local lsocket = require("lsocket") while true do local result = db:rows("PRAGMA sync_status") local status = json:decode(result[0]) if status["db_is_ready"] == true then break end lsocket.select(250) end -- now we can access the db start_access(db)
package main import ( "database/sql" _ "github.com/litesync/go-sqlite3" "time" ) func main() { db, err := sql.Open("sqlite3", "file:/path/to/app.db?node=secondary&connect=tcp://myserver.ddns.net:1234") // wait until the db is ready for !db.IsReady() { time.Sleep(1000 * time.Millisecond) } // now we can access the db start_access(db) }
セキュリティ
LiteSyncは、秘密鍵による暗号化を通じて、どのノードがネットワークの一部になれるかを制御する「共有秘密鍵」方式を使用しています
データベースとノード間の通信の両方で暗号化を有効にすることが可能(そして推奨)です
暗号化についての説明をご確認ください
現在の制限
1 非決定的な関数(それぞれの呼び出しで異なる値を返す)は、random()やdate('now')のようなものはブロックされます。アプリで生成された明示的な値を使用してください
2 AUTOINCREMENTキーワードはサポートされていません - しかし、それは必要ありません!(詳細はビデオをご覧ください)
3 同時にデータベースにアクセスできるのは一つのアプリケーションだけです。各インスタンスは自身のデータベースを使用し、それらはLiteSyncを使用して複製および同期されます