Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package rqlite for openSUSE:Factory checked 
in at 2026-09-04 12:43:37
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/rqlite (Old)
 and      /work/SRC/openSUSE:Factory/.rqlite.new.1265 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "rqlite"

Fri Sep  4 12:43:37 2026 rev:54 rq:1375706 version:10.3.1

Changes:
--------
--- /work/SRC/openSUSE:Factory/rqlite/rqlite.changes    2026-09-02 
16:59:47.023179740 +0200
+++ /work/SRC/openSUSE:Factory/.rqlite.new.1265/rqlite.changes  2026-09-04 
12:43:38.829405305 +0200
@@ -1,0 +2,8 @@
+Fri Sep 04 05:17:34 UTC 2026 - Andreas Stieger <[email protected]>
+
+- Update to version 10.3.1:
+  * Consolidate SQLite driver construction
+  * Plumb Term and Index through during Snapshot Restore
+  * Hard exit if FSM Restore fails post database-swap
+
+-------------------------------------------------------------------

Old:
----
  rqlite-10.3.0.tar.xz

New:
----
  rqlite-10.3.1.tar.xz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ rqlite.spec ++++++
--- /var/tmp/diff_new_pack.XUWVbL/_old  2026-09-04 12:43:39.884442356 +0200
+++ /var/tmp/diff_new_pack.XUWVbL/_new  2026-09-04 12:43:39.886442427 +0200
@@ -17,7 +17,7 @@
 
 
 Name:           rqlite
-Version:        10.3.0
+Version:        10.3.1
 Release:        0
 Summary:        Distributed relational database built on SQLite
 License:        MIT

++++++ _service ++++++
--- /var/tmp/diff_new_pack.XUWVbL/_old  2026-09-04 12:43:39.918443550 +0200
+++ /var/tmp/diff_new_pack.XUWVbL/_new  2026-09-04 12:43:39.921443656 +0200
@@ -3,7 +3,7 @@
     <param name="url">https://github.com/rqlite/rqlite.git</param>
     <param name="scm">git</param>
     <param name="exclude">.git</param>
-    <param name="revision">v10.3.0</param>
+    <param name="revision">v10.3.1</param>
     <param name="versionformat">@PARENT_TAG@</param>
     <param name="changesgenerate">enable</param>
     <param name="versionrewrite-pattern">v(.*)</param>

++++++ _servicedata ++++++
--- /var/tmp/diff_new_pack.XUWVbL/_old  2026-09-04 12:43:39.941444358 +0200
+++ /var/tmp/diff_new_pack.XUWVbL/_new  2026-09-04 12:43:39.944444463 +0200
@@ -1,7 +1,7 @@
 <servicedata>
   <service name="tar_scm">
     <param name="url">https://github.com/rqlite/rqlite.git</param>
-    <param 
name="changesrevision">6144b4c536861b6be7ded7ae75b8bbce1a9dba82</param>
+    <param 
name="changesrevision">c1a3b427827b3babc8c5fa7a7ad35ab2f9d598b0</param>
   </service>
 </servicedata>
 (No newline at EOF)

