Bug 1209027 - Reduce queries load on visits addition. r=adw

MozReview-Commit-ID: AvW7WB2LXZE

--HG--
extra : rebase_source : bb5ab637dfe69f2b4587932bd1c506e18b81bca2
This commit is contained in:
Marco Bonardo 2016-05-31 15:19:16 +02:00
Родитель c8e28547cf
Коммит 22a82c8e00
11 изменённых файлов: 318 добавлений и 269 удалений

Просмотреть файл

@ -1037,6 +1037,8 @@ Database::InitFunctions()
NS_ENSURE_SUCCESS(rv, rv);
rv = FrecencyNotificationFunction::create(mMainConn);
NS_ENSURE_SUCCESS(rv, rv);
rv = StoreLastInsertedIdFunction::create(mMainConn);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}

Просмотреть файл

@ -73,10 +73,14 @@ struct VisitData {
: placeId(0)
, visitId(0)
, hidden(true)
, shouldUpdateHidden(true)
, typed(false)
, transitionType(UINT32_MAX)
, visitTime(0)
, frecency(-1)
, lastVisitId(0)
, lastVisitTime(0)
, referrerVisitId(0)
, titleChanged(false)
, shouldUpdateFrecency(true)
{
@ -89,10 +93,14 @@ struct VisitData {
: placeId(0)
, visitId(0)
, hidden(true)
, shouldUpdateHidden(true)
, typed(false)
, transitionType(UINT32_MAX)
, visitTime(0)
, frecency(-1)
, lastVisitId(0)
, lastVisitTime(0)
, referrerVisitId(0)
, titleChanged(false)
, shouldUpdateFrecency(true)
{
@ -121,36 +129,19 @@ struct VisitData {
transitionType = aTransitionType;
}
/**
* Determines if this refers to the same url as aOther, and updates aOther
* with missing information if so.
*
* @param aOther
* The other place to check against.
* @return true if this is a visit for the same place as aOther, false
* otherwise.
*/
bool IsSamePlaceAs(VisitData& aOther)
{
if (!spec.Equals(aOther.spec)) {
return false;
}
aOther.placeId = placeId;
aOther.guid = guid;
return true;
}
int64_t placeId;
nsCString guid;
int64_t visitId;
nsCString spec;
nsString revHost;
bool hidden;
bool shouldUpdateHidden;
bool typed;
uint32_t transitionType;
PRTime visitTime;
int32_t frecency;
int64_t lastVisitId;
PRTime lastVisitTime;
/**
* Stores the title. If this is empty (IsEmpty() returns true), then the
@ -161,6 +152,7 @@ struct VisitData {
nsString title;
nsCString referrerSpec;
int64_t referrerVisitId;
// TODO bug 626836 hook up hidden and typed change tracking too!
bool titleChanged;
@ -623,10 +615,8 @@ NS_IMPL_ISUPPORTS_INHERITED(
class NotifyVisitObservers : public Runnable
{
public:
NotifyVisitObservers(VisitData& aPlace,
VisitData& aReferrer)
NotifyVisitObservers(VisitData& aPlace)
: mPlace(aPlace)
, mReferrer(aReferrer)
, mHistory(History::GetService())
{
}
@ -657,7 +647,7 @@ public:
// to the database, thus cannot be queried and we don't notify them.
if (mPlace.transitionType != nsINavHistoryService::TRANSITION_EMBED) {
navHistory->NotifyOnVisit(uri, mPlace.visitId, mPlace.visitTime,
mReferrer.visitId, mPlace.transitionType,
mPlace.referrerVisitId, mPlace.transitionType,
mPlace.guid, mPlace.hidden);
}
@ -678,7 +668,6 @@ public:
}
private:
VisitData mPlace;
VisitData mReferrer;
RefPtr<History> mHistory;
};
@ -927,7 +916,6 @@ public:
VisitData* lastFetchedPlace = nullptr;
for (nsTArray<VisitData>::size_type i = 0; i < mPlaces.Length(); i++) {
VisitData& place = mPlaces.ElementAt(i);
VisitData& referrer = mReferrers.ElementAt(i);
// Fetching from the database can overwrite this information, so save it
// apart.
@ -936,7 +924,7 @@ public:
// We can avoid a database lookup if it's the same place as the last
// visit we added.
bool known = lastFetchedPlace && lastFetchedPlace->IsSamePlaceAs(place);
bool known = lastFetchedPlace && lastFetchedPlace->spec.Equals(place.spec);
if (!known) {
nsresult rv = mHistory->FetchPageInfo(place, &known);
if (NS_FAILED(rv)) {
@ -948,6 +936,14 @@ public:
return NS_OK;
}
lastFetchedPlace = &mPlaces.ElementAt(i);
} else {
// Copy over the data from the already known place.
place.placeId = lastFetchedPlace->placeId;
place.guid = lastFetchedPlace->guid;
place.lastVisitId = lastFetchedPlace->visitId;
place.lastVisitTime = lastFetchedPlace->visitTime;
place.titleChanged = !lastFetchedPlace->title.Equals(place.title);
place.frecency = lastFetchedPlace->frecency;
}
// If any transition is typed, ensure the page is marked as typed.
@ -960,9 +956,15 @@ public:
place.hidden = false;
}
FetchReferrerInfo(referrer, place);
// If this is a new page, or the existing page was already visible,
// there's no need to try to unhide it.
if (!known || !lastFetchedPlace->hidden) {
place.shouldUpdateHidden = false;
}
nsresult rv = DoDatabaseInserts(known, place, referrer);
FetchReferrerInfo(place);
nsresult rv = DoDatabaseInserts(known, place);
if (!!mCallback) {
nsCOMPtr<nsIRunnable> event =
new NotifyPlaceInfoCallback(mCallback, place, true, rv);
@ -971,7 +973,7 @@ public:
}
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIRunnable> event = new NotifyVisitObservers(place, referrer);
nsCOMPtr<nsIRunnable> event = new NotifyVisitObservers(place);
rv = NS_DispatchToMainThread(event);
NS_ENSURE_SUCCESS(rv, rv);
@ -999,18 +1001,15 @@ private:
MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
mPlaces.SwapElements(aPlaces);
mReferrers.SetLength(mPlaces.Length());
for (nsTArray<VisitData>::size_type i = 0; i < mPlaces.Length(); i++) {
mReferrers[i].spec = mPlaces[i].referrerSpec;
#ifdef DEBUG
for (nsTArray<VisitData>::size_type i = 0; i < mPlaces.Length(); i++) {
nsCOMPtr<nsIURI> uri;
MOZ_ASSERT(NS_SUCCEEDED(NS_NewURI(getter_AddRefs(uri), mPlaces[i].spec)));
NS_ASSERTION(CanAddURI(uri),
"Passed a VisitData with a URI we cannot add to history!");
#endif
MOZ_ASSERT(CanAddURI(uri),
"Passed a VisitData with a URI we cannot add to history!");
}
#endif
}
/**
@ -1022,12 +1021,9 @@ private:
* otherwise.
* @param aPlace
* The place we are adding a visit for.
* @param aReferrer
* The referrer for aPlace.
*/
nsresult DoDatabaseInserts(bool aKnown,
VisitData& aPlace,
VisitData& aReferrer)
VisitData& aPlace)
{
MOZ_ASSERT(!NS_IsMainThread(), "This should not be called on the main thread");
@ -1041,22 +1037,11 @@ private:
else {
rv = mHistory->InsertPlace(aPlace);
NS_ENSURE_SUCCESS(rv, rv);
// We need the place id and guid of the page we just inserted when we
// have a callback or when the GUID isn't known. No point in doing the
// disk I/O if we do not need it.
if (!!mCallback || aPlace.guid.IsEmpty()) {
bool exists;
rv = mHistory->FetchPageInfo(aPlace, &exists);
NS_ENSURE_SUCCESS(rv, rv);
if (!exists) {
NS_NOTREACHED("should have an entry in moz_places");
}
}
aPlace.placeId = nsNavHistory::sLastInsertedPlaceId;
}
MOZ_ASSERT(aPlace.placeId > 0);
rv = AddVisit(aPlace, aReferrer);
rv = AddVisit(aPlace);
NS_ENSURE_SUCCESS(rv, rv);
// TODO (bug 623969) we shouldn't update this after each visit, but
@ -1071,102 +1056,42 @@ private:
return NS_OK;
}
/**
* Loads visit information about the page into _place.
*
* @param _place
* The VisitData for the place we need to know visit information about.
* @param [optional] aThresholdStart
* The timestamp of a new visit (not represented by _place) used to
* determine if the page was recently visited or not.
* @return true if the page was recently (determined with aThresholdStart)
* visited, false otherwise.
*/
bool FetchVisitInfo(VisitData& _place,
PRTime aThresholdStart = 0)
{
NS_PRECONDITION(!_place.spec.IsEmpty(), "must have a non-empty spec!");
nsCOMPtr<mozIStorageStatement> stmt;
// If we have a visitTime, we want information on that specific visit.
if (_place.visitTime) {
stmt = mHistory->GetStatement(
"SELECT id, visit_date "
"FROM moz_historyvisits "
"WHERE place_id = (SELECT id FROM moz_places WHERE url = :page_url) "
"AND visit_date = :visit_date "
);
NS_ENSURE_TRUE(stmt, false);
mozStorageStatementScoper scoper(stmt);
nsresult rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("visit_date"),
_place.visitTime);
NS_ENSURE_SUCCESS(rv, false);
scoper.Abandon();
}
// Otherwise, we want information about the most recent visit.
else {
stmt = mHistory->GetStatement(
"SELECT id, visit_date "
"FROM moz_historyvisits "
"WHERE place_id = (SELECT id FROM moz_places WHERE url = :page_url) "
"ORDER BY visit_date DESC "
);
NS_ENSURE_TRUE(stmt, false);
}
mozStorageStatementScoper scoper(stmt);
nsresult rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"),
_place.spec);
NS_ENSURE_SUCCESS(rv, false);
bool hasResult;
rv = stmt->ExecuteStep(&hasResult);
NS_ENSURE_SUCCESS(rv, false);
if (!hasResult) {
return false;
}
rv = stmt->GetInt64(0, &_place.visitId);
NS_ENSURE_SUCCESS(rv, false);
rv = stmt->GetInt64(1, reinterpret_cast<int64_t*>(&_place.visitTime));
NS_ENSURE_SUCCESS(rv, false);
// If we have been given a visit threshold start time, go ahead and
// calculate if we have been recently visited.
if (aThresholdStart &&
aThresholdStart - _place.visitTime <= RECENT_EVENT_THRESHOLD) {
return true;
}
return false;
}
/**
* Fetches information about a referrer for aPlace if it was a recent
* visit or not.
*
* @param aReferrer
* The VisitData for the referrer. This will be populated with
* FetchVisitInfo.
* @param aPlace
* The VisitData for the visit we will eventually add.
*
*/
void FetchReferrerInfo(VisitData& aReferrer,
VisitData& aPlace)
void FetchReferrerInfo(VisitData& aPlace)
{
if (aReferrer.spec.IsEmpty()) {
if (aPlace.referrerSpec.IsEmpty()) {
return;
}
if (!FetchVisitInfo(aReferrer, aPlace.visitTime)) {
// We must change both the place and referrer to indicate that we will
// not be using the referrer's data. This behavior has test coverage, so
// if this invariant changes, we'll know.
VisitData referrer;
referrer.spec = aPlace.referrerSpec;
// If the referrer is the same as the page, we don't need to fetch it.
if (aPlace.referrerSpec.Equals(aPlace.spec)) {
referrer = aPlace;
// The page last visit id is also the referrer visit id.
aPlace.referrerVisitId = aPlace.lastVisitId;
} else {
bool exists = false;
if (NS_SUCCEEDED(mHistory->FetchPageInfo(referrer, &exists)) && exists) {
// Copy the referrer last visit id.
aPlace.referrerVisitId = referrer.lastVisitId;
}
}
// Check if the page has effectively been visited recently, otherwise
// discard the referrer info.
if (!aPlace.referrerVisitId || !referrer.lastVisitTime ||
aPlace.visitTime - referrer.lastVisitTime > RECENT_EVENT_THRESHOLD) {
// We will not be using the referrer data.
aPlace.referrerSpec.Truncate();
aReferrer.visitId = 0;
aPlace.referrerVisitId = 0;
}
}
@ -1175,36 +1100,25 @@ private:
*
* @param _place
* The VisitData for the place we need to know visit information about.
* @param aReferrer
* A reference to the referrer's visit data.
*/
nsresult AddVisit(VisitData& _place,
const VisitData& aReferrer)
nsresult AddVisit(VisitData& _place)
{
MOZ_ASSERT(_place.placeId > 0);
nsresult rv;
nsCOMPtr<mozIStorageStatement> stmt;
if (_place.placeId) {
stmt = mHistory->GetStatement(
"INSERT INTO moz_historyvisits "
"(from_visit, place_id, visit_date, visit_type, session) "
"VALUES (:from_visit, :page_id, :visit_date, :visit_type, 0) "
);
NS_ENSURE_STATE(stmt);
rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), _place.placeId);
NS_ENSURE_SUCCESS(rv, rv);
}
else {
stmt = mHistory->GetStatement(
"INSERT INTO moz_historyvisits "
"(from_visit, place_id, visit_date, visit_type, session) "
"VALUES (:from_visit, (SELECT id FROM moz_places WHERE url = :page_url), :visit_date, :visit_type, 0) "
);
NS_ENSURE_STATE(stmt);
rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), _place.spec);
NS_ENSURE_SUCCESS(rv, rv);
}
stmt = mHistory->GetStatement(
"INSERT INTO moz_historyvisits "
"(from_visit, place_id, visit_date, visit_type, session) "
"VALUES (:from_visit, :page_id, :visit_date, :visit_type, 0) "
);
NS_ENSURE_STATE(stmt);
mozStorageStatementScoper scoper(stmt);
rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), _place.placeId);
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("from_visit"),
aReferrer.visitId);
_place.referrerVisitId);
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("visit_date"),
_place.visitTime);
@ -1217,13 +1131,11 @@ private:
transitionType);
NS_ENSURE_SUCCESS(rv, rv);
mozStorageStatementScoper scoper(stmt);
rv = stmt->Execute();
NS_ENSURE_SUCCESS(rv, rv);
// Now that it should be in the database, we need to obtain the id of the
// visit we just added.
(void)FetchVisitInfo(_place);
_place.visitId = nsNavHistory::sLastInsertedVisitId;
MOZ_ASSERT(_place.visitId > 0);
return NS_OK;
}
@ -1237,66 +1149,43 @@ private:
nsresult UpdateFrecency(const VisitData& aPlace)
{
MOZ_ASSERT(aPlace.shouldUpdateFrecency);
MOZ_ASSERT(aPlace.placeId > 0);
nsresult rv;
{ // First, set our frecency to the proper value.
nsCOMPtr<mozIStorageStatement> stmt;
if (aPlace.placeId) {
stmt = mHistory->GetStatement(
"UPDATE moz_places "
"SET frecency = NOTIFY_FRECENCY("
"CALCULATE_FRECENCY(:page_id), "
"url, guid, hidden, last_visit_date"
") "
"WHERE id = :page_id"
);
NS_ENSURE_STATE(stmt);
rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), aPlace.placeId);
NS_ENSURE_SUCCESS(rv, rv);
}
else {
stmt = mHistory->GetStatement(
"UPDATE moz_places "
"SET frecency = NOTIFY_FRECENCY("
"CALCULATE_FRECENCY(id), url, guid, hidden, last_visit_date"
") "
"WHERE url = :page_url"
);
NS_ENSURE_STATE(stmt);
rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), aPlace.spec);
NS_ENSURE_SUCCESS(rv, rv);
}
stmt = mHistory->GetStatement(
"UPDATE moz_places "
"SET frecency = NOTIFY_FRECENCY("
"CALCULATE_FRECENCY(:page_id), "
"url, guid, hidden, last_visit_date"
") "
"WHERE id = :page_id"
);
NS_ENSURE_STATE(stmt);
mozStorageStatementScoper scoper(stmt);
rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), aPlace.placeId);
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->Execute();
NS_ENSURE_SUCCESS(rv, rv);
}
if (!aPlace.hidden) {
if (!aPlace.hidden && aPlace.shouldUpdateHidden) {
// Mark the page as not hidden if the frecency is now nonzero.
nsCOMPtr<mozIStorageStatement> stmt;
if (aPlace.placeId) {
stmt = mHistory->GetStatement(
"UPDATE moz_places "
"SET hidden = 0 "
"WHERE id = :page_id AND frecency <> 0"
);
NS_ENSURE_STATE(stmt);
rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), aPlace.placeId);
NS_ENSURE_SUCCESS(rv, rv);
}
else {
stmt = mHistory->GetStatement(
"UPDATE moz_places "
"SET hidden = 0 "
"WHERE url = :page_url AND frecency <> 0"
);
NS_ENSURE_STATE(stmt);
rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), aPlace.spec);
NS_ENSURE_SUCCESS(rv, rv);
}
stmt = mHistory->GetStatement(
"UPDATE moz_places "
"SET hidden = 0 "
"WHERE id = :page_id AND frecency <> 0"
);
NS_ENSURE_STATE(stmt);
mozStorageStatementScoper scoper(stmt);
rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), aPlace.placeId);
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->Execute();
NS_ENSURE_SUCCESS(rv, rv);
}
@ -1307,7 +1196,6 @@ private:
mozIStorageConnection* mDBConn;
nsTArray<VisitData> mPlaces;
nsTArray<VisitData> mReferrers;
nsMainThreadPtrHandle<mozIVisitInfoCallback> mCallback;
@ -1428,8 +1316,8 @@ public:
return NS_OK;
}
NS_ASSERTION(mPlace.placeId > 0,
"We somehow have an invalid place id here!");
MOZ_ASSERT(mPlace.placeId > 0,
"We somehow have an invalid place id here!");
// Now we can update our database record.
nsCOMPtr<mozIStorageStatement> stmt =
@ -1954,8 +1842,7 @@ StoreAndNotifyEmbedVisit(VisitData& aPlace,
(void)NS_DispatchToMainThread(event);
}
VisitData noReferrer;
nsCOMPtr<nsIRunnable> event = new NotifyVisitObservers(aPlace, noReferrer);
nsCOMPtr<nsIRunnable> event = new NotifyVisitObservers(aPlace);
(void)NS_DispatchToMainThread(event);
}
@ -2135,10 +2022,11 @@ History::GetIsVisitedStatement(mozIStorageCompletionCallback* aCallback)
}
nsresult
History::InsertPlace(const VisitData& aPlace)
History::InsertPlace(VisitData& aPlace)
{
NS_PRECONDITION(aPlace.placeId == 0, "should not have a valid place id!");
NS_PRECONDITION(!NS_IsMainThread(), "must be called off of the main thread!");
MOZ_ASSERT(aPlace.placeId == 0, "should not have a valid place id!");
MOZ_ASSERT(!aPlace.shouldUpdateHidden, "We should not need to update hidden");
MOZ_ASSERT(!NS_IsMainThread(), "must be called off of the main thread!");
nsCOMPtr<mozIStorageStatement> stmt = GetStatement(
"INSERT INTO moz_places "
@ -2172,12 +2060,11 @@ History::InsertPlace(const VisitData& aPlace)
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("hidden"), aPlace.hidden);
NS_ENSURE_SUCCESS(rv, rv);
nsAutoCString guid(aPlace.guid);
if (aPlace.guid.IsVoid()) {
rv = GenerateGUID(guid);
rv = GenerateGUID(aPlace.guid);
NS_ENSURE_SUCCESS(rv, rv);
}
rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("guid"), guid);
rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("guid"), aPlace.guid);
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->Execute();
NS_ENSURE_SUCCESS(rv, rv);
@ -2185,7 +2072,8 @@ History::InsertPlace(const VisitData& aPlace)
// Post an onFrecencyChanged observer notification.
const nsNavHistory* navHistory = nsNavHistory::GetConstHistoryService();
NS_ENSURE_STATE(navHistory);
navHistory->DispatchFrecencyChangedNotification(aPlace.spec, frecency, guid,
navHistory->DispatchFrecencyChangedNotification(aPlace.spec, frecency,
aPlace.guid,
aPlace.hidden,
aPlace.visitTime);
@ -2195,9 +2083,9 @@ History::InsertPlace(const VisitData& aPlace)
nsresult
History::UpdatePlace(const VisitData& aPlace)
{
NS_PRECONDITION(!NS_IsMainThread(), "must be called off of the main thread!");
NS_PRECONDITION(aPlace.placeId > 0, "must have a valid place id!");
NS_PRECONDITION(!aPlace.guid.IsVoid(), "must have a guid!");
MOZ_ASSERT(!NS_IsMainThread(), "must be called off of the main thread!");
MOZ_ASSERT(aPlace.placeId > 0, "must have a valid place id!");
MOZ_ASSERT(!aPlace.guid.IsVoid(), "must have a guid!");
nsCOMPtr<mozIStorageStatement> stmt = GetStatement(
"UPDATE moz_places "
@ -2238,8 +2126,8 @@ History::UpdatePlace(const VisitData& aPlace)
nsresult
History::FetchPageInfo(VisitData& _place, bool* _exists)
{
NS_PRECONDITION(!_place.spec.IsEmpty() || !_place.guid.IsEmpty(), "must have either a non-empty spec or guid!");
NS_PRECONDITION(!NS_IsMainThread(), "must be called off of the main thread!");
MOZ_ASSERT(!_place.spec.IsEmpty() || !_place.guid.IsEmpty(), "must have either a non-empty spec or guid!");
MOZ_ASSERT(!NS_IsMainThread(), "must be called off of the main thread!");
nsresult rv;
@ -2248,8 +2136,10 @@ History::FetchPageInfo(VisitData& _place, bool* _exists)
bool selectByURI = !_place.spec.IsEmpty();
if (selectByURI) {
stmt = GetStatement(
"SELECT guid, id, title, hidden, typed, frecency "
"FROM moz_places "
"SELECT guid, id, title, hidden, typed, frecency, last_visit_date, "
"(SELECT id FROM moz_historyvisits "
"WHERE place_id = h.id AND visit_date = h.last_visit_date) AS last_visit_id "
"FROM moz_places h "
"WHERE url = :page_url "
);
NS_ENSURE_STATE(stmt);
@ -2259,8 +2149,10 @@ History::FetchPageInfo(VisitData& _place, bool* _exists)
}
else {
stmt = GetStatement(
"SELECT url, id, title, hidden, typed, frecency "
"FROM moz_places "
"SELECT url, id, title, hidden, typed, frecency, last_visit_date, "
"(SELECT id FROM moz_historyvisits "
"WHERE place_id = h.id AND visit_date = h.last_visit_date) AS last_visit_id "
"FROM moz_places h "
"WHERE guid = :guid "
);
NS_ENSURE_STATE(stmt);
@ -2323,6 +2215,11 @@ History::FetchPageInfo(VisitData& _place, bool* _exists)
rv = stmt->GetInt32(5, &_place.frecency);
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->GetInt64(6, &_place.lastVisitTime);
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->GetInt64(7, &_place.lastVisitId);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}

Просмотреть файл

@ -69,7 +69,7 @@ public:
* @param aVisitData
* The visit data to use to populate a new row in moz_places.
*/
nsresult InsertPlace(const VisitData& aVisitData);
nsresult InsertPlace(VisitData& aVisitData);
/**
* Updates an entry in moz_places with the data in aVisitData.

Просмотреть файл

@ -844,5 +844,55 @@ namespace places {
return NS_OK;
}
////////////////////////////////////////////////////////////////////////////////
//// Store Last Inserted Id Function
StoreLastInsertedIdFunction::~StoreLastInsertedIdFunction()
{
}
/* static */
nsresult
StoreLastInsertedIdFunction::create(mozIStorageConnection *aDBConn)
{
RefPtr<StoreLastInsertedIdFunction> function =
new StoreLastInsertedIdFunction();
nsresult rv = aDBConn->CreateFunction(
NS_LITERAL_CSTRING("store_last_inserted_id"), 2, function
);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
NS_IMPL_ISUPPORTS(
StoreLastInsertedIdFunction,
mozIStorageFunction
)
NS_IMETHODIMP
StoreLastInsertedIdFunction::OnFunctionCall(mozIStorageValueArray *aArgs,
nsIVariant **_result)
{
uint32_t numArgs;
nsresult rv = aArgs->GetNumEntries(&numArgs);
NS_ENSURE_SUCCESS(rv, rv);
MOZ_ASSERT(numArgs == 2);
nsAutoCString table;
rv = aArgs->GetUTF8String(0, table);
NS_ENSURE_SUCCESS(rv, rv);
int64_t lastInsertedId = aArgs->AsInt64(1);
nsNavHistory::StoreLastInsertedId(table, lastInsertedId);
RefPtr<nsVariant> result = new nsVariant();
rv = result->SetAsInt64(lastInsertedId);
NS_ENSURE_SUCCESS(rv, rv);
result.forget(_result);
return NS_OK;
}
} // namespace places
} // namespace mozilla

Просмотреть файл

@ -324,6 +324,34 @@ public:
};
////////////////////////////////////////////////////////////////////////////////
//// Store Last Inserted Id Function
/**
* Store the last inserted id for reference purpose.
*
* @param tableName
* The table name.
* @param id
* The last inserted id.
* @return null
*/
class StoreLastInsertedIdFunction final : public mozIStorageFunction
{
~StoreLastInsertedIdFunction();
public:
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_MOZISTORAGEFUNCTION
/**
* Registers the function with the specified database connection.
*
* @param aDBConn
* The database connection to register with.
*/
static nsresult create(mozIStorageConnection *aDBConn);
};
} // namespace places
} // namespace mozilla

