mirror of
https://github.com/FirebirdSQL/firebird.git
synced 2025-01-27 18:03:04 +01:00
f2d0b64ba2
class.
532 lines
12 KiB
C++
532 lines
12 KiB
C++
/*
|
|
* The contents of this file are subject to the Initial
|
|
* Developer's Public License Version 1.0 (the "License");
|
|
* you may not use this file except in compliance with the
|
|
* License. You may obtain a copy of the License at
|
|
* http://www.ibphoenix.com/main.nfs?a=ibphoenix&page=ibp_idpl.
|
|
*
|
|
* Software distributed under the License is distributed AS IS,
|
|
* WITHOUT WARRANTY OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing rights
|
|
* and limitations under the License.
|
|
*
|
|
* The Original Code was created by Vlad Khorsun
|
|
* for the Firebird Open Source RDBMS project.
|
|
*
|
|
* Copyright (c) 2005 Vlad Khorsun <hvlad@users.sourceforge.net>
|
|
* and all contributors signed below.
|
|
*
|
|
* All Rights Reserved.
|
|
* Contributor(s): ______________________________________.
|
|
*/
|
|
|
|
#include "firebird.h"
|
|
#include "../jrd/Relation.h"
|
|
|
|
#include "../jrd/tra.h"
|
|
#include "../jrd/btr_proto.h"
|
|
#include "../jrd/dpm_proto.h"
|
|
#include "../jrd/idx_proto.h"
|
|
#include "../jrd/lck_proto.h"
|
|
#include "../jrd/met_proto.h"
|
|
#include "../jrd/pag_proto.h"
|
|
#include "../jrd/vio_debug.h"
|
|
|
|
using namespace Jrd;
|
|
|
|
/// jrd_rel
|
|
|
|
RelationPages* jrd_rel::getPagesInternal(thread_db* tdbb, TraNumber tran, bool allocPages)
|
|
{
|
|
if (tdbb->tdbb_flags & TDBB_use_db_page_space)
|
|
return &rel_pages_base;
|
|
|
|
Jrd::Attachment* attachment = tdbb->getAttachment();
|
|
Database* dbb = tdbb->getDatabase();
|
|
|
|
SINT64 inst_id;
|
|
// Vlad asked for this compile-time check to make sure we can contain a txn number here
|
|
typedef int RangeCheck1[sizeof(inst_id) >= sizeof(TraNumber)];
|
|
typedef int RangeCheck2[sizeof(inst_id) >= sizeof(AttNumber)];
|
|
|
|
if (rel_flags & REL_temp_tran)
|
|
{
|
|
if (tran > 0 && tran != MAX_TRA_NUMBER) //if (tran > 0)
|
|
inst_id = tran;
|
|
else if (tdbb->tdbb_temp_traid)
|
|
inst_id = tdbb->tdbb_temp_traid;
|
|
else if (tdbb->getTransaction())
|
|
inst_id = tdbb->getTransaction()->tra_number;
|
|
else // called without transaction, maybe from OPT or CMP ?
|
|
return &rel_pages_base;
|
|
}
|
|
else
|
|
inst_id = PAG_attachment_id(tdbb);
|
|
|
|
if (!rel_pages_inst)
|
|
rel_pages_inst = FB_NEW_POOL(*rel_pool) RelationPagesInstances(*rel_pool);
|
|
|
|
FB_SIZE_T pos;
|
|
if (!rel_pages_inst->find(inst_id, pos))
|
|
{
|
|
if (!allocPages)
|
|
return 0;
|
|
|
|
RelationPages* newPages = rel_pages_free;
|
|
if (!newPages)
|
|
{
|
|
const size_t BULK_ALLOC = 8;
|
|
|
|
RelationPages* allocatedPages = newPages =
|
|
FB_NEW_POOL(*rel_pool) RelationPages[BULK_ALLOC];
|
|
|
|
rel_pages_free = ++allocatedPages;
|
|
for (size_t i = 1; i < BULK_ALLOC - 1; i++, allocatedPages++)
|
|
allocatedPages->rel_next_free = allocatedPages + 1;
|
|
}
|
|
else
|
|
{
|
|
rel_pages_free = newPages->rel_next_free;
|
|
newPages->rel_next_free = 0;
|
|
}
|
|
|
|
fb_assert(newPages->useCount == 0);
|
|
|
|
newPages->addRef();
|
|
newPages->rel_instance_id = inst_id;
|
|
newPages->rel_pg_space_id = dbb->dbb_page_manager.getTempPageSpaceID(tdbb);
|
|
rel_pages_inst->add(newPages);
|
|
|
|
// create primary pointer page and index root page
|
|
DPM_create_relation_pages(tdbb, this, newPages);
|
|
|
|
#ifdef VIO_DEBUG
|
|
VIO_trace(DEBUG_WRITES,
|
|
"jrd_rel::getPages inst %" ULONGFORMAT", ppp %" SLONGFORMAT", irp %" SLONGFORMAT", addr 0x%x\n",
|
|
newPages->rel_instance_id,
|
|
newPages->rel_pages ? (*newPages->rel_pages)[0] : 0,
|
|
newPages->rel_index_root,
|
|
newPages);
|
|
#endif
|
|
|
|
// create indexes
|
|
MemoryPool* pool = tdbb->getDefaultPool();
|
|
const bool poolCreated = !pool;
|
|
|
|
if (poolCreated)
|
|
pool = dbb->createPool();
|
|
Jrd::ContextPoolHolder context(tdbb, pool);
|
|
|
|
jrd_tra* idxTran = tdbb->getTransaction();
|
|
if (!idxTran)
|
|
idxTran = attachment->getSysTransaction();
|
|
|
|
IndexDescAlloc* indices = NULL;
|
|
// read indices from "base" index root page
|
|
const USHORT idx_count = BTR_all(tdbb, this, &indices, &rel_pages_base);
|
|
|
|
const index_desc* const end = indices->items + idx_count;
|
|
for (index_desc* idx = indices->items; idx < end; idx++)
|
|
{
|
|
Firebird::MetaName idx_name;
|
|
MET_lookup_index(tdbb, idx_name, this->rel_name, idx->idx_id + 1);
|
|
|
|
idx->idx_root = 0;
|
|
SelectivityList selectivity(*pool);
|
|
IDX_create_index(tdbb, this, idx, idx_name.c_str(), NULL, idxTran, selectivity);
|
|
|
|
#ifdef VIO_DEBUG
|
|
VIO_trace(DEBUG_WRITES,
|
|
"jrd_rel::getPages inst %" SQUADFORMAT", irp %" SLONGFORMAT", idx %u, idx_root %" SLONGFORMAT", addr 0x%x\n",
|
|
newPages->rel_instance_id,
|
|
newPages->rel_index_root,
|
|
idx->idx_id,
|
|
idx->idx_root,
|
|
newPages);
|
|
#endif
|
|
}
|
|
|
|
if (poolCreated)
|
|
dbb->deletePool(pool);
|
|
delete indices;
|
|
|
|
return newPages;
|
|
}
|
|
|
|
RelationPages* pages = (*rel_pages_inst)[pos];
|
|
fb_assert(pages->rel_instance_id == inst_id);
|
|
return pages;
|
|
}
|
|
|
|
bool jrd_rel::delPages(thread_db* tdbb, TraNumber tran, RelationPages* aPages)
|
|
{
|
|
RelationPages* pages = aPages ? aPages : getPages(tdbb, tran, false);
|
|
if (!pages || !pages->rel_instance_id)
|
|
return false;
|
|
|
|
//fb_assert((tran <= 0) || ((tran > 0) && (pages->rel_instance_id == tran)));
|
|
fb_assert(tran == 0 || tran == MAX_TRA_NUMBER ||
|
|
(tran > 0 && pages->rel_instance_id == tran));
|
|
|
|
fb_assert(pages->useCount > 0);
|
|
|
|
if (--pages->useCount)
|
|
return false;
|
|
|
|
#ifdef VIO_DEBUG
|
|
VIO_trace(DEBUG_WRITES,
|
|
"jrd_rel::delPages inst %" ULONGFORMAT", ppp %" SLONGFORMAT", irp %" SLONGFORMAT", addr 0x%x\n",
|
|
pages->rel_instance_id,
|
|
pages->rel_pages ? (*pages->rel_pages)[0] : 0,
|
|
pages->rel_index_root,
|
|
pages);
|
|
#endif
|
|
|
|
FB_SIZE_T pos;
|
|
#ifdef DEV_BUILD
|
|
const bool found =
|
|
#endif
|
|
rel_pages_inst->find(pages->rel_instance_id, pos);
|
|
fb_assert(found && ((*rel_pages_inst)[pos] == pages) );
|
|
|
|
rel_pages_inst->remove(pos);
|
|
|
|
if (pages->rel_index_root)
|
|
IDX_delete_indices(tdbb, this, pages);
|
|
|
|
if (pages->rel_pages)
|
|
DPM_delete_relation_pages(tdbb, this, pages);
|
|
|
|
pages->free(rel_pages_free);
|
|
return true;
|
|
}
|
|
|
|
void jrd_rel::getRelLockKey(thread_db* tdbb, UCHAR* key)
|
|
{
|
|
const ULONG val = rel_id;
|
|
memcpy(key, &val, sizeof(ULONG));
|
|
key += sizeof(ULONG);
|
|
|
|
const SINT64 inst_id = getPages(tdbb)->rel_instance_id;
|
|
memcpy(key, &inst_id, sizeof(SINT64));
|
|
}
|
|
|
|
USHORT jrd_rel::getRelLockKeyLength() const
|
|
{
|
|
return sizeof(ULONG) + sizeof(SINT64);
|
|
}
|
|
|
|
void jrd_rel::cleanUp()
|
|
{
|
|
delete rel_pages_inst;
|
|
rel_pages_inst = NULL;
|
|
}
|
|
|
|
|
|
void jrd_rel::fillPagesSnapshot(RelPagesSnapshot& snapshot, const bool attachmentOnly)
|
|
{
|
|
if (rel_pages_inst)
|
|
{
|
|
for (FB_SIZE_T i = 0; i < rel_pages_inst->getCount(); i++)
|
|
{
|
|
RelationPages* relPages = (*rel_pages_inst)[i];
|
|
|
|
if (!attachmentOnly)
|
|
{
|
|
snapshot.add(relPages);
|
|
relPages->addRef();
|
|
}
|
|
else if ((rel_flags & REL_temp_conn) &&
|
|
PAG_attachment_id(snapshot.spt_tdbb) == relPages->rel_instance_id)
|
|
{
|
|
snapshot.add(relPages);
|
|
relPages->addRef();
|
|
}
|
|
else if (rel_flags & REL_temp_tran)
|
|
{
|
|
const jrd_tra* tran = snapshot.spt_tdbb->getAttachment()->att_transactions;
|
|
for (; tran; tran = tran->tra_next)
|
|
{
|
|
if (tran->tra_number == relPages->rel_instance_id)
|
|
{
|
|
snapshot.add(relPages);
|
|
relPages->addRef();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
snapshot.add(&rel_pages_base);
|
|
}
|
|
|
|
void jrd_rel::RelPagesSnapshot::clear()
|
|
{
|
|
#ifdef DEV_BUILD
|
|
thread_db* tdbb = NULL;
|
|
SET_TDBB(tdbb);
|
|
fb_assert(tdbb == spt_tdbb);
|
|
#endif
|
|
|
|
for (FB_SIZE_T i = 0; i < getCount(); i++)
|
|
{
|
|
RelationPages* relPages = (*this)[i];
|
|
(*this)[i] = NULL;
|
|
|
|
spt_relation->delPages(spt_tdbb, MAX_TRA_NUMBER, relPages);
|
|
}
|
|
|
|
inherited::clear();
|
|
}
|
|
|
|
bool jrd_rel::hasTriggers() const
|
|
{
|
|
typedef const trig_vec* ctv;
|
|
ctv trigs[6] = // non-const array, don't want optimization tricks by the compiler.
|
|
{
|
|
rel_pre_erase,
|
|
rel_post_erase,
|
|
rel_pre_modify,
|
|
rel_post_modify,
|
|
rel_pre_store,
|
|
rel_post_store
|
|
};
|
|
|
|
for (int i = 0; i < 6; ++i)
|
|
{
|
|
if (trigs[i] && trigs[i]->getCount())
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
Lock* jrd_rel::createLock(thread_db* tdbb, MemoryPool* pool, jrd_rel* relation, lck_t lckType, bool noAst)
|
|
{
|
|
if (!pool)
|
|
pool = relation->rel_pool;
|
|
|
|
const USHORT relLockLen = relation->getRelLockKeyLength();
|
|
|
|
Lock* lock = FB_NEW_RPT(*pool, relLockLen) Lock(tdbb, relLockLen, lckType, relation);
|
|
relation->getRelLockKey(tdbb, lock->getKeyPtr());
|
|
|
|
lock->lck_type = lckType;
|
|
switch (lckType)
|
|
{
|
|
case LCK_relation:
|
|
break;
|
|
|
|
case LCK_rel_gc:
|
|
lock->lck_ast = noAst ? NULL : blocking_ast_gcLock;
|
|
break;
|
|
|
|
default:
|
|
fb_assert(false);
|
|
}
|
|
|
|
return lock;
|
|
}
|
|
|
|
bool jrd_rel::acquireGCLock(thread_db* tdbb, int wait)
|
|
{
|
|
fb_assert(rel_flags & REL_gc_lockneed);
|
|
if (!(rel_flags & REL_gc_lockneed))
|
|
{
|
|
fb_assert(rel_gc_lock->lck_id);
|
|
fb_assert(rel_gc_lock->lck_physical == (rel_flags & REL_gc_disabled ? LCK_SR : LCK_SW));
|
|
return true;
|
|
}
|
|
|
|
if (!rel_gc_lock)
|
|
rel_gc_lock = createLock(tdbb, NULL, this, LCK_rel_gc, false);
|
|
|
|
fb_assert(!rel_gc_lock->lck_id);
|
|
fb_assert(!(rel_flags & REL_gc_blocking));
|
|
|
|
ThreadStatusGuard temp_status(tdbb);
|
|
|
|
const USHORT level = (rel_flags & REL_gc_disabled) ? LCK_SR : LCK_SW;
|
|
bool ret = LCK_lock(tdbb, rel_gc_lock, level, wait);
|
|
if (!ret && (level == LCK_SW))
|
|
{
|
|
rel_flags |= REL_gc_disabled;
|
|
ret = LCK_lock(tdbb, rel_gc_lock, LCK_SR, wait);
|
|
if (!ret)
|
|
rel_flags &= ~REL_gc_disabled;
|
|
}
|
|
|
|
if (ret)
|
|
rel_flags &= ~REL_gc_lockneed;
|
|
|
|
return ret;
|
|
}
|
|
|
|
void jrd_rel::downgradeGCLock(thread_db* tdbb)
|
|
{
|
|
if (!rel_sweep_count && (rel_flags & REL_gc_blocking))
|
|
{
|
|
fb_assert(!(rel_flags & REL_gc_lockneed));
|
|
fb_assert(rel_gc_lock->lck_id);
|
|
fb_assert(rel_gc_lock->lck_physical == LCK_SW);
|
|
|
|
rel_flags &= ~REL_gc_blocking;
|
|
rel_flags |= REL_gc_disabled;
|
|
|
|
LCK_downgrade(tdbb, rel_gc_lock);
|
|
|
|
if (rel_gc_lock->lck_physical != LCK_SR)
|
|
{
|
|
rel_flags &= ~REL_gc_disabled;
|
|
if (rel_gc_lock->lck_physical < LCK_SR)
|
|
rel_flags |= REL_gc_lockneed;
|
|
}
|
|
}
|
|
}
|
|
|
|
int jrd_rel::blocking_ast_gcLock(void* ast_object)
|
|
{
|
|
/****
|
|
SR - gc forbidden, awaiting moment to re-establish SW lock
|
|
SW - gc allowed, usual state
|
|
PW - gc allowed to the one connection only
|
|
****/
|
|
|
|
jrd_rel* relation = static_cast<jrd_rel*>(ast_object);
|
|
|
|
try
|
|
{
|
|
Lock* lock = relation->rel_gc_lock;
|
|
Database* dbb = lock->lck_dbb;
|
|
|
|
AsyncContextHolder tdbb(dbb, FB_FUNCTION, lock);
|
|
|
|
fb_assert(!(relation->rel_flags & REL_gc_lockneed));
|
|
if (relation->rel_flags & REL_gc_lockneed) // work already done synchronously ?
|
|
return 0;
|
|
|
|
relation->rel_flags |= REL_gc_blocking;
|
|
if (relation->rel_sweep_count)
|
|
return 0;
|
|
|
|
if (relation->rel_flags & REL_gc_disabled)
|
|
{
|
|
// someone acquired EX lock
|
|
|
|
fb_assert(lock->lck_id);
|
|
fb_assert(lock->lck_physical == LCK_SR);
|
|
|
|
LCK_release(tdbb, lock);
|
|
relation->rel_flags &= ~(REL_gc_disabled | REL_gc_blocking);
|
|
relation->rel_flags |= REL_gc_lockneed;
|
|
}
|
|
else
|
|
{
|
|
// someone acquired PW lock
|
|
|
|
fb_assert(lock->lck_id);
|
|
fb_assert(lock->lck_physical == LCK_SW);
|
|
|
|
relation->rel_flags |= REL_gc_disabled;
|
|
relation->downgradeGCLock(tdbb);
|
|
}
|
|
}
|
|
catch (const Firebird::Exception&)
|
|
{} // no-op
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/// jrd_rel::GCExclusive
|
|
|
|
jrd_rel::GCExclusive::GCExclusive(thread_db* tdbb, jrd_rel* relation) :
|
|
m_tdbb(tdbb),
|
|
m_relation(relation),
|
|
m_lock(NULL)
|
|
{
|
|
}
|
|
|
|
jrd_rel::GCExclusive::~GCExclusive()
|
|
{
|
|
release();
|
|
delete m_lock;
|
|
}
|
|
|
|
bool jrd_rel::GCExclusive::acquire(int wait)
|
|
{
|
|
// if validation is already running - go out
|
|
if (m_relation->rel_flags & REL_gc_disabled)
|
|
return false;
|
|
|
|
ThreadStatusGuard temp_status(m_tdbb);
|
|
|
|
m_relation->rel_flags |= REL_gc_disabled;
|
|
|
|
int sleeps = -wait * 10;
|
|
while (m_relation->rel_sweep_count)
|
|
{
|
|
EngineCheckout cout(m_tdbb, FB_FUNCTION);
|
|
Thread::sleep(100);
|
|
|
|
if (wait < 0 && --sleeps == 0)
|
|
break;
|
|
}
|
|
|
|
if (m_relation->rel_sweep_count)
|
|
{
|
|
m_relation->rel_flags &= ~REL_gc_disabled;
|
|
return false;
|
|
}
|
|
|
|
if (!(m_relation->rel_flags & REL_gc_lockneed))
|
|
{
|
|
m_relation->rel_flags |= REL_gc_lockneed;
|
|
LCK_release(m_tdbb, m_relation->rel_gc_lock);
|
|
}
|
|
|
|
// we need no AST here
|
|
if (!m_lock)
|
|
m_lock = jrd_rel::createLock(m_tdbb, NULL, m_relation, LCK_rel_gc, true);
|
|
|
|
const bool ret = LCK_lock(m_tdbb, m_lock, LCK_PW, wait);
|
|
if (!ret)
|
|
m_relation->rel_flags &= ~REL_gc_disabled;
|
|
|
|
return ret;
|
|
}
|
|
|
|
void jrd_rel::GCExclusive::release()
|
|
{
|
|
if (!m_lock || !m_lock->lck_id)
|
|
return;
|
|
|
|
fb_assert(m_relation->rel_flags & REL_gc_disabled);
|
|
|
|
if (!(m_relation->rel_flags & REL_gc_lockneed))
|
|
{
|
|
m_relation->rel_flags |= REL_gc_lockneed;
|
|
LCK_release(m_tdbb, m_relation->rel_gc_lock);
|
|
}
|
|
|
|
LCK_convert(m_tdbb, m_lock, LCK_EX, LCK_WAIT);
|
|
m_relation->rel_flags &= ~REL_gc_disabled;
|
|
|
|
LCK_release(m_tdbb, m_lock);
|
|
}
|
|
|
|
|
|
/// RelationPages
|
|
|
|
void RelationPages::free(RelationPages*& nextFree)
|
|
{
|
|
rel_next_free = nextFree;
|
|
nextFree = this;
|
|
|
|
if (rel_pages)
|
|
rel_pages->clear();
|
|
|
|
rel_index_root = rel_data_pages = 0;
|
|
rel_slot_space = rel_pri_data_space = rel_sec_data_space = 0;
|
|
rel_instance_id = 0;
|
|
}
|