From 421377e67ac7b5289c667a3ef4a23826908213b4 Mon Sep 17 00:00:00 2001
From: drh <drh@noemail.net>
Date: Thu, 15 Mar 2012 21:28:54 +0000
Subject: [PATCH] Add the sqlite3_db_readonly() interface.  This is still
 tentative, pending a closer look at other ideas to accomplish the same thing.

FossilOrigin-Name: 254f99ea9ff1534948bdb179e69ab0c940c87ec1
---
 manifest         | 23 ++++++++-------
 manifest.uuid    |  2 +-
 src/main.c       | 75 +++++++++++++++++++++++++++---------------------
 src/sqlite.h.in  |  9 ++++++
 src/sqliteInt.h  |  1 +
 src/test1.c      | 25 ++++++++++++++++
 test/pager1.test | 18 ++++++++++++
 7 files changed, 110 insertions(+), 43 deletions(-)

diff --git a/manifest b/manifest
index ff717fe1dd..13a39a43c3 100644
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Candidate\sfix\sfor\sthe\soptimizer\sproblem\sdescribed\sin\sticket\s\n[b7c8682cc17f3]\swhich\scan\scauses\sa\sLEFT\sJOIN\sto\sbe\schanged\ninto\sa\sINNER\sJOIN\sif\sthere\sare\sOR\sterms\sin\sthe\sWHERE\sclause.
-D 2012-03-09T22:02:08.875
+C Add\sthe\ssqlite3_db_readonly()\sinterface.\s\sThis\sis\sstill\stentative,\spending\na\scloser\slook\sat\sother\sideas\sto\saccomplish\sthe\ssame\sthing.
+D 2012-03-15T21:28:54.896
 F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f
 F Makefile.in 2f37e468503dbe79d35c9f6dffcf3fae1ae9ec20
 F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23
@@ -147,7 +147,7 @@ F src/journal.c 552839e54d1bf76fb8f7abe51868b66acacf6a0e
 F src/legacy.c a199d7683d60cef73089e892409113e69c23a99f
 F src/lempar.c 0ee69fca0be54cd93939df98d2aca4ca46f44416
 F src/loadext.c f20382fbaeec832438a1ba7797bee3d3c8a6d51d
-F src/main.c dcb4a15254dca9cc59dba63e813a8c140c021832
+F src/main.c 91458c713e9b7f8dbc98d79e78f1150f0ca9c2a1
 F src/malloc.c 15afac5e59b6584efe072e9933aefb4230e74f97
 F src/mem0.c 6a55ebe57c46ca1a7d98da93aaa07f99f1059645
 F src/mem1.c b3677415e69603d6a0e7c5410a1b3731d55beda1
@@ -182,14 +182,14 @@ F src/resolve.c 3d3e80a98f203ac6b9329e9621e29eda85ddfd40
 F src/rowset.c 69afa95a97c524ba6faf3805e717b5b7ae85a697
 F src/select.c 44ccdcb5d2a1c48622c179b2d72167b716388581
 F src/shell.c aa28f117033ba3e44b5eaaf2ad572222bcdfd66e
-F src/sqlite.h.in f46e368d1a28b09d876e35444785674d170f2d62
+F src/sqlite.h.in 307317b26050634adc0e22c1db2ff071b3262088
 F src/sqlite3ext.h 6904f4aadf976f95241311fbffb00823075d9477
-F src/sqliteInt.h b013dab7d43fb67c3ca2f0253d7863abb37e233c
+F src/sqliteInt.h e65429a6f19b41720561b9434b2192574a91cfa2
 F src/sqliteLimit.h 164b0e6749d31e0daa1a4589a169d31c0dec7b3d
 F src/status.c 4568e72dfd36b6a5911f93457364deb072e0b03a
 F src/table.c 2cd62736f845d82200acfa1287e33feb3c15d62e
 F src/tclsqlite.c 2aeb69958965dad0842d5ea1456f1a958ef296e6
-F src/test1.c 328cbe4a4ee6d10d67ece2a7adaa2770569fae0f
+F src/test1.c 07f30e8714bab94d8de8a88865d9c59bc512a1a8
 F src/test2.c 711555927f1f7e8db9aab86b512bc6934a774abe
 F src/test3.c 91d3f1a09cfae3533ef17d8b484a160f3d1f1a21
 F src/test4.c d1e5a5e904d4b444cf572391fdcb017638e36ff7
@@ -625,7 +625,7 @@ F test/notnull.test cc7c78340328e6112a13c3e311a9ab3127114347
 F test/null.test a8b09b8ed87852742343b33441a9240022108993
 F test/openv2.test 0d3040974bf402e19b7df4b783e447289d7ab394
 F test/oserror.test 50417780d0e0d7cd23cf12a8277bb44024765df3