Просмотреть файл

@ -405,8 +405,8 @@ nsNavHistory::GetOrCreateIdForPage(nsIURI* aURI,
// Create a new hidden, untyped and unvisited entry.
nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
"INSERT OR IGNORE INTO moz_places (url, rev_host, hidden, frecency, guid) "
"VALUES (:page_url, :rev_host, :hidden, :frecency, GENERATE_GUID()) "
"INSERT INTO moz_places (url, rev_host, hidden, frecency, guid) "
"VALUES (:page_url, :rev_host, :hidden, :frecency, :guid) "
);
NS_ENSURE_STATE(stmt);
mozStorageStatementScoper scoper(stmt);
@ -431,28 +431,16 @@ nsNavHistory::GetOrCreateIdForPage(nsIURI* aURI,
rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("frecency"),
IsQueryURI(spec) ? 0 : -1);
NS_ENSURE_SUCCESS(rv, rv);
nsAutoCString guid;
rv = GenerateGUID(_GUID);
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("guid"), _GUID);
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->Execute();
NS_ENSURE_SUCCESS(rv, rv);
{
nsCOMPtr<mozIStorageStatement> getIdStmt = mDB->GetStatement(
"SELECT id, guid FROM moz_places WHERE url = :page_url "
);
NS_ENSURE_STATE(getIdStmt);
mozStorageStatementScoper getIdScoper(getIdStmt);
rv = URIBinder::Bind(getIdStmt, NS_LITERAL_CSTRING("page_url"), aURI);
NS_ENSURE_SUCCESS(rv, rv);
bool hasResult = false;
rv = getIdStmt->ExecuteStep(&hasResult);
NS_ENSURE_SUCCESS(rv, rv);
NS_ASSERTION(hasResult, "hasResult is false but the call succeeded?");
*_pageId = getIdStmt->AsInt64(0);
rv = getIdStmt->GetUTF8String(1, _GUID);
NS_ENSURE_SUCCESS(rv, rv);
}
*_pageId = sLastInsertedPlaceId;
return NS_OK;
}
@ -613,6 +601,21 @@ nsNavHistory::DispatchFrecencyChangedNotification(const nsACString& aSpec,
(void)NS_DispatchToMainThread(notif);
}
Atomic<int64_t> nsNavHistory::sLastInsertedPlaceId(0);
Atomic<int64_t> nsNavHistory::sLastInsertedVisitId(0);
void // static
nsNavHistory::StoreLastInsertedId(const nsACString& aTable,
const int64_t aLastInsertedId) {
if (aTable.Equals(NS_LITERAL_CSTRING("moz_places"))) {
nsNavHistory::sLastInsertedPlaceId = aLastInsertedId;
} else if (aTable.Equals(NS_LITERAL_CSTRING("moz_historyvisits"))) {
nsNavHistory::sLastInsertedVisitId = aLastInsertedId;
} else {
MOZ_ASSERT(false, "Trying to store the insert id for an unknown table?");
}
}
int32_t
nsNavHistory::GetDaysOfHistory() {
MOZ_ASSERT(NS_IsMainThread(), "This can only be called on the main thread");

Просмотреть файл

@ -27,6 +27,7 @@
#include "nsNavHistoryQuery.h"
#include "Database.h"
#include "mozilla/Attributes.h"
#include "mozilla/Atomics.h"
#define QUERYUPDATE_TIME 0
#define QUERYUPDATE_SIMPLE 1
@ -467,6 +468,15 @@ public:
bool aHidden,
PRTime aLastVisitDate) const;
/**
* Store last insterted id for a table.
*/
static mozilla::Atomic<int64_t> sLastInsertedPlaceId;
static mozilla::Atomic<int64_t> sLastInsertedVisitId;
static void StoreLastInsertedId(const nsACString& aTable,
const int64_t aLastInsertedId);
bool isBatching() {
return mBatchLevel > 0;
}

