]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Add experimental API sqlite3_set_errmsg(). Use this in sqlite3changeset_apply() to...
authordan <Dan Kennedy>
Fri, 12 Sep 2025 15:02:47 +0000 (15:02 +0000)
committerdan <Dan Kennedy>
Fri, 12 Sep 2025 15:02:47 +0000 (15:02 +0000)
FossilOrigin-Name: 4d5b60a1e57448f03af2a657fe7cdabb04ebaf9688d5cc700dd8f9892a5cba15

ext/session/session9.test
ext/session/sqlite3session.c
manifest
manifest.tags
manifest.uuid
src/main.c
src/sqlite.h.in

index 5c406c344e86fde3adee7e930a0f86e3f3ab208d..6207aae427da2c8987787e6f1da4781b42110488 100644 (file)
@@ -290,4 +290,39 @@ do_execsql_test 5.4 {
   SELECT * FROM X1;
 } {1 1}
 
+#--------------------------------------------------------------------
+reset_db
+do_execsql_test 6.0 {
+  CREATE TABLE p1(a INTEGER PRIMARY KEY, b);
+  CREATE TABLE c1(x INTEGER PRIMARY KEY, y REFERENCES p1);
+}
+
+set ::cc [changeset_from_sql {
+  INSERT INTO c1 VALUES(10, 20);
+}]
+
+forcedelete test.db2
+sqlite3 db2 test.db2
+do_execsql_test -db db2 6.1 {
+  CREATE TABLE c1(x INTEGER PRIMARY KEY, y REFERENCES p1);
+  PRAGMA foreign_keys = ON;
+  PRAGMA foreign_keys;
+} {1}
+
+proc conflict_handler {args} { 
+  puts $args
+  return "OMIT" 
+}
+
+do_test 6.2 {
+  list [catch {sqlite3changeset_apply_v2 db2 $::cc conflict_handler} msg] $msg
+} {1 SQLITE_ERROR}
+
+do_test 6.3.1 { sqlite3_errmsg db2 }  {no such table: main.p1}
+do_test 6.3.2 { sqlite3_errcode db2 } {SQLITE_ERROR}
+
+do_test 6.4 {
+  catchsql { INSERT INTO c1 VALUES(100, 200) } db2
+} {1 {no such table: main.p1}}
+
 finish_test
index 81ba73834051172ea9d1fe80ba489a9dfc675f3e..586b51a83ce4b2878af1d97b2030864dba73d9a5 100644 (file)
@@ -2815,6 +2815,19 @@ static int sessionAppendDelete(
   return rc;
 }
 
+static int sessionPrepare(
+  sqlite3 *db, 
+  sqlite3_stmt **pp, 
+  char **pzErrmsg,
+  const char *zSql
+){
+  int rc = sqlite3_prepare_v2(db, zSql, -1, pp, 0);
+  if( pzErrmsg && rc!=SQLITE_OK ){
+    *pzErrmsg = sqlite3_mprintf("%s", sqlite3_errmsg(db));
+  }
+  return rc;
+}
+
 /*
 ** Formulate and prepare a SELECT statement to retrieve a row from table
 ** zTab in database zDb based on its primary key. i.e.
@@ -2836,12 +2849,12 @@ static int sessionSelectStmt(
   int nCol,                       /* Number of columns in table */
   const char **azCol,             /* Names of table columns */
   u8 *abPK,                       /* PRIMARY KEY  array */
