Bug 329691 r=bryner Refactor init so that table creation happens before the

dummy database statement (which keeps the cache from expiring) starts. Fixes
errors during DB init and also performance problems on first run.
This commit is contained in:
brettw%gmail.com 2006-03-09 17:34:35 +00:00
parent b33591dc65
commit 333e978287
6 changed files with 240 additions and 105 deletions

View File

@ -86,16 +86,14 @@ nsAnnotationService::Init()
{
nsresult rv;
// The history service will normally already be created and will call our
// static InitTables function. It will get autocreated here if it hasn't
// already been created.
nsNavHistory* history = nsNavHistory::GetHistoryService();
if (! history)
return NS_ERROR_FAILURE;
mDBConn = history->GetStorageConnection();
// create the database
rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING("CREATE TABLE moz_anno (anno_id INTEGER PRIMARY KEY, page INTEGER NOT NULL, name VARCHAR(32) NOT NULL, mime_type VARCHAR(32) DEFAULT NULL, content LONGVARCHAR, flags INTEGER DEFAULT 0, expiration INTEGER DEFAULT 0)"));
rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING("CREATE INDEX moz_anno_pageindex ON moz_anno (page)"));
rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING("CREATE INDEX moz_anno_nameindex ON moz_anno (name)"));
// annotation statements
rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING("UPDATE moz_anno SET mime_type = ?4, content = ?5, flags = ?6, expiration = ?7 WHERE anno_id = ?1"),
getter_AddRefs(mDBSetAnnotation));
@ -120,6 +118,42 @@ nsAnnotationService::Init()
}
// nsAnnotationService::InitTables
//
// All commands that initialize the schema of the DB go in here. This is
// called from history init before the dummy DB connection is started that
// will prevent us from modifying the schema.
//
// The history service will always be created before us (we get it at the
// beginning of the init function which covers us if it's not).
nsresult // static
nsAnnotationService::InitTables(mozIStorageConnection* aDBConn)
{
nsresult rv;
PRBool exists;
rv = aDBConn->TableExists(NS_LITERAL_CSTRING("moz_anno"), &exists);
NS_ENSURE_SUCCESS(rv, rv);
if (! exists) {
rv = aDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING("CREATE TABLE moz_anno ("
"anno_id INTEGER PRIMARY KEY,"
"page INTEGER NOT NULL,"
"name VARCHAR(32) NOT NULL,"
"mime_type VARCHAR(32) DEFAULT NULL,"
"content LONGVARCHAR, flags INTEGER DEFAULT 0,"
"expiration INTEGER DEFAULT 0)"));
NS_ENSURE_SUCCESS(rv, rv);
rv = aDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"CREATE INDEX moz_anno_pageindex ON moz_anno (page)"));
NS_ENSURE_SUCCESS(rv, rv);
rv = aDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"CREATE INDEX moz_anno_nameindex ON moz_anno (name)"));
NS_ENSURE_SUCCESS(rv, rv);
}
return NS_OK;
}
// nsAnnotationService::SetAnnotationString
NS_IMETHODIMP

View File

@ -52,6 +52,8 @@ public:
nsresult Init();
static nsresult InitTables(mozIStorageConnection* aDBConn);
NS_DECL_ISUPPORTS
NS_DECL_NSIANNOTATIONSERVICE

View File