Просмотреть файл

@ -27,6 +27,7 @@
"CREATE TEMP TRIGGER moz_historyvisits_afterinsert_v2_trigger " \
"AFTER INSERT ON moz_historyvisits FOR EACH ROW " \
"BEGIN " \
"SELECT store_last_inserted_id('moz_historyvisits', NEW.id); " \
"UPDATE moz_places SET " \
"visit_count = visit_count + (SELECT NEW.visit_type NOT IN (" EXCLUDED_VISIT_TYPES ")), "\
"last_visit_date = MAX(IFNULL(last_visit_date, 0), NEW.visit_date) " \
@ -94,20 +95,20 @@
#define CREATE_PLACES_AFTERINSERT_TRIGGER NS_LITERAL_CSTRING( \
"CREATE TEMP TRIGGER moz_places_afterinsert_trigger " \
"AFTER INSERT ON moz_places FOR EACH ROW " \
"WHEN LENGTH(NEW.rev_host) > 1 " \
"BEGIN " \
"SELECT store_last_inserted_id('moz_places', NEW.id); " \
"INSERT OR REPLACE INTO moz_hosts (id, host, frecency, typed, prefix) " \
"VALUES (" \
"(SELECT id FROM moz_hosts WHERE host = fixup_url(get_unreversed_host(NEW.rev_host))), " \
"fixup_url(get_unreversed_host(NEW.rev_host)), " \
"MAX(IFNULL((SELECT frecency FROM moz_hosts WHERE host = fixup_url(get_unreversed_host(NEW.rev_host))), -1), NEW.frecency), " \
"MAX(IFNULL((SELECT typed FROM moz_hosts WHERE host = fixup_url(get_unreversed_host(NEW.rev_host))), 0), NEW.typed), " \
"(" HOSTS_PREFIX_PRIORITY_FRAGMENT \
"FROM ( " \
"SELECT fixup_url(get_unreversed_host(NEW.rev_host)) AS host " \
") AS match " \
") " \
"); " \
"SELECT " \
"(SELECT id FROM moz_hosts WHERE host = fixup_url(get_unreversed_host(NEW.rev_host))), " \
"fixup_url(get_unreversed_host(NEW.rev_host)), " \
"MAX(IFNULL((SELECT frecency FROM moz_hosts WHERE host = fixup_url(get_unreversed_host(NEW.rev_host))), -1), NEW.frecency), " \
"MAX(IFNULL((SELECT typed FROM moz_hosts WHERE host = fixup_url(get_unreversed_host(NEW.rev_host))), 0), NEW.typed), " \
"(" HOSTS_PREFIX_PRIORITY_FRAGMENT \
"FROM ( " \
"SELECT fixup_url(get_unreversed_host(NEW.rev_host)) AS host " \
") AS match " \
") " \
" WHERE LENGTH(NEW.rev_host) > 1; " \
"END" \
)

