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-01-08 15:27:08
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/rqlite (Old)
 and      /work/SRC/openSUSE:Factory/.rqlite.new.1928 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "rqlite"

Thu Jan  8 15:27:08 2026 rev:39 rq:1325841 version:9.3.11

Changes:
--------
--- /work/SRC/openSUSE:Factory/rqlite/rqlite.changes    2026-01-06 
17:46:59.095551933 +0100
+++ /work/SRC/openSUSE:Factory/.rqlite.new.1928/rqlite.changes  2026-01-08 
15:28:28.791685656 +0100
@@ -1,0 +2,6 @@
+Wed Jan 07 19:06:58 UTC 2026 - Andreas Stieger <[email protected]>
+
+- Update to version 9.3.11:
+  * Correctly handle EXPLAIN QUERY PLAN for mutations
+
+-------------------------------------------------------------------

Old:
----
  rqlite-9.3.10.tar.xz

New:
----
  rqlite-9.3.11.tar.xz

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

Other differences:
------------------
++++++ rqlite.spec ++++++
--- /var/tmp/diff_new_pack.ly14fK/_old  2026-01-08 15:28:29.351708902 +0100
+++ /var/tmp/diff_new_pack.ly14fK/_new  2026-01-08 15:28:29.351708902 +0100
@@ -17,7 +17,7 @@
 
 
 Name:           rqlite
-Version:        9.3.10
+Version:        9.3.11
 Release:        0
 Summary:        Distributed relational database built on SQLite
 License:        MIT

++++++ _service ++++++
--- /var/tmp/diff_new_pack.ly14fK/_old  2026-01-08 15:28:29.387710397 +0100
+++ /var/tmp/diff_new_pack.ly14fK/_new  2026-01-08 15:28:29.391710563 +0100
@@ -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">v9.3.10</param>
+    <param name="revision">v9.3.11</param>
     <param name="versionformat">@PARENT_TAG@</param>
     <param name="changesgenerate">enable</param>
     <param name="versionrewrite-pattern">v(.*)</param>

++++++ _servicedata ++++++
--- /var/tmp/diff_new_pack.ly14fK/_old  2026-01-08 15:28:29.427712057 +0100
+++ /var/tmp/diff_new_pack.ly14fK/_new  2026-01-08 15:28:29.431712223 +0100
@@ -1,7 +1,7 @@
 <servicedata>
   <service name="tar_scm">
     <param name="url">https://github.com/rqlite/rqlite.git</param>
-    <param 
name="changesrevision">fdd7ac15bf191ab7c850e60e5cd928484553c57a</param>
+    <param 
name="changesrevision">9589c3caa5da939fb3e2bbb143e0b06b301ac1dd</param>
   </service>
 </servicedata>
 (No newline at EOF)