-  sqlite3_stmt **ppStmt           /* OUT: Prepared SELECT statement */
+  sqlite3_stmt **ppStmt,          /* OUT: Prepared SELECT statement */
+  char **pzErrmsg                 /* OUT: Error message */
 ){
   int rc = SQLITE_OK;
   char *zSql = 0;
   const char *zSep = "";
-  int nSql = -1;
   int i;
 
   SessionBuffer cols = {0, 0, 0};
@@ -2921,7 +2934,7 @@ static int sessionSelectStmt(
 #endif
 
   if( rc==SQLITE_OK ){
-    rc = sqlite3_prepare_v2(db, zSql, nSql, ppStmt, 0);
+    rc = sessionPrepare(db, ppStmt, pzErrmsg, zSql);
   }
   sqlite3_free(zSql);
   sqlite3_free(nooptest.aBuf);
@@ -3085,7 +3098,7 @@ static int sessionGenerateChangeset(
       /* Build and compile a statement to execute: */
       if( rc==SQLITE_OK ){
         rc = sessionSelectStmt(db, 0, pSession->zDb, 
-            zName, pTab->bRowid, pTab->nCol, pTab->azCol, pTab->abPK, &pSel
+            zName, pTab->bRowid, pTab->nCol, pTab->azCol, pTab->abPK, &pSel, 0
         );
       }
 
@@ -4294,6 +4307,7 @@ struct SessionApplyCtx {
   u8 bRebase;                     /* True to collect rebase information */
   u8 bIgnoreNoop;                 /* True to ignore no-op conflicts */
   int bRowid;
+  char *zErr;                     /* Error message, if any */
 };
 
 /* Number of prepared UPDATE statements to cache. */
@@ -4519,7 +4533,7 @@ static int sessionDeleteRow(
   }
 
   if( rc==SQLITE_OK ){
-    rc = sqlite3_prepare_v2(db, (char *)buf.aBuf, buf.nBuf, &p->pDelete, 0);
+    rc = sessionPrepare(db, &p->pDelete, &p->zErr, (char*)buf.aBuf);
   }
   sqlite3_free(buf.aBuf);
 
@@ -4546,7 +4560,7 @@ static int sessionSelectRow(
 ){
   /* TODO */
   return sessionSelectStmt(db, p->bIgnoreNoop,
-      "main", zTab, p->bRowid, p->nCol, p->azCol, p->abPK, &p->pSelect
+      "main", zTab, p->bRowid, p->nCol, p->azCol, p->abPK, &p->pSelect, &p->zErr
   );
 }
 
@@ -4583,16 +4597,12 @@ static int sessionInsertRow(
   sessionAppendStr(&buf, ")", &rc);
 
   if( rc==SQLITE_OK ){
-    rc = sqlite3_prepare_v2(db, (char *)buf.aBuf, buf.nBuf, &p->pInsert, 0);
+    rc = sessionPrepare(db, &p->pInsert, &p->zErr, (char*)buf.aBuf);
   }
   sqlite3_free(buf.aBuf);
   return rc;
 }
 
-static int sessionPrepare(sqlite3 *db, sqlite3_stmt **pp, const char *zSql){
-  return sqlite3_prepare_v2(db, zSql, -1, pp, 0);
-}
-
 /*
 ** Prepare statements for applying changes to the sqlite_stat1 table.
 ** These are similar to those created by sessionSelectRow(),
@@ -4602,14 +4612,14 @@ static int sessionPrepare(sqlite3 *db, sqlite3_stmt **pp, const char *zSql){
 static int sessionStat1Sql(sqlite3 *db, SessionApplyCtx *p){
   int rc = sessionSelectRow(db, "sqlite_stat1", p);
   if( rc==SQLITE_OK ){
-    rc = sessionPrepare(db, &p->pInsert,
+    rc = sessionPrepare(db, &p->pInsert, 0,
         "INSERT INTO main.sqlite_stat1 VALUES(?1, "
         "CASE WHEN length(?2)=0 AND typeof(?2)='blob' THEN NULL ELSE ?2 END, "
         "?3)"
     );
   }
   if( rc==SQLITE_OK ){
-    rc = sessionPrepare(db, &p->pDelete,
+    rc = sessionPrepare(db, &p->pDelete, 0,
         "DELETE FROM main.sqlite_stat1 WHERE tbl=?1 AND idx IS "
         "CASE WHEN length(?2)=0 AND typeof(?2)='blob' THEN NULL ELSE ?2 END "
         "AND (?4 OR stat IS ?3)"
@@ -5392,6 +5402,11 @@ static int sessionChangesetApply(
     db->flags &= ~((u64)SQLITE_FkNoAction);
     db->aDb[0].pSchema->schema_cookie -= 32;
   }
+
+  assert( rc!=SQLITE_OK || sApply.zErr==0 );
+  sqlite3_set_errmsg(db, rc, sApply.zErr);
+  sqlite3_free(sApply.zErr);
+
   sqlite3_mutex_leave(sqlite3_db_mutex(db));
   return rc;
 }
index 2b7a3014da85b9d2ec902cc3f7415706edc34007..e8baa6e4166d7ce160bfe9ffe54445bb1121e33b 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Fix\san\soff-by-one\serror\sin\ssqlite3_rsync.\n[forum:/info/46753431d4|Forum\spost\s46753431d4].
-D 2025-09-11T10:58:49.199
+C Add\sexperimental\sAPI\ssqlite3_set_errmsg().\sUse\sthis\sin\ssqlite3changeset_apply()\sto\sreturn\sany\serror\scode\sand\serror\smessage\svia\sthe\sSQLite\shandle.
+D 2025-09-12T15:02:47.133
 F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x
 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
@@ -540,7 +540,7 @@ F ext/session/session4.test 823f6f018fcbb8dacf61e2960f8b3b848d492b094f8b495eae1d
 F ext/session/session5.test 716bc6fafd625ce60dfa62ae128971628c1a1169
 F ext/session/session6.test 35279f2ec45448cd2e24a61688219dc6cf7871757716063acf4a8b5455e1e926
 F ext/session/session8.test 326f3273abf9d5d2d7d559eee8f5994c4ea74a5d935562454605e6607ee29904
-F ext/session/session9.test 4e3aff62d6b4294498ddbe309076de06f4fddffad4fe5f5a6c033358b01df083
+F ext/session/session9.test 0c4a8fbe7a5031f50855f020f3408e1f07fd7859f1daa1629eadcec3422072d6
 F ext/session/sessionA.test 1feeab0b8e03527f08f2f1defb442da25480138f
 F ext/session/sessionB.test c4fb7f8a688787111606e123a555f18ee04f65bb9f2a4bb2aa71d55ce4e6d02c
 F ext/session/sessionC.test f8a5508bc059ae646e5ec9bdbca66ad24bc92fe99fda5790ac57e1f59fce2fdf
@@ -573,7 +573,7 @@ F ext/session/sessionrowid.test 85187c2f1b38861a5844868126f69f9ec62223a03449a98a
 F ext/session/sessionsize.test 8fcf4685993c3dbaa46a24183940ab9f5aa9ed0d23e5fb63bfffbdb56134b795
 F ext/session/sessionstat1.test 5e718d5888c0c49bbb33a7a4f816366db85f59f6a4f97544a806421b85dc2dec
 F ext/session/sessionwor.test 6fd9a2256442cebde5b2284936ae9e0d54bde692d0f5fd009ecef8511f4cf3fc
-F ext/session/sqlite3session.c 9205e6e8f389ea44a8118082ce3832374da92284a60e4fb3ea6b1b421f0dbc54
+F ext/session/sqlite3session.c 9cd47bfefb23c114b7a5d9ee5822d941398902f30516bf0ddfb131d94f8bb840
 F ext/session/sqlite3session.h 6641184274b1d46594e450793d39aa96053916043234fb9e653c661160a166a7
 F ext/session/test_session.c 8766b5973a6323934cb51248f621c3dc87ad2a98f023c3cc280d79e7d78d36fb
 F ext/wasm/EXPORTED_FUNCTIONS.fiddle.in 27450c8b8c70875a260aca55435ec927068b34cef801a96205adb81bdcefc65c
@@ -705,7 +705,7 @@ F src/insert.c dfd311b0ac2d4f6359e62013db67799757f4d2cc56cca5c10f4888acfbbfa3fd
 F src/json.c cb87977b1ee25ee7d27505d65a9261b687395bf895342c8ba566b7c01aee2047
 F src/legacy.c d7874bc885906868cd51e6c2156698f2754f02d9eee1bae2d687323c3ca8e5aa
 F src/loadext.c d7edd8e671237539d795d30daaf888908a2c82e99bade4c78f3be021e8b7d655
-F src/main.c 458e79da86c957a094c1c256136af43e7dfec2c58475ee9cbbda4dc202005021
+F src/main.c a826d261631d1510184476fe4784f6f4ab64a5ac1b38bbc68c60148acd63feeb
 F src/malloc.c 410e570b30c26cc36e3372577df50f7a96ee3eed5b2b161c6b6b48773c650c5e
 F src/mem0.c 6a55ebe57c46ca1a7d98da93aaa07f99f1059645
 F src/mem1.c 3bb59158c38e05f6270e761a9f435bf19827a264c13d1631c58b84bdc96d73b2
@@ -743,7 +743,7 @@ F src/resolve.c f8d1d011aba0964ff1bdccd049d4d2c2fec217efd90d202a4bb775e926b2c25d
 F src/rowset.c 8432130e6c344b3401a8874c3cb49fefe6873fec593294de077afea2dce5ec97
 F src/select.c b95181711d59c36d9789e67f76c4cfec64b99f9629a50be5e6566e117b87d957
 F src/shell.c.in c309e6e95b4de2be9dd0fbe4d40f729199a85bcc54d66759a0aef3b3e6504b22
-F src/sqlite.h.in 79dd3963888543f3120536608bf51024c93c7eb163a255098ffd569710189781
+F src/sqlite.h.in 527d13b8cf5ed097cea7f806565eab5b6c783799da89a21223b422308b3b4479
 F src/sqlite3.rc 015537e6ac1eec6c7050e17b616c2ffe6f70fca241835a84a4f0d5937383c479
 F src/sqlite3ext.h 0bfd049bb2088cc44c2ad54f2079d1c6e43091a4e1ce8868779b75f6c1484f1e
 F src/sqliteInt.h 27c73e48878d31ef230ba867d1f8c3af6aed357fd93ccc605d3f1aae007ea62b
@@ -2174,8 +2174,11 @@ F tool/version-info.c 3b36468a90faf1bbd59c65fd0eb66522d9f941eedd364fabccd7227350
 F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee87c1b31a7
 F tool/warnings.sh 1ad0169b022b280bcaaf94a7fa231591be96b514230ab5c98fbf15cd7df842dd
 F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f
-P 5bc6b9352236df3091ee69b947d0c397264156539d13e5968ec3b9a6e55dc800
-R a5539b6621653da7959fb888474ce821
-U drh
-Z 3cde64f623da7bc733a5eed51b57ab95
+P ef3b7be6f2037871f6f1b1944fed3dda28216e7f179080d3be2e2620c031f48c
+R 262c29c5815b20dcd06e87f8949e0e9a
+T *branch * sqlite3-set-errmsg
+T *sym-sqlite3-set-errmsg *
+T -sym-trunk *
+U dan
+Z ca78b995013b1b3c1c01f7a5a981e1b3
 # Remove this line to create a well-formed Fossil manifest.
index bec971799ff1b8ee641c166c7aeb22d12c785393..94b3af9bd8cb41ea7ec0ba874978355910352137 100644 (file)
@@ -1,2 +1,2 @@
-branch trunk
-tag trunk
+branch sqlite3-set-errmsg
+tag sqlite3-set-errmsg
index ea679c53ec20ebab1980b576f8fff009c8d8875b..29425ccd3f68743896f53f46ed098b3d3004986d 100644 (file)
@@ -1 +1 @@
-ef3b7be6f2037871f6f1b1944fed3dda28216e7f179080d3be2e2620c031f48c
+4d5b60a1e57448f03af2a657fe7cdabb04ebaf9688d5cc700dd8f9892a5cba15
index 53b728f0236eb88e0c6ba0427de3a436b469cf4e..e079cbed3f5b6b30d137a2fd0c1fe85f1d89ed27 100644 (file)
@@ -2716,6 +2716,25 @@ const char *sqlite3_errmsg(sqlite3 *db){
   return z;
 }
 
+/*
+** Set the error code and error message associated with the database handle.
+*/
+int sqlite3_set_errmsg(sqlite3 *db, int errcode, const char *zMsg){
+  int rc = SQLITE_OK;
+  if( !sqlite3SafetyCheckSickOrOk(db) ){
+    return SQLITE_MISUSE_BKPT;
+  }
+  sqlite3_mutex_enter(db->mutex);
+  if( zMsg ){
+    sqlite3ErrorWithMsg(db, errcode, "%s", zMsg);
+  }else{
+    sqlite3Error(db, errcode);
+  }
+  rc = sqlite3ApiExit(db, rc);
+  sqlite3_mutex_leave(db->mutex);
+  return rc;
+}
+
 /*
 ** Return the byte offset of the most recent error
 */
index 3c17808fc3cdc00bc0761cf6bba159f0319f1efc..05c14f5fd26a974d730146840400b3c0b1b0dec4 100644 (file)
@@ -4206,6 +4206,21 @@ const void *sqlite3_errmsg16(sqlite3*);
 const char *sqlite3_errstr(int);
 int sqlite3_error_offset(sqlite3 *db);
 
+/*
+** CAPI3REF: Set Error Codes And Message
+** METHOD: sqlite3
+**
+** Set the error code of the database handle passed as the first argument
+** to errcode, and the error message to a copy of nul-terminated string
+** zErrMsg. If zErrMsg is passed NULL, then the error message is set to
+** the default message associated with the supplied error code.
+**
+** This function returns SQLITE_OK if the error code and error message are
+** successfully set, SQLITE_NOMEM if an OOM occurs, and SQLITE_MISUSE if
+** the database handle is NULL or invalid.
+*/
+int sqlite3_set_errmsg(sqlite3 *db, int errcode, const char *zErrMsg);
+
 /*
 ** CAPI3REF: Prepared Statement Object
 ** KEYWORDS: {prepared statement} {prepared statements}