Просмотреть файл

@ -57,11 +57,16 @@ this.PlacesTestUtils = Object.freeze({
if (typeof place.uri == "string") {
place.uri = NetUtil.newURI(place.uri);
} else if (place.uri instanceof URL) {
place.uri = NetUtil.newURI(place.href);
place.uri = NetUtil.newURI(place.uri.href);
}
if (typeof place.title != "string") {
place.title = "test visit for " + place.uri.spec;
}
if (typeof place.referrer == "string") {
place.referrer = NetUtil.newURI(place.referrer);
} else if (place.referrer instanceof URL) {
place.referrer = NetUtil.newURI(place.referrer.href);
}
place.visits = [{
transitionType: place.transition === undefined ? Ci.nsINavHistoryService.TRANSITION_LINK
: place.transition,

Просмотреть файл

@ -0,0 +1,52 @@
// Test that repeated additions of the same URI through updatePlaces, properly
// update from_visit and notify titleChanged.
add_task(function* test() {
let uri = "http://test.com/";
let promiseTitleChangedNotifications = new Promise(resolve => {
let historyObserver = {
_count: 0,
__proto__: NavHistoryObserver.prototype,
onTitleChanged(aURI, aTitle, aGUID) {
Assert.equal(aURI.spec, uri, "Should notify the proper url");
if (++this._count == 2) {
PlacesUtils.history.removeObserver(historyObserver);
resolve();
}
}
};
PlacesUtils.history.addObserver(historyObserver, false);
});
// This repeats the url on purpose, don't merge it into a single place entry.
yield PlacesTestUtils.addVisits([
{ uri, title: "test" },
{ uri, referrer: uri, title: "test2" },
]);
let options = PlacesUtils.history.getNewQueryOptions();
let query = PlacesUtils.history.getNewQuery();
query.uri = NetUtil.newURI(uri);
options.resultType = options.RESULTS_AS_VISIT;
let root = PlacesUtils.history.executeQuery(query, options).root;
root.containerOpen = true;
Assert.equal(root.childCount, 2);
let child = root.getChild(0);
Assert.equal(child.visitType, TRANSITION_LINK, "Visit type should be TRANSITION_LINK");
Assert.equal(child.visitId, 1, "Visit ID should be 1");
Assert.equal(child.fromVisitId, -1, "Should have no referrer visit ID");
Assert.equal(child.title, "test2", "Should have the correct title");
child = root.getChild(1);
Assert.equal(child.visitType, TRANSITION_LINK, "Visit type should be TRANSITION_LINK");
Assert.equal(child.visitId, 2, "Visit ID should be 2");
Assert.equal(child.fromVisitId, 1, "First visit should be the referring visit");
Assert.equal(child.title, "test2", "Should have the correct title");
root.containerOpen = false;
yield promiseTitleChangedNotifications;
});

Просмотреть файл

@ -6,3 +6,4 @@ tail =
[test_remove.js]
[test_removeVisits.js]
[test_removeVisitsByFilter.js]
[test_updatePlaces_sameUri_titleChanged.js]