-F test/pager1.test 101032cb9d8093806600b343fdcf78ba51c1e3e9
+F test/pager1.test 6e43e79d6ac1fc31b494ff3a0b56445b7eda5d2b
 F test/pager2.test 745b911dde3d1f24ae0870bd433dfa83d7c658c1
 F test/pager3.test 3856d9c80839be0668efee1b74811b1b7f7fc95f
 F test/pagerfault.test 452f2cc23e3bfcfa935f4442aec1da4fe1dc0442
@@ -992,7 +992,10 @@ F tool/tostr.awk e75472c2f98dd76e06b8c9c1367f4ab07e122d06
 F tool/vdbe-compress.tcl d70ea6d8a19e3571d7ab8c9b75cba86d1173ff0f
 F tool/warnings-clang.sh 9f406d66e750e8ac031c63a9ef3248aaa347ef2a
 F tool/warnings.sh fbc018d67fd7395f440c28f33ef0f94420226381
-P efee39e64bd95c284220fdb0ae8ee6c1847fadab
-R bea1320a1e93f035f866bb48777a9a0b
+P 0dc4cb935514131c99172175d57feec3a1743aa9
+R 851ef17fd9b25c32052af88e7352afdb
+T *branch * db-readonly-api
+T *sym-db-readonly-api *
+T -sym-trunk *
 U drh