++++++ rqlite-9.3.10.tar.xz -> rqlite-9.3.11.tar.xz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rqlite-9.3.10/CHANGELOG.md 
new/rqlite-9.3.11/CHANGELOG.md
--- old/rqlite-9.3.10/CHANGELOG.md      2026-01-05 14:44:28.000000000 +0100
+++ new/rqlite-9.3.11/CHANGELOG.md      2026-01-07 05:16:04.000000000 +0100
@@ -1,3 +1,8 @@
+## v9.3.11 (January 6th 2026)
+### Implementation changes and bug fixes
+- [PR #2438](https://github.com/rqlite/rqlite/pull/2438): Correctly handle 
`EXPLAIN QUERY PLAN` for mutations. Fixes issue 
[#2433](https://github.com/rqlite/rqlite/issues/2433).
+- [PR #2436](https://github.com/rqlite/rqlite/pull/2436): Add unit testing for 
EXPLAIN SELECT at DB level.
+
 ## v9.3.10 (January 5th 2026)
 ### Implementation changes and bug fixes
 - [PR #2427](https://github.com/rqlite/rqlite/pull/2427): Remove any temporary 
WAL files if persisting a Snapshot fails or is not even invoked.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rqlite-9.3.10/command/proto/command.pb.go 
new/rqlite-9.3.11/command/proto/command.pb.go
--- old/rqlite-9.3.10/command/proto/command.pb.go       2026-01-05 
14:44:28.000000000 +0100
+++ new/rqlite-9.3.11/command/proto/command.pb.go       2026-01-07 
05:16:04.000000000 +0100
@@ -440,6 +440,7 @@
        Parameters    []*Parameter           
`protobuf:"bytes,2,rep,name=parameters,proto3" json:"parameters,omitempty"`
        ForceQuery    bool                   
`protobuf:"varint,3,opt,name=forceQuery,proto3" json:"forceQuery,omitempty"`
        ForceStall    bool                   
`protobuf:"varint,4,opt,name=forceStall,proto3" json:"forceStall,omitempty"`
+       SqlExplain    bool                   
`protobuf:"varint,5,opt,name=sql_explain,json=sqlExplain,proto3" 
json:"sql_explain,omitempty"`
        unknownFields protoimpl.UnknownFields
        sizeCache     protoimpl.SizeCache
 }
@@ -502,6 +503,13 @@
        return false
 }
 
+func (x *Statement) GetSqlExplain() bool {
+       if x != nil {
+               return x.SqlExplain
+       }
+       return false
+}
+
 type Request struct {
        state           protoimpl.MessageState `protogen:"open.v1"`
        Transaction     bool                   
`protobuf:"varint,1,opt,name=transaction,proto3" json:"transaction,omitempty"`
@@ -2094,7 +2102,7 @@
        "\x01y\x18\x04 \x01(\fH\x00R\x01y\x12\x0e\n" +
        "\x01s\x18\x05 \x01(\tH\x00R\x01s\x12\x12\n" +
        "\x04name\x18\x06 \x01(\tR\x04nameB\a\n" +
-       "\x05value\"\x91\x01\n" +
+       "\x05value\"\xb2\x01\n" +
        "\tStatement\x12\x10\n" +
        "\x03sql\x18\x01 \x01(\tR\x03sql\x122\n" +
        "\n" +
@@ -2105,7 +2113,9 @@
        "forceQuery\x12\x1e\n" +
        "\n" +
        "forceStall\x18\x04 \x01(\bR\n" +
-       "forceStall\"\xa7\x01\n" +
+       "forceStall\x12\x1f\n" +
+       "\vsql_explain\x18\x05 \x01(\bR\n" +
+       "sqlExplain\"\xa7\x01\n" +
        "\aRequest\x12 \n" +
        "\vtransaction\x18\x01 \x01(\bR\vtransaction\x122\n" +
        "\n" +
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rqlite-9.3.10/command/proto/command.proto 
new/rqlite-9.3.11/command/proto/command.proto
--- old/rqlite-9.3.10/command/proto/command.proto       2026-01-05 
14:44:28.000000000 +0100
+++ new/rqlite-9.3.11/command/proto/command.proto       2026-01-07 
05:16:04.000000000 +0100
@@ -20,6 +20,7 @@
        repeated Parameter parameters = 2;
        bool forceQuery = 3;
        bool forceStall = 4;
+       bool sql_explain = 5;
 }
 
 message Request {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rqlite-9.3.10/command/sql/processor.go 
new/rqlite-9.3.11/command/sql/processor.go
--- old/rqlite-9.3.10/command/sql/processor.go  2026-01-05 14:44:28.000000000 
+0100
+++ new/rqlite-9.3.11/command/sql/processor.go  2026-01-07 05:16:04.000000000 
+0100
@@ -51,13 +51,15 @@
                lowered := strings.ToLower(stmts[i].Sql)
                if (!rwtime || !ContainsTime(lowered)) &&
                        (!rwrand || !ContainsRandom(lowered)) &&
-                       !ContainsReturning(lowered) {
+                       !ContainsReturning(lowered) &&
+                       !ContainsExplain(lowered) {
                        continue
                }
                parsed, err := 
rsql.NewParser(strings.NewReader(stmts[i].Sql)).ParseStatement()
                if err != nil {
                        continue
                }
+               _, stmts[i].SqlExplain = parsed.(*sql.ExplainStatement)
                rewriter := NewRewriter()
                rewriter.RewriteRand = rwrand
                rewriter.RewriteTime = rwtime
@@ -110,6 +112,13 @@
        return strings.Contains(stmt, "returning ")
 }
 
+// ContainsExplain returns true if the statement contains an EXPLAIN clause.
+// The function performs a lower-case comparison so it is up to the caller to
+// ensure the statement is lower-cased.
+func ContainsExplain(stmt string) bool {
+       return strings.Contains(stmt, "explain ")
+}
+
 // Rewriter rewrites SQL statements.
 type Rewriter struct {
        RewriteRand bool
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rqlite-9.3.10/db/db.go new/rqlite-9.3.11/db/db.go
--- old/rqlite-9.3.10/db/db.go  2026-01-05 14:44:28.000000000 +0100
+++ new/rqlite-9.3.11/db/db.go  2026-01-07 05:16:04.000000000 +0100
@@ -1168,7 +1168,7 @@
                        allRows = append(allRows, rows)
                        continue
                }
-               if !readOnly {
+               if !readOnly && !stmt.SqlExplain {
                        stats.Add(numQueryErrors, 1)
                        rows = &command.QueryRows{
                                Error: "attempt to change database via query 
operation",
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rqlite-9.3.10/db/db_common_test.go 
new/rqlite-9.3.11/db/db_common_test.go
--- old/rqlite-9.3.10/db/db_common_test.go      2026-01-05 14:44:28.000000000 
+0100
+++ new/rqlite-9.3.11/db/db_common_test.go      2026-01-07 05:16:04.000000000 
+0100
@@ -106,6 +106,64 @@
        }
 }
 
+func Test_DB_ExplainSelect(t *testing.T) {
+       db, path := mustCreateOnDiskDatabaseWAL()
+       defer os.Remove(path)
+       defer db.Close()
+
+       // Create a table
+       r, err := db.ExecuteStringStmt("CREATE TABLE foo (id INTEGER NOT NULL 
PRIMARY KEY, name TEXT)")
+       if err != nil {
+               t.Fatalf("failed to create table: %s", err.Error())
+       }
+       if exp, got := `[{}]`, asJSON(r); exp != got {
+               t.Fatalf("unexpected results for create table, expected %s, got 
%s", exp, got)
+       }
+
+       // Insert a record
+       r, err = db.ExecuteStringStmt(`INSERT INTO foo(id, name) VALUES(1, 
"test")`)
+       if err != nil {
+               t.Fatalf("failed to insert record: %s", err.Error())
+       }
+       if exp, got := `[{"last_insert_id":1,"rows_affected":1}]`, asJSON(r); 
exp != got {
+               t.Fatalf("unexpected results for insert, expected %s, got %s", 
exp, got)
+       }
+
+       // Execute EXPLAIN SELECT
+       q, err := db.QueryStringStmt("EXPLAIN SELECT * FROM foo")
+       if err != nil {
+               t.Fatalf("failed to execute EXPLAIN SELECT: %s", err.Error())
+       }
+
+       // Check that we got results with expected columns
+       if len(q) == 0 {
+               t.Fatalf("EXPLAIN SELECT returned no results")
+       }
+       if len(q[0].Columns) == 0 {
+               t.Fatalf("EXPLAIN SELECT returned no columns")
+       }
+
+       // Verify that columns include typical EXPLAIN columns (like "addr", 
"opcode", etc.)
+       columns := q[0].Columns
+       hasAddr := false
+       hasOpcode := false
+       hasComment := false
+       for _, col := range columns {
+               if col == "addr" {
+                       hasAddr = true
+               }
+               if col == "opcode" {
+                       hasOpcode = true
+               }
+               if col == "comment" {
+                       hasComment = true
+               }
+       }
+       if !hasAddr || !hasOpcode || !hasComment {
+               t.Fatalf("EXPLAIN SELECT did not return expected columns (addr, 
opcode), got: %v", columns)
+       }
+}
+
 func Test_DB_TableCreationFTS(t *testing.T) {
        db, path := mustCreateOnDiskDatabaseWAL()
        defer os.Remove(path)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rqlite-9.3.10/http/service.go 
new/rqlite-9.3.11/http/service.go
--- old/rqlite-9.3.10/http/service.go   2026-01-05 14:44:28.000000000 +0100
+++ new/rqlite-9.3.11/http/service.go   2026-01-07 05:16:04.000000000 +0100
@@ -1443,14 +1443,11 @@
        }
        stats.Add(numQueryStmtsRx, int64(len(queries)))
 
-       // No point rewriting queries if they don't go through the Raft log, 
since they
-       // will never be replayed from the log anyway.
-       if qp.Level() == proto.ConsistencyLevel_STRONG {
-               if !qp.NoParse() {
-                       if err := sql.Process(queries, qp.NoRewriteRandom(), 
!qp.NoRewriteTime()); err != nil {
-                               http.Error(w, fmt.Sprintf("SQL rewrite: %s", 
err.Error()), http.StatusInternalServerError)
-                               return
-                       }
+       if !qp.NoParse() {
+               fmt.Println("Processing queries:", queries)
+               if err := sql.Process(queries, qp.NoRewriteRandom(), 
!qp.NoRewriteTime()); err != nil {
+                       http.Error(w, fmt.Sprintf("SQL rewrite: %s", 
err.Error()), http.StatusInternalServerError)
+                       return
                }
        }
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rqlite-9.3.10/store/store.go 
new/rqlite-9.3.11/store/store.go
--- old/rqlite-9.3.10/store/store.go    2026-01-05 14:44:28.000000000 +0100
+++ new/rqlite-9.3.11/store/store.go    2026-01-07 05:16:04.000000000 +0100
@@ -2153,13 +2153,17 @@
 }
 
 // RORWCount returns the number of read-only and read-write statements in the
-// given ExecuteQueryRequest.
+// given ExecuteQueryRequest. EXPLAIN statements are always considered 
read-only.
 func (s *Store) RORWCount(eqr *proto.ExecuteQueryRequest) (nRW, nRO int) {
        for _, stmt := range eqr.Request.Statements {
                sql := stmt.Sql
                if sql == "" {
                        continue
                }
+               if stmt.SqlExplain {
+                       nRO++
+                       continue
+               }
                ro, err := s.db.StmtReadOnly(sql)
                if err == nil && ro {
                        nRO++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rqlite-9.3.10/store/store_test.go 
new/rqlite-9.3.11/store/store_test.go
--- old/rqlite-9.3.10/store/store_test.go       2026-01-05 14:44:28.000000000 
+0100
+++ new/rqlite-9.3.11/store/store_test.go       2026-01-07 05:16:04.000000000 
+0100
@@ -759,6 +759,78 @@
        }
 }
 
+func Test_SingleNodeExecuteQuery_EXPLAIN(t *testing.T) {
+       s, ln := mustNewStore(t)
+       defer ln.Close()
+
+       if err := s.Open(); err != nil {
+               t.Fatalf("failed to open single-node store: %s", err.Error())
+       }
+       if err := s.Bootstrap(NewServer(s.ID(), s.Addr(), true)); err != nil {
+               t.Fatalf("failed to bootstrap single-node store: %s", 
err.Error())
+       }
+       defer s.Close(true)
+       _, err := s.WaitForLeader(10 * time.Second)
+       if err != nil {
+               t.Fatalf("Error waiting for leader: %s", err)
+       }
+
+       er := executeRequestFromString(`CREATE TABLE foo (id INTEGER NOT NULL 
PRIMARY KEY, name TEXT)`,
+               false, false)
+       _, _, err = s.Execute(er)
+       if err != nil {
+               t.Fatalf("failed to execute on single node: %s", err.Error())
+       }
+
+       // Simple read-only statement.
+       eqr := executeQueryRequestFromString("EXPLAIN QUERY PLAN SELECT * FROM 
foo",
+               proto.ConsistencyLevel_WEAK, false, false)
+       eqr.Request.Statements[0].SqlExplain = true
+       resp, _, _, err := s.Request(eqr)
+       if err != nil {
+               t.Fatalf("failed to perform EXPLAIN SELECT on single node: %s", 
err.Error())
+       }
+       if !strings.Contains(asJSON(resp), "SCAN foo") { // Simple check that 
it looks right
+               t.Fatalf("unexpected results for EXPLAIN QUERY PLAN\ngot: %s", 
asJSON(resp))
+       }
+
+       // SQLite C code considers this a read-write statement so check that
+       // the Store handles this by converting to query.
+       eqr = executeQueryRequestFromString(`EXPLAIN QUERY PLAN INSERT INTO 
foo(name) VALUES("fiona")`,
+               proto.ConsistencyLevel_WEAK, false, false)
+       eqr.Request.Statements[0].SqlExplain = true
+       resp, _, _, err = s.Request(eqr)
+       if err != nil {
+               t.Fatalf("failed to perform EXPLAIN INSERT on single node: %s", 
err.Error())
+       }
+       if !strings.Contains(asJSON(resp), "columns") { // Simple check that it 
looks right
+               t.Fatalf("unexpected results for EXPLAIN QUERY PLAN\ngot: %s", 
asJSON(resp))
+       }
+
+       // Check that EXPLAIN sent directory to query endpoint also works OK.
+       qr := queryRequestFromString("EXPLAIN QUERY PLAN SELECT * FROM foo", 
false, false)
+       qr.Level = proto.ConsistencyLevel_WEAK
+       qr.Request.Statements[0].SqlExplain = true
+       rows, _, _, err := s.Query(qr)
+       if err != nil {
+               t.Fatalf("failed to perform EXPLAIN SELECT on single node: %s", 
err.Error())
+       }
+       if !strings.Contains(asJSON(rows), "SCAN foo") { // Simple check that 
it looks right
+               t.Fatalf("unexpected results for EXPLAIN QUERY PLAN\ngot: %s", 
asJSON(rows))
+       }
+
+       qr = queryRequestFromString(`EXPLAIN QUERY PLAN INSERT INTO foo(name) 
VALUES("fiona")`, false, false)
+       qr.Level = proto.ConsistencyLevel_WEAK
+       qr.Request.Statements[0].SqlExplain = true
+       rows, _, _, err = s.Query(qr)
+       if err != nil {
+               t.Fatalf("failed to perform EXPLAIN SELECT on single node: %s", 
err.Error())
+       }
+       if !strings.Contains(asJSON(rows), "columns") { // Simple check that it 
looks right
+               t.Fatalf("unexpected results for EXPLAIN QUERY PLAN\ngot: %s", 
asJSON(rows))
+       }
+}
+
 func Test_SingleNodeExecuteQuery_RETURNING(t *testing.T) {
        s, ln := mustNewStore(t)
        defer ln.Close()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rqlite-9.3.10/system_test/single_node_test.go 
new/rqlite-9.3.11/system_test/single_node_test.go
--- old/rqlite-9.3.10/system_test/single_node_test.go   2026-01-05 
14:44:28.000000000 +0100
+++ new/rqlite-9.3.11/system_test/single_node_test.go   2026-01-07 
05:16:04.000000000 +0100
@@ -942,6 +942,32 @@
        }
 }
 
+func Test_SingleNode_EXPLAIN(t *testing.T) {
+       node := mustNewLeaderNode("leader1")
+       defer node.Deprovision()
+
+       _, err := node.Execute(`CREATE TABLE foo (id integer not null primary 
key, name text)`)
+       if err != nil {
+               t.Fatalf(`CREATE TABLE failed: %s`, err.Error())
+       }
+
+       res, err := node.Query(`EXPLAIN QUERY PLAN INSERT INTO foo(name) 
VALUES("declan")`)
+       if err != nil {
+               t.Fatalf(`EXPLAIN failed: %s`, err.Error())
+       }
+       if !strings.Contains(res, "notused") {
+               t.Fatalf("EXPLAIN result does not appear valid: %s", res)
+       }
+
+       res, err = node.Query(`EXPLAIN QUERY PLAN SELECT * FROM foo`)
+       if err != nil {
+               t.Fatalf(`EXPLAIN failed: %s`, err.Error())
+       }
+       if !strings.Contains(res, "notused") {
+               t.Fatalf("EXPLAIN result does not appear valid: %s", res)
+       }
+}
+
 func Test_SingleNodeQueued(t *testing.T) {
        node := mustNewLeaderNode("leader1")
        defer node.Deprovision()

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

Reply via email to