MetaName Jrd::Attachment::nameToUserCharSet(thread_db* tdbb, const MetaName& name) { if (att_charset == CS_METADATA || att_charset == CS_NONE) return name; UCHAR buffer[MAX_SQL_IDENTIFIER_SIZE]; ULONG len = INTL_convert_bytes(tdbb, att_charset, buffer, MAX_SQL_IDENTIFIER_LEN, CS_METADATA, (const BYTE*) name.c_str(), name.length(), ERR_post); buffer[len] = '\0'; return MetaName((const char*) buffer); }
void RecordSource::printInversion(thread_db* tdbb, const InversionNode* inversion, string& plan, bool detailed, unsigned level, bool navigation) { if (detailed) plan += printIndent(++level); switch (inversion->type) { case InversionNode::TYPE_AND: if (detailed) plan += "Bitmap And"; printInversion(tdbb, inversion->node1, plan, detailed, level); printInversion(tdbb, inversion->node2, plan, detailed, level); break; case InversionNode::TYPE_OR: case InversionNode::TYPE_IN: if (detailed) plan += "Bitmap Or"; printInversion(tdbb, inversion->node1, plan, detailed, level); printInversion(tdbb, inversion->node2, plan, detailed, level); break; case InversionNode::TYPE_DBKEY: if (detailed) plan += "DBKEY"; break; case InversionNode::TYPE_INDEX: { MetaName indexName; MET_lookup_index(tdbb, indexName, inversion->retrieval->irb_relation->rel_name, (USHORT) (inversion->retrieval->irb_index + 1)); if (detailed) { if (!navigation) plan += "Bitmap" + printIndent(++level); plan += "Index \"" + printName(tdbb, indexName.c_str()) + "\" Scan"; } else { plan += (plan.hasData() ? ", " : "") + printName(tdbb, indexName.c_str()); } } break; default: fb_assert(false); } }
void TraceSvcJrd::startSession(TraceSession& session, bool interactive) { if (!TraceManager::pluginsCount()) { m_svc.printf(false, "Can not start trace session. There are no trace plugins loaded\n"); return; } ConfigStorage* storage = TraceManager::getStorage(); { // scope StorageGuard guard(storage); session.ses_auth = m_authBlock; session.ses_user = m_user; MetaName role = m_role; UserId::makeRoleName(role, SQL_DIALECT_V6); session.ses_role = role.c_str(); session.ses_flags = trs_active; if (m_admin) { session.ses_flags |= trs_admin; } if (interactive) { Guid guid; GenerateGuid(&guid); char* buff = session.ses_logfile.getBuffer(GUID_BUFF_SIZE); GuidToString(buff, &guid); session.ses_logfile.insert(0, "fb_trace."); } storage->addSession(session); m_chg_number = storage->getChangeNumber(); } m_svc.started(); m_svc.printf(false, "Trace session ID %ld started\n", session.ses_id); if (interactive) { readSession(session); { StorageGuard guard(storage); storage->removeSession(session.ses_id); } } }
bool InternalConnection::isSameDatabase(thread_db* tdbb, const PathName& dbName, const MetaName& user, const string& pwd, const MetaName& role) const { if (m_isCurrent) { const UserId* attUser = m_attachment->getHandle()->att_user; return ((user.isEmpty() || user == attUser->getUserName()) && pwd.isEmpty() && (role.isEmpty() || role == attUser->getSqlRole())); } return Connection::isSameDatabase(tdbb, dbName, user, pwd, role); }
void InternalConnection::attach(thread_db* tdbb, const PathName& dbName, const MetaName& user, const string& pwd, const MetaName& role) { fb_assert(!m_attachment); Database* dbb = tdbb->getDatabase(); fb_assert(dbName.isEmpty() || dbName == dbb->dbb_database_name.c_str()); // Don't wrap raised errors. This is needed for backward compatibility. setWrapErrors(false); Jrd::Attachment* attachment = tdbb->getAttachment(); if ((user.isEmpty() || user == attachment->att_user->getUserName()) && pwd.isEmpty() && (role.isEmpty() || role == attachment->att_user->getSqlRole())) { m_isCurrent = true; m_attachment = attachment->getInterface(); } else { m_isCurrent = false; m_dbName = dbb->dbb_database_name.c_str(); generateDPB(tdbb, m_dpb, user, pwd, role); // Avoid change of m_dpb by validatePassword() below ClumpletWriter newDpb(m_dpb); validatePassword(tdbb, m_dbName, newDpb); FbLocalStatus status; { EngineCallbackGuard guard(tdbb, *this, FB_FUNCTION); RefPtr<JProvider> jInstance(JProvider::getInstance()); jInstance->setDbCryptCallback(&status, tdbb->getAttachment()->att_crypt_callback); m_attachment.assignRefNoIncr(jInstance->attachDatabase(&status, m_dbName.c_str(), newDpb.getBufferLength(), newDpb.getBuffer())); } if (status->getState() & IStatus::STATE_ERRORS) raise(&status, tdbb, "JProvider::attach"); } m_sqlDialect = (m_attachment->getHandle()->att_database->dbb_flags & DBB_DB_SQL_dialect_3) ? SQL_DIALECT_V6 : SQL_DIALECT_V5; }
void PAR_dependency(thread_db* tdbb, CompilerScratch* csb, StreamType stream, SSHORT id, const MetaName& field_name) { /************************************** * * P A R _ d e p e n d e n c y * ************************************** * * Functional description * Register a field, relation, procedure or exception reference * as a dependency. * **************************************/ SET_TDBB(tdbb); CompilerScratch::Dependency dependency(0); if (csb->csb_rpt[stream].csb_relation) { dependency.relation = csb->csb_rpt[stream].csb_relation; // How do I determine reliably this is a view? // At this time, rel_view_rse is still null. //if (is_view) // dependency.objType = obj_view; //else dependency.objType = obj_relation; } else if (csb->csb_rpt[stream].csb_procedure) { if (csb->csb_rpt[stream].csb_procedure->isSubRoutine()) return; dependency.procedure = csb->csb_rpt[stream].csb_procedure; dependency.objType = obj_procedure; } if (field_name.length() > 0) dependency.subName = FB_NEW_POOL(*tdbb->getDefaultPool()) MetaName(*tdbb->getDefaultPool(), field_name); else if (id >= 0) dependency.subNumber = id; csb->csb_dependencies.push(dependency); }
void Applier::setSequence(thread_db* tdbb, const MetaName& genName, SINT64 value) { const auto attachment = tdbb->getAttachment(); auto gen_id = attachment->att_generators.lookup(genName); if (gen_id < 0) { gen_id = MET_lookup_generator(tdbb, genName); if (gen_id < 0) raiseError("Generator %s is not found", genName.c_str()); attachment->att_generators.store(gen_id, genName); } if (DPM_gen_id(tdbb, gen_id, false, 0) < value) DPM_gen_id(tdbb, gen_id, true, value); }
bool checkCreateDatabaseGrant(const MetaName& userName, const MetaName& trustedRole, const MetaName& sqlRole, const char* securityDb) { if (userName == SYSDBA_USER_NAME) return true; RefPtr<IAttachment> att; RefPtr<ITransaction> tra; if (!openDb(securityDb, att, tra)) return false; FbLocalStatus st; MetaName role(sqlRole); if (role.hasData()) { const UCHAR info[] = { isc_info_db_sql_dialect, isc_info_end }; UCHAR buffer[BUFFER_TINY]; att->getInfo(&st, sizeof(info), info, sizeof(buffer), buffer); check("IAttachment::getInfo", &st); int dialect = SQL_DIALECT_V5; // reasonable default const UCHAR* p = buffer; while (*p != isc_info_end && *p != isc_info_truncated && p < buffer + sizeof(buffer)) { const UCHAR item = (UCHAR) *p++; const USHORT length = gds__vax_integer(p, sizeof(USHORT)); p += sizeof(USHORT); switch (item) { case isc_info_db_sql_dialect: dialect = gds__vax_integer(p, length); break; } p += length; } JRD_make_role_name(role, dialect); // We need to check is admin role granted to userName in security DB const char* sql = "select count(*) from RDB$USER_PRIVILEGES " "where RDB$USER = ? and RDB$RELATION_NAME = ? and RDB$PRIVILEGE = 'M'"; Message prm; Field<Varying> u(prm, MAX_SQL_IDENTIFIER_LEN); Field<Varying> r(prm, MAX_SQL_IDENTIFIER_LEN); u = userName.c_str(); r = role.c_str(); Message result; Field<ISC_INT64> cnt(result); att->execute(&st, tra, 0, sql, SQL_DIALECT_V6, prm.getMetadata(), prm.getBuffer(), result.getMetadata(), result.getBuffer()); if (st->getState() & IStatus::STATE_ERRORS) { // isc_dsql_relation_err when exec SQL - i.e. table RDB$USER_PRIVILEGES // is missing due to non-FB security DB if (!fb_utils::containsErrorCode(st->getErrors(), isc_dsql_relation_err)) check("IAttachment::execute", &st); role = ""; } else if (cnt == 0) role = ""; } else role = trustedRole; if (role == ADMIN_ROLE) return true; Message gr; Field<ISC_SHORT> uType(gr); Field<Varying> u(gr, MAX_SQL_IDENTIFIER_LEN); Field<ISC_SHORT> rType(gr); Field<Varying> r(gr, MAX_SQL_IDENTIFIER_LEN); uType = obj_user; u = userName.c_str(); rType = role.hasData() ? obj_sql_role : 255; r = role.c_str(); Message result; Field<ISC_INT64> cnt(result); att->execute(&st, tra, 0, "select count(*) from RDB$DB_CREATORS" " where (RDB$USER_TYPE = ? and RDB$USER = ?) or (RDB$USER_TYPE = ? and RDB$USER = ?)", SQL_DIALECT_V6, gr.getMetadata(), gr.getBuffer(), result.getMetadata(), result.getBuffer()); if (st->getState() & IStatus::STATE_ERRORS) { if (fb_utils::containsErrorCode(st->getErrors(), isc_dsql_relation_err)) { // isc_dsql_relation_err when exec SQL - i.e. table RDB$DB_CREATORS // is missing due to non-FB3 security DB return false; } check("IAttachment::execute", &st); } return cnt > 0; }
Str::Str(const MetaName& text) throw() : Base(isc_arg_string, (ISC_STATUS)(IPTR) text.c_str()) { }
void Applier::deleteRecord(thread_db* tdbb, TraNumber traNum, const MetaName& relName, ULONG length, const UCHAR* data) { jrd_tra* transaction = NULL; if (!m_txnMap.get(traNum, transaction)) raiseError("Transaction %" SQUADFORMAT" is not found", traNum); LocalThreadContext context(tdbb, transaction, m_request); TRA_attach_request(transaction, m_request); const auto relation = MET_lookup_relation(tdbb, relName); if (!relation) raiseError("Table %s is not found", relName.c_str()); if (!(relation->rel_flags & REL_scanned)) MET_scan_relation(tdbb, relation); const auto format = findFormat(tdbb, relation, length); record_param rpb; rpb.rpb_relation = relation; rpb.rpb_record = m_record; const auto record = m_record = VIO_record(tdbb, &rpb, format, m_request->req_pool); rpb.rpb_format_number = format->fmt_version; rpb.rpb_address = record->getData(); rpb.rpb_length = length; record->copyDataFrom(data); index_desc idx; const bool indexed = lookupRecord(tdbb, relation, record, m_bitmap, idx); bool found = false; AutoPtr<Record> cleanup; if (m_bitmap->getFirst()) { record_param tempRpb = rpb; tempRpb.rpb_record = NULL; do { tempRpb.rpb_number.setValue(m_bitmap->current()); if (VIO_get(tdbb, &tempRpb, transaction, m_request->req_pool) && (!indexed || compareKey(tdbb, relation, idx, record, tempRpb.rpb_record))) { if (found) raiseError("Record in table %s is ambiguously identified using the primary/unique key", relName.c_str()); rpb = tempRpb; found = true; } } while (m_bitmap->getNext()); cleanup = tempRpb.rpb_record; } if (found) { doDelete(tdbb, &rpb, transaction); } else { #ifdef RESOLVE_CONFLICTS logWarning("Record being deleted from table %s does not exist, ignoring", relName.c_str()); #else raiseError("Record in table %s cannot be located via the primary/unique key", relName.c_str()); #endif } }
void Applier::updateRecord(thread_db* tdbb, TraNumber traNum, const MetaName& relName, ULONG orgLength, const UCHAR* orgData, ULONG newLength, const UCHAR* newData) { jrd_tra* transaction = NULL; if (!m_txnMap.get(traNum, transaction)) raiseError("Transaction %" SQUADFORMAT" is not found", traNum); LocalThreadContext context(tdbb, transaction, m_request); TRA_attach_request(transaction, m_request); const auto relation = MET_lookup_relation(tdbb, relName); if (!relation) raiseError("Table %s is not found", relName.c_str()); if (!(relation->rel_flags & REL_scanned)) MET_scan_relation(tdbb, relation); const auto orgFormat = findFormat(tdbb, relation, orgLength); record_param orgRpb; orgRpb.rpb_relation = relation; orgRpb.rpb_record = m_record; const auto orgRecord = m_record = VIO_record(tdbb, &orgRpb, orgFormat, m_request->req_pool); orgRpb.rpb_format_number = orgFormat->fmt_version; orgRpb.rpb_address = orgRecord->getData(); orgRpb.rpb_length = orgLength; orgRecord->copyDataFrom(orgData); BlobList sourceBlobs(getPool()); sourceBlobs.resize(orgFormat->fmt_count); for (USHORT id = 0; id < orgFormat->fmt_count; id++) { dsc desc; if (DTYPE_IS_BLOB(orgFormat->fmt_desc[id].dsc_dtype) && EVL_field(NULL, orgRecord, id, &desc)) { const auto source = (bid*) desc.dsc_address; if (!source->isEmpty()) sourceBlobs[id] = *source; } } index_desc idx; const auto indexed = lookupRecord(tdbb, relation, orgRecord, m_bitmap, idx); bool found = false; AutoPtr<Record> cleanup; if (m_bitmap->getFirst()) { record_param tempRpb = orgRpb; tempRpb.rpb_record = NULL; do { tempRpb.rpb_number.setValue(m_bitmap->current()); if (VIO_get(tdbb, &tempRpb, transaction, m_request->req_pool) && (!indexed || compareKey(tdbb, relation, idx, orgRecord, tempRpb.rpb_record))) { if (found) raiseError("Record in table %s is ambiguously identified using the primary/unique key", relName.c_str()); orgRpb = tempRpb; found = true; } } while (m_bitmap->getNext()); cleanup = tempRpb.rpb_record; } const auto newFormat = findFormat(tdbb, relation, newLength); record_param newRpb; newRpb.rpb_relation = relation; newRpb.rpb_record = NULL; AutoPtr<Record> newRecord(VIO_record(tdbb, &newRpb, newFormat, m_request->req_pool)); newRpb.rpb_format_number = newFormat->fmt_version; newRpb.rpb_address = newRecord->getData(); newRpb.rpb_length = newLength; newRecord->copyDataFrom(newData); if (found) { doUpdate(tdbb, &orgRpb, &newRpb, transaction, &sourceBlobs); } else { #ifdef RESOLVE_CONFLICTS logWarning("Record being updated in table %s does not exist, inserting instead", relName.c_str()); doInsert(tdbb, &newRpb, transaction); #else raiseError("Record in table %s cannot be located via the primary/unique key", relName.c_str()); #endif } }
void Applier::insertRecord(thread_db* tdbb, TraNumber traNum, const MetaName& relName, ULONG length, const UCHAR* data) { jrd_tra* transaction = NULL; if (!m_txnMap.get(traNum, transaction)) raiseError("Transaction %" SQUADFORMAT" is not found", traNum); LocalThreadContext context(tdbb, transaction, m_request); TRA_attach_request(transaction, m_request); const auto relation = MET_lookup_relation(tdbb, relName); if (!relation) raiseError("Table %s is not found", relName.c_str()); if (!(relation->rel_flags & REL_scanned)) MET_scan_relation(tdbb, relation); const auto format = findFormat(tdbb, relation, length); record_param rpb; rpb.rpb_relation = relation; rpb.rpb_record = m_record; const auto record = m_record = VIO_record(tdbb, &rpb, format, m_request->req_pool); rpb.rpb_format_number = format->fmt_version; rpb.rpb_address = record->getData(); rpb.rpb_length = length; record->copyDataFrom(data); try { doInsert(tdbb, &rpb, transaction); return; } catch (const status_exception& ex) { // Uniqueness violation is handled below, other exceptions are re-thrown if (ex.value()[1] != isc_unique_key_violation && ex.value()[1] != isc_no_dup) { throw; } fb_utils::init_status(tdbb->tdbb_status_vector); } bool found = false; #ifdef RESOLVE_CONFLICTS index_desc idx; const auto indexed = lookupRecord(tdbb, relation, record, m_bitmap, idx); AutoPtr<Record> cleanup; if (m_bitmap->getFirst()) { record_param tempRpb = rpb; tempRpb.rpb_record = NULL; do { tempRpb.rpb_number.setValue(m_bitmap->current()); if (VIO_get(tdbb, &tempRpb, transaction, m_request->req_pool) && (!indexed || compareKey(tdbb, relation, idx, record, tempRpb.rpb_record))) { if (found) raiseError("Record in table %s is ambiguously identified using the primary/unique key", relName.c_str()); rpb = tempRpb; found = true; } } while (m_bitmap->getNext()); cleanup = tempRpb.rpb_record; } #endif if (found) { logWarning("Record being inserted into table %s already exists, updating instead", relName.c_str()); record_param newRpb; newRpb.rpb_relation = relation; newRpb.rpb_record = NULL; AutoPtr<Record> newRecord(VIO_record(tdbb, &newRpb, format, m_request->req_pool)); newRpb.rpb_format_number = format->fmt_version; newRpb.rpb_address = newRecord->getData(); newRpb.rpb_length = length; newRecord->copyDataFrom(data); doUpdate(tdbb, &rpb, &newRpb, transaction, NULL); } else { doInsert(tdbb, &rpb, transaction); // second (paranoid) attempt } }
bool checkCreateDatabaseGrant(const MetaName& userName, const MetaName& trustedRole, const MetaName& sqlRole, const char* securityDb) { if (userName == DBA_USER_NAME) return true; RefPtr<IAttachment> att; RefPtr<ITransaction> tra; bool hasDb = openDb(securityDb, att, tra); FbLocalStatus st; MetaName role(sqlRole); if (hasDb && role.hasData()) { const UCHAR info[] = { isc_info_db_sql_dialect, isc_info_end }; UCHAR buffer[BUFFER_TINY]; att->getInfo(&st, sizeof(info), info, sizeof(buffer), buffer); check("IAttachment::getInfo", &st); int dialect = SQL_DIALECT_V5; // reasonable default const UCHAR* p = buffer; while (*p != isc_info_end && *p != isc_info_truncated && p < buffer + sizeof(buffer)) { const UCHAR item = (UCHAR) *p++; const USHORT length = gds__vax_integer(p, sizeof(USHORT)); p += sizeof(USHORT); switch (item) { case isc_info_db_sql_dialect: dialect = gds__vax_integer(p, length); break; } p += length; } UserId::makeRoleName(role, dialect); // We need to check is role granted to userName in security DB const char* sql = "select count(*) from RDB$USER_PRIVILEGES " "where RDB$USER = ? and RDB$RELATION_NAME = ? and RDB$PRIVILEGE = 'M'"; Message prm; Field<Varying> u(prm, MAX_SQL_IDENTIFIER_LEN); Field<Varying> r(prm, MAX_SQL_IDENTIFIER_LEN); u = userName.c_str(); r = role.c_str(); Message result; Field<ISC_INT64> cnt(result); att->execute(&st, tra, 0, sql, SQL_DIALECT_V6, prm.getMetadata(), prm.getBuffer(), result.getMetadata(), result.getBuffer()); if (st->getState() & IStatus::STATE_ERRORS) { // isc_dsql_relation_err when exec SQL - i.e. table RDB$USER_PRIVILEGES // is missing due to non-FB security DB if (!fb_utils::containsErrorCode(st->getErrors(), isc_dsql_relation_err)) check("IAttachment::execute", &st); role = ""; } else if (cnt == 0) role = ""; } else role = trustedRole; if (role == ADMIN_ROLE) return true; if (!hasDb) return false; // check db creators table Message gr; Field<ISC_SHORT> uType(gr); Field<Varying> u(gr, MAX_SQL_IDENTIFIER_LEN); Field<ISC_SHORT> rType(gr); Field<Varying> r(gr, MAX_SQL_IDENTIFIER_LEN); uType = obj_user; u = userName.c_str(); rType = role.hasData() ? obj_sql_role : 255; r = role.c_str(); Message result; Field<ISC_INT64> cnt(result); att->execute(&st, tra, 0, "select count(*) from RDB$DB_CREATORS" " where (RDB$USER_TYPE = ? and RDB$USER = ?) or (RDB$USER_TYPE = ? and RDB$USER = ?)", SQL_DIALECT_V6, gr.getMetadata(), gr.getBuffer(), result.getMetadata(), result.getBuffer()); if (st->getState() & IStatus::STATE_ERRORS) { if (fb_utils::containsErrorCode(st->getErrors(), isc_dsql_relation_err)) { // isc_dsql_relation_err when exec SQL - i.e. table RDB$DB_CREATORS // is missing due to non-FB3 security DB return false; } check("IAttachment::execute", &st); } if (cnt > 0) return true; if (!role.hasData()) role = "NONE"; Message par2; Field<ISC_SHORT> uType2(par2); Field<Varying> u2(par2, MAX_SQL_IDENTIFIER_LEN); Field<Varying> r2(par2, MAX_SQL_IDENTIFIER_LEN); uType2 = obj_user; u2 = userName.c_str(); r2 = role.c_str(); Message res2; Field<Text> priv(res2, 8); const char* sql = "with recursive role_tree as ( " " select rdb$relation_name as nm, 0 as ur from rdb$user_privileges " " where rdb$privilege = 'M' and rdb$field_name = 'D' and rdb$user_type = ? and rdb$user = ? " " union all " " select rdb$role_name as nm, 1 as ur from rdb$roles " " where rdb$role_name = ? " " union all " " select p.rdb$relation_name as nm, t.ur from rdb$user_privileges p " " join role_tree t on t.nm = p.rdb$user " " where p.rdb$privilege = 'M' and (p.rdb$field_name = 'D' or t.ur = 1)) " "select r.rdb$system_privileges " " from role_tree t join rdb$roles r on t.nm = r.rdb$role_name "; RefPtr<IResultSet> rs(REF_NO_INCR, att->openCursor(&st, tra, 0, sql, SQL_DIALECT_V6, par2.getMetadata(), par2.getBuffer(), res2.getMetadata(), NULL, 0)); check("IAttachment::execute", &st); UserId::Privileges privileges, wrk; while (rs->fetchNext(&st, res2.getBuffer()) == IStatus::RESULT_OK) { wrk.load(&priv); privileges |= wrk; } check("IResultSet::fetchNext", &st); return wrk.test(CREATE_DATABASE); }
void RecordSource::printInversion(thread_db* tdbb, const InversionNode* inversion, string& plan, bool detailed, unsigned level, bool navigation) { if (detailed) plan += printIndent(++level); switch (inversion->type) { case InversionNode::TYPE_AND: if (detailed) plan += "Bitmap And"; printInversion(tdbb, inversion->node1, plan, detailed, level); printInversion(tdbb, inversion->node2, plan, detailed, level); break; case InversionNode::TYPE_OR: case InversionNode::TYPE_IN: if (detailed) plan += "Bitmap Or"; printInversion(tdbb, inversion->node1, plan, detailed, level); printInversion(tdbb, inversion->node2, plan, detailed, level); break; case InversionNode::TYPE_DBKEY: if (detailed) plan += "DBKEY"; break; case InversionNode::TYPE_INDEX: { const IndexRetrieval* const retrieval = inversion->retrieval; const jrd_rel* const relation = retrieval->irb_relation; MetaName indexName; if (retrieval->irb_name && retrieval->irb_name->hasData()) indexName = *retrieval->irb_name; else indexName.printf("<index id %d>", retrieval->irb_index + 1); if (detailed) { if (!navigation) plan += "Bitmap" + printIndent(++level); const index_desc& idx = retrieval->irb_desc; const bool uniqueIdx = (idx.idx_flags & idx_unique); const USHORT segCount = idx.idx_count; const USHORT minSegs = MIN(retrieval->irb_lower_count, retrieval->irb_upper_count); const USHORT maxSegs = MAX(retrieval->irb_lower_count, retrieval->irb_upper_count); const bool equality = (retrieval->irb_generic & irb_equality); const bool partial = (retrieval->irb_generic & irb_partial); const bool fullscan = (maxSegs == 0); const bool unique = uniqueIdx && equality && (minSegs == segCount); string bounds; if (!unique && !fullscan) { if (retrieval->irb_lower_count && retrieval->irb_upper_count) { if (equality) { if (partial) bounds.printf(" (partial match: %d/%d)", maxSegs, segCount); else bounds.printf(" (full match)"); } else { bounds.printf(" (lower bound: %d/%d, upper bound: %d/%d)", retrieval->irb_lower_count, segCount, retrieval->irb_upper_count, segCount); } } else if (retrieval->irb_lower_count) { bounds.printf(" (lower bound: %d/%d)", retrieval->irb_lower_count, segCount); } else if (retrieval->irb_upper_count) { bounds.printf(" (upper bound: %d/%d)", retrieval->irb_upper_count, segCount); } } plan += "Index " + printName(tdbb, indexName.c_str()) + (fullscan ? " Full" : unique ? " Unique" : " Range") + " Scan" + bounds; } else { plan += (plan.hasData() ? ", " : "") + printName(tdbb, indexName.c_str(), false); } } break; default: fb_assert(false); } }