@ -93,65 +93,6 @@ nsNavBookmarks::Init()
mozIStorageConnection *dbConn = DBConn();
mozStorageTransaction transaction(dbConn, PR_FALSE);
PRBool exists = PR_FALSE;
dbConn->TableExists(NS_LITERAL_CSTRING("moz_bookmarks"), &exists);
if (!exists) {
rv = dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING("CREATE TABLE moz_bookmarks ("
"item_child INTEGER, "
"folder_child INTEGER, "
"parent INTEGER, "
"position INTEGER)"));
NS_ENSURE_SUCCESS(rv, rv);
// this index will make it faster to determine if a given item is
// bookmarked (used by history queries and vacuuming, for example)
rv = dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"CREATE INDEX moz_bookmarks_itemindex ON moz_bookmarks (item_child)"));
NS_ENSURE_SUCCESS(rv, rv);
// the most common operation is to find the children given a parent
rv = dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"CREATE INDEX moz_bookmarks_parentindex ON moz_bookmarks (parent)"));
NS_ENSURE_SUCCESS(rv, rv);
}
// moz_bookmarks_folders
dbConn->TableExists(NS_LITERAL_CSTRING("moz_bookmarks_folders"), &exists);
if (!exists) {
rv = dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING("CREATE TABLE moz_bookmarks_folders ("
"id INTEGER PRIMARY KEY, "
"name LONGVARCHAR, "
"type LONGVARCHAR)"));
NS_ENSURE_SUCCESS(rv, rv);
}
// moz_bookmarks_roots
dbConn->TableExists(NS_LITERAL_CSTRING("moz_bookmarks_roots"), &exists);
if (!exists) {
rv = dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING("CREATE TABLE moz_bookmarks_roots ("
"root_name VARCHAR(16) UNIQUE, "
"folder_id INTEGER)"));
NS_ENSURE_SUCCESS(rv, rv);
}
// moz_keywords
dbConn->TableExists(NS_LITERAL_CSTRING("moz_keywords"), &exists);
if (! exists) {
rv = dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"CREATE TABLE moz_keywords ("
"keyword VARCHAR(32) UNIQUE,"
"page_id INTEGER)"));
NS_ENSURE_SUCCESS(rv, rv);
// it should be fast to go url->ID and ID->url
rv = dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"CREATE INDEX moz_keywords_keywordindex ON moz_keywords (keyword)"));
NS_ENSURE_SUCCESS(rv, rv);
rv = dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"CREATE INDEX moz_keywords_pageindex ON moz_keywords (page_id)"));
NS_ENSURE_SUCCESS(rv, rv);
}
rv = dbConn->CreateStatement(NS_LITERAL_CSTRING("SELECT id, name, type FROM moz_bookmarks_folders WHERE id = ?1"),
getter_AddRefs(mDBGetFolderInfo));
NS_ENSURE_SUCCESS(rv, rv);
@ -301,6 +242,84 @@ nsNavBookmarks::Init()
return NS_OK;
}
// nsNavBookmarks::InitTables
//
// All commands that initialize the schema of the DB go in here. This is
// called from history init before the dummy DB connection is started that
// will prevent us from modifying the schema.
nsresult // static
nsNavBookmarks::InitTables(mozIStorageConnection* aDBConn)
{
nsresult rv;
PRBool exists;
rv = aDBConn->TableExists(NS_LITERAL_CSTRING("moz_bookmarks"), &exists);
NS_ENSURE_SUCCESS(rv, rv);
if (! exists) {
rv = aDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING("CREATE TABLE moz_bookmarks ("
"item_child INTEGER, "
"folder_child INTEGER, "
"parent INTEGER, "
"position INTEGER)"));
NS_ENSURE_SUCCESS(rv, rv);
// this index will make it faster to determine if a given item is
// bookmarked (used by history queries and vacuuming, for example)
rv = aDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"CREATE INDEX moz_bookmarks_itemindex ON moz_bookmarks (item_child)"));
NS_ENSURE_SUCCESS(rv, rv);
// the most common operation is to find the children given a parent
rv = aDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"CREATE INDEX moz_bookmarks_parentindex ON moz_bookmarks (parent)"));
NS_ENSURE_SUCCESS(rv, rv);
}
// moz_bookmarks_folders
rv = aDBConn->TableExists(NS_LITERAL_CSTRING("moz_bookmarks_folders"), &exists);
NS_ENSURE_SUCCESS(rv, rv);
if (! exists) {
rv = aDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING("CREATE TABLE moz_bookmarks_folders ("
"id INTEGER PRIMARY KEY, "
"name LONGVARCHAR, "
"type LONGVARCHAR)"));
NS_ENSURE_SUCCESS(rv, rv);
}
// moz_bookmarks_roots
rv = aDBConn->TableExists(NS_LITERAL_CSTRING("moz_bookmarks_roots"), &exists);
NS_ENSURE_SUCCESS(rv, rv);
if (!exists) {
rv = aDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING("CREATE TABLE moz_bookmarks_roots ("
"root_name VARCHAR(16) UNIQUE, "
"folder_id INTEGER)"));
NS_ENSURE_SUCCESS(rv, rv);
}
// moz_keywords
rv = aDBConn->TableExists(NS_LITERAL_CSTRING("moz_keywords"), &exists);
NS_ENSURE_SUCCESS(rv, rv);
if (! exists) {
rv = aDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"CREATE TABLE moz_keywords ("
"keyword VARCHAR(32) UNIQUE,"
"page_id INTEGER)"));
NS_ENSURE_SUCCESS(rv, rv);
// it should be fast to go url->ID and ID->url
rv = aDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"CREATE INDEX moz_keywords_keywordindex ON moz_keywords (keyword)"));
NS_ENSURE_SUCCESS(rv, rv);
rv = aDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"CREATE INDEX moz_keywords_pageindex ON moz_keywords (page_id)"));
NS_ENSURE_SUCCESS(rv, rv);
}
return NS_OK;
}
struct RenumberItem {
PRInt64 folderChild;
nsCOMPtr<nsIURI> itemURI;

View File

@ -56,6 +56,9 @@ public:
nsNavBookmarks();
nsresult Init();
// called by nsNavHistory::Init
static nsresult InitTables(mozIStorageConnection* aDBConn);
static nsNavBookmarks* GetBookmarksService() {
if (!sInstance) {
nsresult rv;

View File

@ -40,6 +40,7 @@
#include "nsNavHistory.h"
#include "nsNavBookmarks.h"
#include "nsMorkHistoryImporter.h"
#include "nsAnnotationService.h"
#include "nsArray.h"
#include "nsArrayEnumerator.h"
@ -371,16 +372,6 @@ nsNavHistory::Init()
// nsNavHistory::InitDB
//
// sqlite page caches are discarded when a statement is complete. This sucks
// for things like history queries where we do many small reads. This means
// that for every small transaction, we have to re-read from disk (or the OS
// cache) all pages associated with that transaction.
//
// To get around this, we keep a different connection. This dummy connection
// has a statement that stays open and thus keeps is pager cache in memory.
// When the shared pager cache is enabled before either connection has been
// opened (this is done by the storage service on DB init), our main
// connection will get the same pager cache, which will be persisted.
nsresult
nsNavHistory::InitDB(PRBool *aDoImport)
@ -402,41 +393,24 @@ nsNavHistory::InitDB(PRBool *aDoImport)
rv = mDBService->OpenDatabase(dbFile, getter_AddRefs(mDBConn));
NS_ENSURE_SUCCESS(rv, rv);
// create a dummy table that we can keep a transaction open on; the
// dummy statement needs something to work on that will always exist.
// This table must have something in it or the statement will be
// automatically closed because there is no data.
/* FIXME(brettw) This is commented out until a better table creation
* scheme can be created.
rv = mDBConn->TableExists(NS_LITERAL_CSTRING("moz_dummy_table"), &tableExists);
NS_ENSURE_SUCCESS(rv, rv);
if (! tableExists) {
rv = mDBConn->ExecuteSimpleSQL(
NS_LITERAL_CSTRING("CREATE TABLE moz_dummy_table (id INTEGER PRIMARY KEY)"));
NS_ENSURE_SUCCESS(rv, rv);
}
rv = mDBConn->ExecuteSimpleSQL(
NS_LITERAL_CSTRING("INSERT OR IGNORE INTO moz_dummy_table VALUES (1)"));
NS_ENSURE_SUCCESS(rv, rv);
*/
// dummy DB (see comment above function) and statement that stays open
// dummy database connection
rv = mDBService->OpenDatabase(dbFile, getter_AddRefs(mDummyDBConn));
NS_ENSURE_SUCCESS(rv, rv);
rv = mDummyDBConn->CreateStatement(NS_LITERAL_CSTRING(
"SELECT rowid FROM sqlite_master LIMIT 1"), getter_AddRefs(mDummyStatement));
//"SELECT id FROM moz_dummy_table LIMIT 1"), getter_AddRefs(mDummyStatement));
NS_ENSURE_SUCCESS(rv, rv);
PRBool dummyHasResults;
rv = mDummyStatement->ExecuteStep(&dummyHasResults);
NS_ENSURE_SUCCESS(rv, rv);
// ...now forget about that statement, keeping it open
// the favicon tables must be initialized, since we depend on those in
// some of our queries
// Initialize the other places services' database tables. We do this before:
//
// - Starting the dummy statement, because once the dummy statement has
// started we can't modify the schema. Stopping and re-starting the
// dummy statement is pretty heavyweight
//
// - Creating our statements. Some of our statements depend on these external
// tables, such as the bookmarks or favicon tables.
rv = nsNavBookmarks::InitTables(mDBConn);
NS_ENSURE_SUCCESS(rv, rv);
rv = nsFaviconService::InitTables(mDBConn);
NS_ENSURE_SUCCESS(rv, rv);
rv = nsAnnotationService::InitTables(mDBConn);
NS_ENSURE_SUCCESS(rv, rv);
// REMOVE ME FIXME TODO XXX
{
@ -483,6 +457,12 @@ nsNavHistory::InitDB(PRBool *aDoImport)
NS_ENSURE_SUCCESS(rv, rv);
}
// --- PUT SCHEMA-MODIFYING THINGS (like create table) ABOVE THIS LINE ---
// now that the schema has been finalized, we can initialize the dummy stmt.
rv = StartDummyStatement();
NS_ENSURE_SUCCESS(rv, rv);
// functions (must happen after table creation)
// mDBGetVisitPageInfo
@ -677,6 +657,99 @@ nsNavHistory::InitMemDB()
#endif
// nsNavHistory::StartDummyStatement
//
// sqlite page caches are discarded when a statement is complete. This sucks
// for things like history queries where we do many small reads. This means
// that for every small transaction, we have to re-read from disk (or the OS
// cache) all pages associated with that transaction.
//
// To get around this, we keep a different connection. This dummy connection
// has a statement that stays open and thus keeps its pager cache in memory.
// When the shared pager cache is enabled before either connection has been
// opened (this is done by the storage service on DB init), our main
// connection will get the same pager cache, which will be persisted.
//
// HOWEVER, when a statement is open on a database, it is disallowed to
// change the schema of the database (add or modify tables or indices).
// We deal with this in two ways. First, for initialization, all the
// services that depend on the places connection are told to create their
// tables using static functions. This happens before StartDummyStatement
// is called in InitDB so that this problem doesn't happen.
//
// If some service needs to change the schema for some reason after this,
// they can call StopDummyStatement which will terminate the statement and
// clear the cache. This will allow the database schema to be modified, but
// will have bad performance implications (because the cache will need to
// be re-loaded). It is also possible for some buggy function to leave a
// statement open that will prevent modifictation of the DB.
nsresult
nsNavHistory::StartDummyStatement()
{
nsresult rv;
NS_ASSERTION(mDummyDBConn, "The dummy connection should have been set up by Init");
// do nothing if the dummy statement is already running
if (mDBDummyStatement)
return NS_OK;
// Make sure the dummy table exists
PRBool tableExists;
rv = mDBConn->TableExists(NS_LITERAL_CSTRING("moz_dummy_table"), &tableExists);
NS_ENSURE_SUCCESS(rv, rv);
if (! tableExists) {
rv = mDBConn->ExecuteSimpleSQL(
NS_LITERAL_CSTRING("CREATE TABLE moz_dummy_table (id INTEGER PRIMARY KEY)"));
NS_ENSURE_SUCCESS(rv, rv);
}
// This table is guaranteed to have something in it and will keep the dummy
// statement open. If the table is empty, it won't hold the statement open.
// the PRIMARY KEY value on ID means that it is unique. The OR IGNORE means
// that if there is already a value of 1 there, this insert will be ignored,
// which is what we want so as to avoid growing the table infinitely.
rv = mDBConn->ExecuteSimpleSQL(
NS_LITERAL_CSTRING("INSERT OR IGNORE INTO moz_dummy_table VALUES (1)"));
NS_ENSURE_SUCCESS(rv, rv);
rv = mDummyDBConn->CreateStatement(NS_LITERAL_CSTRING(
"SELECT id FROM moz_dummy_table LIMIT 1"),
getter_AddRefs(mDBDummyStatement));
NS_ENSURE_SUCCESS(rv, rv);
// we have to step the dummy statement so that it will hold a lock on the DB
PRBool dummyHasResults;
rv = mDBDummyStatement->ExecuteStep(&dummyHasResults);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
// nsNavHistory::StopDummyStatement
//
// @see StartDummyStatement for how this works.
//
// It is very important that if the dummy statement is ever stopped, that
// it is restarted as soon as possible, or else the whole browser will run
// without DB cache, which will slow everything down.
nsresult
nsNavHistory::StopDummyStatement()
{
// do nothing if the dummy statement isn't running
if (! mDBDummyStatement)
return NS_OK;
nsresult rv = mDBDummyStatement->Reset();
NS_ENSURE_SUCCESS(rv, rv);
mDBDummyStatement = nsnull;
return NS_OK;
}
// nsNavHistory::SaveExpandItem
//
// This adds an item to the persistent list of items that should be

View File

@ -160,6 +160,10 @@ public:
}
#endif
// see comment above StartDummyStatement
nsresult StartDummyStatement();
nsresult StopDummyStatement();
/**
* These functions return non-owning references to the locale-specific
* objects for places components. Guaranteed to return non-NULL.
@ -347,7 +351,7 @@ protected:
// this statement is kept open to persist the cache, see InitDB
nsCOMPtr<mozIStorageConnection> mDummyDBConn;
nsCOMPtr<mozIStorageStatement> mDummyStatement;
nsCOMPtr<mozIStorageStatement> mDBDummyStatement;
nsresult AddVisitChain(nsIURI* aURI, PRBool aToplevel, PRBool aRedirect,
nsIURI* aReferrer, PRInt64* aVisitID,