++++++ rqlite-10.3.0.tar.xz -> rqlite-10.3.1.tar.xz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rqlite-10.3.0/CHANGELOG.md 
new/rqlite-10.3.1/CHANGELOG.md
--- old/rqlite-10.3.0/CHANGELOG.md      2026-09-01 17:26:59.000000000 +0200
+++ new/rqlite-10.3.1/CHANGELOG.md      2026-09-04 04:11:36.000000000 +0200
@@ -1,3 +1,9 @@
+## v10.3.1 (September 3rd 2026)
+### Implementation changes and bug fixes
+- [PR #2758](https://github.com/rqlite/rqlite/pull/2758): Consolidate SQLite 
driver construction. Thanks @karangupta982
+- [PR #2760](https://github.com/rqlite/rqlite/pull/2760): Plumb Term and Index 
through during Snapshot Restore. Fixes issue 
[#2759](https://github.com/rqlite/rqlite/issues/2759). Thanks @rohanpadhye
+- [PR #2761](https://github.com/rqlite/rqlite/pull/2761): Hard exit if FSM 
Restore fails post database-swap.
+
 ## v10.3.0 (September 1st 2026)
 ### New features
 - [PR #2728](https://github.com/rqlite/rqlite/pull/2728): Console app supports 
quickly querying for first 100 rows of a table. Thanks @rodionlim
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rqlite-10.3.0/db/driver.go 
new/rqlite-10.3.1/db/driver.go
--- old/rqlite-10.3.0/db/driver.go      2026-09-01 17:26:59.000000000 +0200
+++ new/rqlite-10.3.1/db/driver.go      2026-09-04 04:11:36.000000000 +0200
@@ -26,6 +26,18 @@
        CnkOnCloseModeEnabled
 )
 
+// DriverConfig holds the configuration for a composable SQLite driver.
+type DriverConfig struct {
+       // Extensions is the list of paths to SQLite extension shared objects.
+       Extensions []string
+
+       // ChkOnClose controls whether SQLite checkpoints the WAL on connection 
close.
+       ChkOnClose CnkOnCloseMode
+
+       // QueryLogger, if non-nil, installs query tracing on every new 
connection.
+       QueryLogger *QueryLogger
+}
+
 // Driver is a Database driver.
 type Driver struct {
        name       string
@@ -33,6 +45,24 @@
        chkOnClose CnkOnCloseMode
 }
 
+// NewDriverFromConfig registers a new SQLite driver under name using cfg to
+// compose the ConnectHook. Every feature in cfg is applied to each new
+// connection, so extensions, checkpoint behavior, and query logging can all
+// coexist.
+// If a driver with name is already registered, a panic will occur. Callers
+// that need a singleton driver (fixed names) should guard this with sync.Once.
+func NewDriverFromConfig(name string, cfg DriverConfig) *Driver {
+       sql.Register(name, &sqlite3.SQLiteDriver{
+               Extensions:  cfg.Extensions,
+               ConnectHook: buildConnectHook(cfg),
+       })
+       return &Driver{
+               name:       name,
+               extensions: cfg.Extensions,
+               chkOnClose: cfg.ChkOnClose,
+       }
+}
+
 var defRegisterOnce sync.Once
 
 // DefaultDriver returns the default driver. It registers the SQLite3 driver
@@ -41,8 +71,8 @@
 // for any database in WAL mode.
 func DefaultDriver() *Driver {
        defRegisterOnce.Do(func() {
-               sql.Register(defaultDriverName, &sqlite3.SQLiteDriver{
-                       ConnectHook: makeConnectHookFn(CnkOnCloseModeDisabled),
+               NewDriverFromConfig(defaultDriverName, DriverConfig{
+                       ChkOnClose: CnkOnCloseModeDisabled,
                })
        })
        return &Driver{
@@ -59,8 +89,8 @@
 // on close for any database in WAL mode.
 func CheckpointDriver() *Driver {
        chkRegisterOnce.Do(func() {
-               sql.Register(chkDriverName, &sqlite3.SQLiteDriver{
-                       ConnectHook: makeConnectHookFn(CnkOnCloseModeEnabled),
+               NewDriverFromConfig(chkDriverName, DriverConfig{
+                       ChkOnClose: CnkOnCloseModeEnabled,
                })
        })
        return &Driver{
@@ -98,15 +128,10 @@
 //
 // If a driver with the given name already exists, a panic will occur.
 func NewDriver(name string, extensions []string, chkpt CnkOnCloseMode) *Driver 
{
-       sql.Register(name, &sqlite3.SQLiteDriver{
-               Extensions:  extensions,
-               ConnectHook: makeConnectHookFn(chkpt),
+       return NewDriverFromConfig(name, DriverConfig{
+               Extensions: extensions,
+               ChkOnClose: chkpt,
        })
-       return &Driver{
-               name:       name,
-               extensions: extensions,
-               chkOnClose: chkpt,
-       }
 }
 
 // Name returns the driver name.
@@ -134,13 +159,28 @@
        return d.chkOnClose
 }
 
-func makeConnectHookFn(chkpt CnkOnCloseMode) func(conn *sqlite3.SQLiteConn) 
error {
+// buildConnectHook composes a ConnectHook from cfg, chaining all requested
+// connection-level behaviors in order: checkpoint config, then query tracing.
+func buildConnectHook(cfg DriverConfig) func(conn *sqlite3.SQLiteConn) error {
        return func(conn *sqlite3.SQLiteConn) error {
-               if chkpt == CnkOnCloseModeDisabled {
+               // Checkpoint-on-close configuration.
+               if cfg.ChkOnClose == CnkOnCloseModeDisabled {
                        if err := conn.DBConfigNoCkptOnClose(); err != nil {
                                return fmt.Errorf("cannot disable checkpoint on 
close: %w", err)
                        }
                }
+
+               // Query tracing.
+               if cfg.QueryLogger != nil {
+                       if err := conn.SetTrace(&sqlite3.TraceConfig{
+                               Callback:        cfg.QueryLogger.TraceHook,
+                               EventMask:       sqlite3.TraceStmt | 
sqlite3.TraceProfile,
+                               WantExpandedSQL: true,
+                       }); err != nil {
+                               return err
+                       }
+               }
+
                return nil
        }
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rqlite-10.3.0/db/driver_test.go 
new/rqlite-10.3.1/db/driver_test.go
--- old/rqlite-10.3.0/db/driver_test.go 2026-09-01 17:26:59.000000000 +0200
+++ new/rqlite-10.3.1/db/driver_test.go 2026-09-04 04:11:36.000000000 +0200
@@ -1,7 +1,12 @@
 package db
 
 import (
+       "bytes"
+       "fmt"
+       "log"
        "os"
+       "strings"
+       "sync/atomic"
        "testing"
 
        "github.com/rqlite/rqlite/v10/internal/fsutil"
@@ -120,3 +125,81 @@
                t.Fatalf("NewDriver returned incorrect checkpoint mode: %v", 
d.CheckpointOnCloseMode())
        }
 }
+
+// A local counter for generating unique driver names.
+var driverTestSeq atomic.Int64
+
+func testDriverConfigName() string {
+       return fmt.Sprintf("test-driver-config-%d", driverTestSeq.Add(1))
+}
+
+// Verifies that a DriverConfig with a QueryLogger
+// produces log output for every executed statement.
+func Test_NewDriverFromConfig_QueryLogOnly(t *testing.T) {
+       var buf bytes.Buffer
+       logger := log.New(&buf, "", 0)
+       ql := NewQueryLogger(QueryLogConfig{Logger: logger})
+
+       d := NewDriverFromConfig(testDriverConfigName(), DriverConfig{
+               ChkOnClose:  CnkOnCloseModeDisabled,
+               QueryLogger: ql,
+       })
+
+       path := mustTempPath()
+       defer os.RemoveAll(path)
+       db, err := OpenWithDriver(d, path, false, true)
+       if err != nil {
+               t.Fatalf("OpenWithDriver failed: %s", err)
+       }
+       defer db.Close()
+
+       mustExecute(db, "CREATE TABLE t (id INTEGER PRIMARY KEY, val TEXT)")
+       mustExecute(db, "INSERT INTO t VALUES (1, 'hello')")
+
+       output := buf.String()
+       if !strings.Contains(output, "CREATE TABLE t") {
+               t.Fatalf("expected CREATE TABLE in query log, got:\n%s", output)
+       }
+       if !strings.Contains(output, "INSERT INTO t") {
+               t.Fatalf("expected INSERT in query log, got:\n%s", output)
+       }
+}
+
+// Verifies that a DriverConfig with nil
+// QueryLogger opens and operates normally without tracing.
+func Test_NewDriverFromConfig_NoQueryLog(t *testing.T) {
+       d := NewDriverFromConfig(testDriverConfigName(), DriverConfig{
+               ChkOnClose:  CnkOnCloseModeDisabled,
+               QueryLogger: nil,
+       })
+       if d.CheckpointOnCloseMode() != CnkOnCloseModeDisabled {
+               t.Fatalf("expected CnkOnCloseModeDisabled, got %v", 
d.CheckpointOnCloseMode())
+       }
+
+       path := mustTempPath()
+       defer os.RemoveAll(path)
+       db, err := OpenWithDriver(d, path, false, true)
+       if err != nil {
+               t.Fatalf("OpenWithDriver failed: %s", err)
+       }
+       defer db.Close()
+
+       mustExecute(db, "CREATE TABLE t (id INTEGER PRIMARY KEY)")
+}
+
+// Verifies that extension paths set in
+// DriverConfig are reflected on the returned Driver struct.
+func Test_DriverConfig_ExtensionsFields(t *testing.T) {
+       exts := []string{"/tmp/ext1.so", "/tmp/ext2.so"}
+       d := NewDriverFromConfig(testDriverConfigName(), DriverConfig{
+               Extensions: exts,
+               ChkOnClose: CnkOnCloseModeDisabled,
+       })
+       if len(d.Extensions()) != 2 {
+               t.Fatalf("expected 2 extensions, got %d", len(d.Extensions()))
+       }
+       names := d.ExtensionNames()
+       if names[0] != "ext1.so" || names[1] != "ext2.so" {
+               t.Fatalf("unexpected extension names: %v", names)
+       }
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rqlite-10.3.0/db/query_log_driver.go 
new/rqlite-10.3.1/db/query_log_driver.go
--- old/rqlite-10.3.0/db/query_log_driver.go    2026-09-01 17:26:59.000000000 
+0200
+++ new/rqlite-10.3.1/db/query_log_driver.go    1970-01-01 01:00:00.000000000 
+0100
@@ -1,62 +0,0 @@
-package db
-
-import (
-       "database/sql"
-       "sync"
-
-       "github.com/mattn/go-sqlite3"
-)
-
-const queryLogDriverName = "rqlite-sqlite3-querylog"
-
-var queryLogDriverOnce sync.Once
-
-// QueryLogDriver returns the query-log driver. It registers the SQLite3
-// driver with query logging support. It can be called multiple times but
-// only registers the driver once. The driver disables checkpoint-on-close
-// and installs the given QueryLogger's TraceHook on every new connection.
-func QueryLogDriver(ql *QueryLogger) *Driver {
-       queryLogDriverOnce.Do(func() {
-               sql.Register(queryLogDriverName, &sqlite3.SQLiteDriver{
-                       ConnectHook: makeQueryLogConnectHookFn(ql),
-               })
-       })
-       return &Driver{
-               name:       queryLogDriverName,
-               chkOnClose: CnkOnCloseModeDisabled,
-       }
-}
-
-// newTestQueryLogDriver registers a query-log driver with the given name.
-// It is used by tests to create isolated drivers.
-func newTestQueryLogDriver(name string, ql *QueryLogger) *Driver {
-       sql.Register(name, &sqlite3.SQLiteDriver{
-               ConnectHook: makeQueryLogConnectHookFn(ql),
-       })
-       return &Driver{
-               name:       name,
-               chkOnClose: CnkOnCloseModeDisabled,
-       }
-}
-
-// makeQueryLogConnectHookFn creates a ConnectHook that:
-// 1. Disables checkpoint-on-close (same as the default driver).
-// 2. Installs the QueryLogger's TraceHook via SetTrace.
-func makeQueryLogConnectHookFn(ql *QueryLogger) func(conn *sqlite3.SQLiteConn) 
error {
-       return func(conn *sqlite3.SQLiteConn) error {
-               if err := conn.DBConfigNoCkptOnClose(); err != nil {
-                       return err
-               }
-
-               if ql != nil {
-                       if err := conn.SetTrace(&sqlite3.TraceConfig{
-                               Callback:        ql.TraceHook,
-                               EventMask:       sqlite3.TraceStmt | 
sqlite3.TraceProfile,
-                               WantExpandedSQL: true,
-                       }); err != nil {
-                               return err
-                       }
-               }
-               return nil
-       }
-}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rqlite-10.3.0/db/query_log_integration_test.go 
new/rqlite-10.3.1/db/query_log_integration_test.go
--- old/rqlite-10.3.0/db/query_log_integration_test.go  2026-09-01 
17:26:59.000000000 +0200
+++ new/rqlite-10.3.1/db/query_log_integration_test.go  2026-09-04 
04:11:36.000000000 +0200
@@ -20,7 +20,10 @@
        logger := log.New(&buf, "[qlog] ", 0)
        ql := NewQueryLogger(QueryLogConfig{Logger: logger})
 
-       drv := newTestQueryLogDriver(testDriverName(), ql)
+       drv := NewDriverFromConfig(testDriverName(), DriverConfig{
+               ChkOnClose:  CnkOnCloseModeDisabled,
+               QueryLogger: ql,
+       })
        dbPath := t.TempDir() + "/test.db"
 
        db, err := OpenWithDriver(drv, dbPath, false, true)
@@ -56,7 +59,10 @@
 
 func Test_QueryLog_Integration_Disabled(t *testing.T) {
        ql := NewQueryLogger(QueryLogConfig{Logger: nil})
-       drv := newTestQueryLogDriver(testDriverName(), ql)
+       drv := NewDriverFromConfig(testDriverName(), DriverConfig{
+               ChkOnClose:  CnkOnCloseModeDisabled,
+               QueryLogger: ql,
+       })
        dbPath := t.TempDir() + "/test.db"
 
        db, err := OpenWithDriver(drv, dbPath, false, true)
@@ -89,7 +95,10 @@
        var buf bytes.Buffer
        logger := log.New(&buf, "", 0)
        ql := NewQueryLogger(QueryLogConfig{Logger: logger})
-       drv := newTestQueryLogDriver(testDriverName(), ql)
+       drv := NewDriverFromConfig(testDriverName(), DriverConfig{
+               ChkOnClose:  CnkOnCloseModeDisabled,
+               QueryLogger: ql,
+       })
        dbPath := t.TempDir() + "/test.db"
 
        db, err := OpenWithDriver(drv, dbPath, false, true)
@@ -128,7 +137,10 @@
        var buf bytes.Buffer
        logger := log.New(&buf, "", 0)
        ql := NewQueryLogger(QueryLogConfig{Logger: logger})
-       drv := newTestQueryLogDriver(testDriverName(), ql)
+       drv := NewDriverFromConfig(testDriverName(), DriverConfig{
+               ChkOnClose:  CnkOnCloseModeDisabled,
+               QueryLogger: ql,
+       })
        dbPath := t.TempDir() + "/test.db"
 
        db, err := OpenWithDriver(drv, dbPath, false, true)
@@ -157,7 +169,10 @@
 }
 
 func Test_QueryLog_Integration_NilQueryLogger(t *testing.T) {
-       drv := newTestQueryLogDriver(testDriverName(), nil)
+       drv := NewDriverFromConfig(testDriverName(), DriverConfig{
+               ChkOnClose:  CnkOnCloseModeDisabled,
+               QueryLogger: nil,
+       })
        dbPath := t.TempDir() + "/test.db"
 
        db, err := OpenWithDriver(drv, dbPath, false, true)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rqlite-10.3.0/snapshot/state.go 
new/rqlite-10.3.1/snapshot/state.go
--- old/rqlite-10.3.0/snapshot/state.go 2026-09-01 17:26:59.000000000 +0200
+++ new/rqlite-10.3.1/snapshot/state.go 2026-09-04 04:11:36.000000000 +0200
@@ -73,6 +73,9 @@
 
 // LatestIndexTerm returns the index and term of the most recent snapshot
 // in the given directory. If no snapshots are found, it returns 0, 0, nil.
+//
+// This function does not take any lock on the Snapshot store contained at dir,
+// so it not safe to call on an open Snapshot store.
 func LatestIndexTerm(dir string) (uint64, uint64, error) {
        cat := &SnapshotCatalog{}
        sset, err := cat.Scan(dir)
@@ -148,3 +151,19 @@
        }
        return it.Index(), it.Term(), nil
 }
+
+// StreamerIndexTerm returns the Raft index and term of the snapshot being 
streamed.
+// Raft does not hand the ReadCloser returned by Store.Open to the FSM 
directly, but
+// wraps it first, so any wrappers are unwrapped before the index and term are 
read.
+func StreamerIndexTerm(rc io.ReadCloser) (uint64, uint64, error) {
+       for src := rc; ; {
+               if it, ok := src.(IndexTermer); ok {
+                       return it.Index(), it.Term(), nil
+               }
+               w, ok := src.(raft.ReadCloserWrapper)
+               if !ok {
+                       return 0, 0, fmt.Errorf("snapshot streamer of type %T 
does not know its index and term", rc)
+               }
+               src = w.WrappedReadCloser()
+       }
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rqlite-10.3.0/snapshot/store.go 
new/rqlite-10.3.1/snapshot/store.go
--- old/rqlite-10.3.0/snapshot/store.go 2026-09-01 17:26:59.000000000 +0200
+++ new/rqlite-10.3.1/snapshot/store.go 2026-09-04 04:11:36.000000000 +0200
@@ -111,22 +111,25 @@
 
        mu    sync.Mutex
        timer *time.Timer
+
+       meta *raft.SnapshotMeta
 }
 
 // NewLockingStreamer returns a new LockingStreamer. If timeout > 0, the
 // streamer will be force-closed after that much time elapses with no Read
 // activity. A timeout of 0 disables the idle check.
-func NewLockingStreamer(rc io.ReadCloser, str *Store, timeout time.Duration) 
*LockingStreamer {
+func NewLockingStreamer(rc io.ReadCloser, str *Store, mt *raft.SnapshotMeta, 
to time.Duration) *LockingStreamer {
        l := &LockingStreamer{
                ReadCloser: rc,
                str:        str,
-               timeout:    timeout,
+               timeout:    to,
                timedOut:   rsync.NewAtomicBool(),
                closed:     rsync.NewAtomicBool(),
+               meta:       mt,
        }
        l.lastRead.Store(time.Now().UnixNano())
-       if timeout > 0 {
-               l.timer = time.AfterFunc(timeout, l.checkIdle)
+       if to > 0 {
+               l.timer = time.AfterFunc(to, l.checkIdle)
        }
        return l
 }
@@ -146,6 +149,16 @@
        return n, err
 }
 
+// Index returns the Raft Index of the streamed Snapshot
+func (l *LockingStreamer) Index() uint64 {
+       return l.meta.Index
+}
+
+// Term returns the Raft Term of the streamed Snapshot
+func (l *LockingStreamer) Term() uint64 {
+       return l.meta.Term
+}
+
 // Close closes the Snapshot and releases the Snapshot Store lock.
 func (l *LockingStreamer) Close() error {
        l.mu.Lock()
@@ -442,7 +455,7 @@
        }
        meta.Size = sz
 
-       return meta, NewLockingStreamer(streamer, s, s.readTimeout), nil
+       return meta, NewLockingStreamer(streamer, s, meta, s.readTimeout), nil
 }
 
 // RegisterObserver registers an observer to receive observations.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rqlite-10.3.0/store/store.go 
new/rqlite-10.3.1/store/store.go
--- old/rqlite-10.3.0/store/store.go    2026-09-01 17:26:59.000000000 +0200
+++ new/rqlite-10.3.1/store/store.go    2026-09-04 04:11:36.000000000 +0200
@@ -782,16 +782,21 @@
                stats.Add(numRecoveries, 1)
        }
 
-       // If SQLite extensions are specified, we need a custom driver.
-       if len(s.dbConf.Extensions) > 0 {
-               s.dbDrv = 
sql.NewDriver(random.StringPattern("rqlite-extended-xxxx-xxxx-xxxx"),
-                       s.dbConf.Extensions, sql.CnkOnCloseModeDisabled)
-       }
-
-       // If query logging is configured, use the query-log driver.
-       if s.dbConf.QueryLogger != nil {
-               ql := sql.NewQueryLogger(sql.QueryLogConfig{Logger: 
s.dbConf.QueryLogger})
-               s.dbDrv = sql.QueryLogDriver(ql)
+       // Build a composed driver if any non-default features are configured.
+       // This replaces the previous sequential override pattern and allows
+       // extensions and query logging to coexist in the same driver.
+       if len(s.dbConf.Extensions) > 0 || s.dbConf.QueryLogger != nil {
+               cfg := sql.DriverConfig{
+                       Extensions: s.dbConf.Extensions,
+                       ChkOnClose: sql.CnkOnCloseModeDisabled,
+               }
+               if s.dbConf.QueryLogger != nil {
+                       cfg.QueryLogger = 
sql.NewQueryLogger(sql.QueryLogConfig{Logger: s.dbConf.QueryLogger})
+               }
+               s.dbDrv = sql.NewDriverFromConfig(
+                       
random.StringPattern("rqlite-configured-xxxx-xxxx-xxxx"),
+                       cfg,
+               )
        }
 
        s.db, err = createDBOnDisk(s.dbPath, s.dbDrv, removeDBFiles, 
s.dbConf.FKConstraints, s.MaxReadOnlyConns)
@@ -2782,9 +2787,14 @@
 // will not be called concurrently with Apply(), so synchronization with 
Execute()
 // is not necessary.
 func (s *Store) fsmRestore(rc io.ReadCloser) (retErr error) {
+       hardExitRequired := false
+
        defer func() {
                if retErr != nil {
                        stats.Add(numRestoresFailed, 1)
+                       if hardExitRequired {
+                               s.logger.Fatalf("restore failed post database 
swap, aborting: %s", retErr)
+                       }
                }
        }()
        s.logger.Printf("initiating node restore on node ID %s", s.raftID)
@@ -2812,26 +2822,39 @@
                s.logger.Printf("error closing snapshot reader after restore: 
%s", err)
        }
 
+       // Take conservative approach and assume that everything has changed, 
so update
+       // the indexes. It is possible that dbAppliedIdx is now ahead of some 
other nodes'
+       // same value, since the last index is not necessarily a 
database-changing index,
+       // but that is OK. Worse that can happen is that anything paying 
attention to the
+       // index might consider the database to be changed when it is not, 
*logically* speaking.
+       li, tm, err := snapshot.StreamerIndexTerm(rc)
+       if err != nil {
+               return fmt.Errorf("failed to get streamed snapshot index and 
term: %s", err)
+       }
+
        // Any existing SQLite file is about to be invalid, so mark that we 
can't
        // fast-restart with it.
        if err := fsutil.RemoveFile(s.cleanSnapshotPath); err != nil {
                return fmt.Errorf("failed to remove clean snapshot file: %w", 
err)
        }
+
+       // We're about to enter a point of no-return. We're going to swap in 
the incoming
+       // database with the one on disk. If an error is encountered after 
swapping the
+       // and simply returned that error to the Raft subsystem, Raft would 
consider the
+       // Restore as FAILED, but the database on disk would be wrong. We could 
attempt
+       // to reverse the swap, but a simpler, easier to ensure it's correct, 
solution
+       // is to just exit hard and let the node restart. It will then do a 
full restore
+       // from Raft.
+       //
+       // Doing it like this protects agains future changes in the code by 
catching the
+       // error in the defer handler.
+       hardExitRequired = true
+
        if err := s.db.Swap(tmpPath, s.dbConf.FKConstraints, true); err != nil {
                return fmt.Errorf("error swapping database file: %v", err)
        }
        s.logger.Printf("successfully opened database at %s due to restore", 
s.db.Path())
 
-       // Take conservative approach and assume that everything has changed, 
so update
-       // the indexes. It is possible that dbAppliedIdx is now ahead of some 
other nodes'
-       // same value, since the last index is not necessarily a 
database-changing index,
-       // but that is OK. Worse that can happen is that anything paying 
attention to the
-       // index might consider the database to be changed when it is not, 
*logically* speaking.
-       li, tm, err := snapshot.LatestIndexTerm(s.snapshotDir)
-       if err != nil {
-               return fmt.Errorf("failed to get latest snapshot index post 
restore: %s", err)
-       }
-
        // Installed SQLite database is safe for fast restarts again. It is 
fingerprinted
        // against the very index and term the node is adopting here, so that a 
restart
        // can tell that the two still correspond to one another.
@@ -2844,11 +2867,7 @@
        s.fsmTerm.Store(tm)
        s.dbAppliedIdx.Store(li)
        s.appliedTarget.Signal(li)
-       lt, err := s.db.DBLastModified()
-       if err != nil {
-               return fmt.Errorf("failed to get last modified time: %s", err)
-       }
-       s.dbModifiedTime.Store(lt)
+       s.dbModifiedTime.Store(time.Now())
        // Swapping in a new database deactivates the CDC hooks, so signal that 
it
        // needs to be reregistered on the next commit.
        s.cdcRegistered.Unset()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rqlite-10.3.0/store/store_snapshot_test.go 
new/rqlite-10.3.1/store/store_snapshot_test.go
--- old/rqlite-10.3.0/store/store_snapshot_test.go      2026-09-01 
17:26:59.000000000 +0200
+++ new/rqlite-10.3.1/store/store_snapshot_test.go      2026-09-04 
04:11:36.000000000 +0200
@@ -73,7 +73,7 @@
                t.Fatalf("failed to open snapshot file: %s", err.Error())
        }
        defer snapFile.Close()
-       if err := fsm.Restore(snapFile); err != nil {
+       if err := fsm.Restore(&mockSnapshotStreamer{snapFile}); err != nil {
                t.Fatalf("failed to restore snapshot from disk: %s", 
err.Error())
        }
 
@@ -885,6 +885,20 @@
        return nil
 }
 
+// mockSnapshotStreamer stands in for the ReadCloser Raft hands to the FSM 
during
+// a restore, which always knows the index and term of the snapshot it streams.
+type mockSnapshotStreamer struct {
+       *os.File
+}
+
+func (m *mockSnapshotStreamer) Index() uint64 {
+       return 1
+}
+
+func (m *mockSnapshotStreamer) Term() uint64 {
+       return 1
+}
+
 func mustExecute(t *testing.T, s *Store, queries []string) 
[]*proto.ExecuteQueryResponse {
        t.Helper()
        rows, _, err := s.Execute(context.Background(), 
executeRequestFromStrings(queries, false, false))

++++++ vendor.tar.xz ++++++

Reply via email to