-Z baafcf2bb8e1f4793d13146917501256
+Z 348596b004b2d886ba8debcb3d4e71fa
diff --git a/manifest.uuid b/manifest.uuid
index b0f211922a..b7a8d8211c 100644
--- a/manifest.uuid
+++ b/manifest.uuid
@@ -1 +1 @@
-0dc4cb935514131c99172175d57feec3a1743aa9
\ No newline at end of file
+254f99ea9ff1534948bdb179e69ab0c940c87ec1
\ No newline at end of file
diff --git a/src/main.c b/src/main.c
index 3a1dff576a..d148b4b42c 100644
--- a/src/main.c
+++ b/src/main.c
@@ -2704,35 +2704,27 @@ int sqlite3_extended_result_codes(sqlite3 *db, int onoff){
 */
 int sqlite3_file_control(sqlite3 *db, const char *zDbName, int op, void *pArg){
   int rc = SQLITE_ERROR;
-  int iDb;
+  Btree *pBtree;
+
   sqlite3_mutex_enter(db->mutex);
-  if( zDbName==0 ){
-    iDb = 0;
-  }else{
-    for(iDb=0; iDb<db->nDb; iDb++){
-      if( strcmp(db->aDb[iDb].zName, zDbName)==0 ) break;
-    }
-  }
-  if( iDb<db->nDb ){
-    Btree *pBtree = db->aDb[iDb].pBt;
-    if( pBtree ){
-      Pager *pPager;
-      sqlite3_file *fd;
-      sqlite3BtreeEnter(pBtree);
-      pPager = sqlite3BtreePager(pBtree);
-      assert( pPager!=0 );
-      fd = sqlite3PagerFile(pPager);
-      assert( fd!=0 );
-      if( op==SQLITE_FCNTL_FILE_POINTER ){
-        *(sqlite3_file**)pArg = fd;
-        rc = SQLITE_OK;
-      }else if( fd->pMethods ){
-        rc = sqlite3OsFileControl(fd, op, pArg);
-      }else{
-        rc = SQLITE_NOTFOUND;
-      }
-      sqlite3BtreeLeave(pBtree);
+  pBtree = sqlite3DbNameToBtree(db, zDbName);
+  if( pBtree ){
+    Pager *pPager;
+    sqlite3_file *fd;
+    sqlite3BtreeEnter(pBtree);
+    pPager = sqlite3BtreePager(pBtree);
+    assert( pPager!=0 );
+    fd = sqlite3PagerFile(pPager);
+    assert( fd!=0 );
+    if( op==SQLITE_FCNTL_FILE_POINTER ){
+      *(sqlite3_file**)pArg = fd;
+      rc = SQLITE_OK;
+    }else if( fd->pMethods ){
+      rc = sqlite3OsFileControl(fd, op, pArg);
+    }else{
+      rc = SQLITE_NOTFOUND;
     }
+    sqlite3BtreeLeave(pBtree);
   }
   sqlite3_mutex_leave(db->mutex);
   return rc;   
@@ -3028,15 +3020,34 @@ sqlite3_int64 sqlite3_uri_int64(
 }
 
 /*
-** Return the filename of the database associated with a database
-** connection.
+** Return the Btree pointer identified by zDbName.  Return NULL if not found.
 */
-const char *sqlite3_db_filename(sqlite3 *db, const char *zDbName){
+Btree *sqlite3DbNameToBtree(sqlite3 *db, const char *zDbName){
   int i;
   for(i=0; i<db->nDb; i++){
-    if( db->aDb[i].pBt && sqlite3StrICmp(zDbName, db->aDb[i].zName)==0 ){
-      return sqlite3BtreeGetFilename(db->aDb[i].pBt);
+    if( db->aDb[i].pBt
+     && (zDbName==0 || sqlite3StrICmp(zDbName, db->aDb[i].zName)==0)
+    ){
+      return db->aDb[i].pBt;
     }
   }
   return 0;
 }
+
+/*
+** Return the filename of the database associated with a database
+** connection.
+*/
+const char *sqlite3_db_filename(sqlite3 *db, const char *zDbName){
+  Btree *pBt = sqlite3DbNameToBtree(db, zDbName);
+  return pBt ? sqlite3BtreeGetFilename(pBt) : 0;
+}
+
+/*
+** Return 1 if database is read-only or 0 if read/write.  Return -1 if
+** no such database exists.
+*/
+int sqlite3_db_readonly(sqlite3 *db, const char *zDbName){
+  Btree *pBt = sqlite3DbNameToBtree(db, zDbName);
+  return pBt ? sqlite3PagerIsreadonly(sqlite3BtreePager(pBt)) : -1;
+}
diff --git a/src/sqlite.h.in b/src/sqlite.h.in
index b43fe208f8..3c82ebd600 100644
--- a/src/sqlite.h.in
+++ b/src/sqlite.h.in
@@ -4494,6 +4494,15 @@ sqlite3 *sqlite3_db_handle(sqlite3_stmt*);
 */
 const char *sqlite3_db_filename(sqlite3 *db, const char *zDbName);
 
+/*
+** CAPI3REF: Determine if a database is read-only
+**
+** ^The sqlite3_db_readonly(D,N) interface returns 1 if the database N
+** of connection D is read-only, 0 if it is read/write, and -1 if N is not
+** the name of a database in connection C.
+*/
+int sqlite3_db_readonly(sqlite3 *db, const char *zDbName);
+
 /*
 ** CAPI3REF: Find the next prepared statement
 **
diff --git a/src/sqliteInt.h b/src/sqliteInt.h
index cfe8fd64fb..15c3f302e2 100644
--- a/src/sqliteInt.h
+++ b/src/sqliteInt.h
@@ -2701,6 +2701,7 @@ void sqlite3AddCollateType(Parse*, Token*);
 void sqlite3EndTable(Parse*,Token*,Token*,Select*);
 int sqlite3ParseUri(const char*,const char*,unsigned int*,
                     sqlite3_vfs**,char**,char **);
+Btree *sqlite3DbNameToBtree(sqlite3*,const char*);
 int sqlite3CodeOnce(Parse *);
 
 Bitvec *sqlite3BitvecCreate(u32);
diff --git a/src/test1.c b/src/test1.c
index 281823d5a8..e1220cf11f 100644
--- a/src/test1.c
+++ b/src/test1.c
@@ -4665,6 +4665,30 @@ static int test_db_filename(
   return TCL_OK;
 }
 
+/*
+** Usage:  sqlite3_db_readonly DB DBNAME
+**
+** Return 1 or 0 if DBNAME is readonly or not.  Return -1 if DBNAME does
+** not exist.
+*/
+static int test_db_readonly(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  sqlite3 *db;
+  const char *zDbName;
+  if( objc!=3 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "DB DBNAME");
+    return TCL_ERROR;
+  }
+  if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR;
+  zDbName = Tcl_GetString(objv[2]);
+  Tcl_SetObjResult(interp, Tcl_NewIntObj(sqlite3_db_readonly(db, zDbName)));
+  return TCL_OK;
+}
+
 /*
 ** Usage:  sqlite3_soft_heap_limit ?N?
 **
@@ -6055,6 +6079,7 @@ int Sqlitetest1_Init(Tcl_Interp *interp){
      { "sqlite3_release_memory",        test_release_memory,     0},
      { "sqlite3_db_release_memory",     test_db_release_memory,  0},
      { "sqlite3_db_filename",           test_db_filename,        0},
+     { "sqlite3_db_readonly",           test_db_readonly,        0},
      { "sqlite3_soft_heap_limit",       test_soft_heap_limit,    0},
      { "sqlite3_thread_cleanup",        test_thread_cleanup,     0},
      { "sqlite3_pager_refcounts",       test_pager_refcounts,    0},
diff --git a/test/pager1.test b/test/pager1.test
index 3f3c12c541..96fcb5ba0e 100644
--- a/test/pager1.test
+++ b/test/pager1.test
@@ -887,6 +887,24 @@ do_test pager1.4.7.3 {
   delete_file test.db-journal
   file exists test.db-journal
 } {0}
+do_test pager1.4.8.1 {
+  catch {file attributes test.db -permissions r--------}
+  catch {file attributes test.db -readonly 1}
+  sqlite3 db test.db
+  db eval { SELECT * FROM t1 }
+  sqlite3_db_readonly db main
+} {1}
+do_test pager1.4.8.2 {
+  sqlite3_db_readonly db xyz
+} {-1}
+do_test pager1.4.8.3 {
+  db close
+  catch {file attributes test.db -permissions rw-rw-rw-}
+  catch {file attributes test.db -readonly 0}
+  sqlite3 db test.db
+  db eval { SELECT * FROM t1 }
+  sqlite3_db_readonly db main
+} {0}
 
 #-------------------------------------------------------------------------
 # The following tests deal with multi-file commits.