8
0
mirror of https://github.com/FirebirdSQL/firebird.git synced 2025-01-31 10:03:03 +01:00
firebird-mirror/src/utilities/gstat/dba_full.epp
robocop f58c769c37 Cleanup. In geeky words:
PandoraBox* pbox = reinterpret_cast<PandoraBox*>(&can_of_worms);
pbox->open();
pbox->flush();
Nickolay may want to undo my ods.h changes if gcc insists
in its crusade against non-PODs and poodles.
2004-03-18 05:56:06 +00:00

1422 lines
34 KiB
Plaintext

/*
* PROGRAM: JRD Access Method
* MODULE: dba_full.epp
* DESCRIPTION: Database analysis tool
*
* The contents of this file are subject to the Interbase 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.Inprise.com/IPL.html
*
* Software distributed under the License is distributed on an
* "AS IS" basis, 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 Inprise Corporation
* and its predecessors. Portions created by Inprise Corporation are
* Copyright (C) Inprise Corporation.
*
* All Rights Reserved.
* Contributor(s): ______________________________________.
*
* 2002.10.29 Sean Leyne - Removed obsolete "Netware" port
*
*/
#include "../jrd/common.h"
#include "firebird.h"
#include "../jrd/ib_stdio.h"
#include <errno.h>
#include <string.h>
#include "../jrd/ibsetjmp.h"
#include "../jrd/jrd_time.h"
#include "../jrd/y_ref.h"
#include "../jrd/ibase.h"
#include "../jrd/ods.h"
#include "../jrd/license.h"
#include "../utilities/gstat/ppg_proto.h"
#include "../jrd/gds_proto.h"
#include "../jrd/isc_f_proto.h"
#include "../jrd/thd.h"
#include "../jrd/enc_proto.h"
#include "../include/fb_exception.h"
#ifdef WIN_NT
#include <windows.h>
#include "../jrd/jrd_pwd.h"
#endif
#define LOCKSMITH_USER "SYSDBA"
#define LOCKSMITH_PASSWORD "masterkey"
/* For Netware the follow DB handle and isc_status is #defined to be a */
/* local variable on the stack in main. This is to avoid multiple */
/* threading problems with module level statics. */
DATABASE DB = STATIC "yachts.lnk";
#define DB db_handle
#define isc_status status_vector
#define ALLOC(size) alloc ((SLONG) size);
#define BUCKETS 5
#define WINDOW_SIZE (1 << 17)
struct dba_rel {
dba_rel* rel_next;
struct idx* rel_indexes; // forward decl
SLONG rel_index_root;
SLONG rel_pointer_page;
SLONG rel_slots;
SLONG rel_data_pages;
SLONG rel_fill_distribution[BUCKETS];
ULONG rel_total_space;
SSHORT rel_id;
SCHAR rel_name[32];
};
struct idx {
idx* idx_next;
SSHORT idx_id;
SSHORT idx_depth;
SLONG idx_leaf_buckets;
SLONG idx_total_duplicates;
SLONG idx_max_duplicates;
SLONG idx_nodes;
SLONG idx_data_length;
SLONG idx_fill_distribution[BUCKETS];
SCHAR idx_name[32];
};
/* kidnapped from jrd/pio.h and abused */
struct dba_fil {
dba_fil* fil_next; /* Next file in database */
ULONG fil_min_page; /* Minimum page number in file */
ULONG fil_max_page; /* Maximum page number in file */
USHORT fil_fudge; /* Fudge factor for page relocation */
#ifdef WIN_NT
void* fil_desc;
#else
int fil_desc;
#endif
USHORT fil_length; /* Length of expanded file name */
SCHAR fil_string[1]; /* Expanded file name */
};
static char* alloc(SLONG);
static void analyze_data(dba_rel*);
static bool analyze_data_page(dba_rel*, const data_page*);
static void analyze_index(dba_rel*, idx*);
#if (defined WIN_NT)
static void db_error(SLONG);
#else
static void db_error(int);
#endif
static dba_fil* db_open(const char*, USHORT);
static const pag* db_read(SLONG);
static bool key_equality(SSHORT, const SCHAR*, const btn*);
static void move(const SCHAR*, SCHAR*, SSHORT);
static void print_distribution(const SCHAR*, const SLONG*);
static void print_header(const header_page*);
static void truncate_name(SCHAR*);
static const TEXT help_text[] = "Available switches:\n\
-a analyze data and index pages\n\
-d analyze data pages\n\
-i analyze index leaf pages\n\
-s analyze system relations\n\
-z display version number\n";
#ifndef FPRINTF
#define FPRINTF ib_fprintf
#endif
/* threading declarations for thread data */
struct tdba {
struct thdd tdba_thd_data;
UCHAR* dba_env;
dba_fil* files;
dba_rel* relations;
SSHORT page_size;
PAG buffer1;
PAG global_buffer;
int exit_code;
IB_FILE* sw_outfile;
};
#ifdef GET_THREAD_DATA
#undef GET_THREAD_DATA
#endif
static tdba* gddba;
#define GET_THREAD_DATA (gddba)
#define SET_THREAD_DATA gddba = tddba = &thd_context; \
tddba->tdba_thd_data.thdd_type = THDD_TYPE_TDBA
#define RESTORE_THREAD_DATA
void inline dba_full_exit(int code, tdba* tddba)
{
tddba->exit_code = code;
throw std::exception();
}
int CLIB_ROUTINE main( int argc, char** argv)
{
/**************************************
*
* m a i n
*
**************************************
*
* Functional description
* Gather information from system relations to do analysis
* of a database.
*
**************************************/
isc_db_handle db_handle = NULL;
struct tdba thd_context, *tddba;
JMP_BUF env;
ISC_STATUS_ARRAY status_vector;
SET_THREAD_DATA;
memset(tddba, 0, sizeof(*tddba));
memset(&status_vector, 0, sizeof(status_vector));
tddba->dba_env = (UCHAR*) env;
if (SETJMP(env)) {
if (status_vector[1]) {
SCHAR s[1024];
const ISC_STATUS* vector = status_vector;
if (isc_interprete_cpp(s, &vector)) {
FPRINTF(tddba->sw_outfile, "%s\n", s);
s[0] = '-';
while (isc_interprete_cpp(s + 1, &vector))
FPRINTF(tddba->sw_outfile, "%s\n", s);
}
}
const int exit_code = tddba->exit_code;
RESTORE_THREAD_DATA;
exit(exit_code);
}
IB_FILE* sw_outfile = tddba->sw_outfile = ib_stderr;
#if defined (WIN95)
bool fAnsiCP = false;
#endif
/* Perform some special handling when run as an Interbase service. The
first switch can be "-svc" (lower case!) or it can be "-svc_re" followed
by 3 file descriptors to use in re-directing ib_stdin, ib_stdout, and ib_stderr. */
if (argc > 1 && !strcmp(argv[1], "-svc")) {
argv++;
argc--;
}
else if (argc > 4 && !strcmp(argv[1], "-svc_re")) {
long redir_in = atol(argv[2]);
long redir_out = atol(argv[3]);
long redir_err = atol(argv[4]);
#ifdef WIN_NT
#if defined (WIN95)
fAnsiCP = true;
#endif
redir_in = _open_osfhandle(redir_in, 0);
redir_out = _open_osfhandle(redir_out, 0);
redir_err = _open_osfhandle(redir_err, 0);
#endif
if (redir_in != 0)
if (dup2((int) redir_in, 0))
close((int) redir_in);
if (redir_out != 1)
if (dup2((int) redir_out, 1))
close((int) redir_out);
if (redir_err != 2)
if (dup2((int) redir_err, 2))
close((int) redir_err);
argv += 4;
argc -= 4;
}
const char* name = NULL;
bool sw_system;
bool sw_data;
bool sw_index;
bool sw_version;
bool sw_header;
bool sw_log;
sw_log = sw_system = sw_data = sw_index = sw_version = sw_header = false;
const char* const* const end = argv + argc;
for (++argv; argv < end;) {
const char* p = *argv++;
if (*p == '-')
switch (UPPER(p[1])) {
case 'S':
sw_system = true;
break;
case 'D':
sw_data = true;
break;
case 'I':
sw_index = true;
break;
case 'A':
sw_index = sw_data = true;
break;
case 'Z':
sw_version = true;
break;
case 'H':
sw_header = true;
break;
case 'L':
sw_log = true;
break;
default:
FPRINTF(sw_outfile, "%s\n", help_text);
dba_full_exit(FINI_ERROR, tddba);
}
else
name = p;
}
if (!name) {
FPRINTF(sw_outfile, "please retry, giving a database name\n");
dba_full_exit(FINI_ERROR, tddba);
}
if (!sw_data && !sw_index)
sw_data = sw_index = true;
#if defined (WIN95)
if (!fAnsiCP) {
ULONG ulConsoleCP;
ulConsoleCP = GetConsoleCP();
if (ulConsoleCP == GetACP())
fAnsiCP = true;
else if (ulConsoleCP != GetOEMCP()) {
FPRINTF(sw_outfile,
"WARNING: The current codepage is not supported. Any use of any\n"
" extended characters may result in incorrect file names.\n");
}
}
#endif
if (sw_version)
FPRINTF(sw_outfile, "gstat version %s\n", GDS_VERSION);
/* Open database and go to work */
dba_fil* current = db_open(name, strlen(name));
char temp[1024];
tddba->page_size = sizeof(temp);
tddba->global_buffer = (PAG) temp;
const header_page* header = (const header_page*) db_read((SLONG) 0);
if (header->hdr_ods_version != ODS_VERSION
&& header->hdr_ods_version != ODS_VERSION6)
{
FPRINTF(sw_outfile,
"Wrong ODS version, expected %d, encountered %d?\n",
ODS_VERSION, header->hdr_ods_version);
dba_full_exit(FINI_ERROR, tddba);
}
char file_name[1024];
#if defined (WIN95)
if (!fAnsiCP)
AnsiToOem(name, file_name);
else
#endif
strcpy(file_name, name);
FPRINTF(sw_outfile, "\nDatabase \"%s\"\n\n", file_name);
tddba->page_size = header->hdr_page_size;
tddba->buffer1 = (PAG) ALLOC(tddba->page_size);
tddba->global_buffer = (PAG) ALLOC(tddba->page_size);
/* gather continuation files */
SLONG page = HEADER_PAGE;
do {
if (page != HEADER_PAGE)
current = db_open(file_name, strlen(file_name));
do {
header = (const hrd*) db_read((SLONG) page);
if (current != tddba->files)
current->fil_fudge = 1; /* ignore header page once read it */
*file_name = '\0';
const UCHAR* vp = header->hdr_data;
for (const UCHAR* const vend = vp + header->hdr_page_size;
vp < vend && *vp != HDR_end; vp += 2 + vp[1])
{
if (*vp == HDR_file) {
memcpy(file_name, vp + 2, vp[1]);
*(file_name + vp[1]) = '\0';
#if defined (WIN95)
if (!fAnsiCP)
AnsiToOem(file_name, file_name);
#endif
}
if (*vp == HDR_last_page)
memcpy(&current->fil_max_page, vp + 2,
sizeof(current->fil_max_page));
}
}
while (page = header->hdr_next_page);
page = current->fil_max_page + 1; /* first page of next file */
}
while (*file_name);
/* Print header page */
page = HEADER_PAGE;
do {
header = (const header_page*) db_read((SLONG) page);
PPG_print_header(header, page, sw_outfile);
}
while (page = header->hdr_next_page);
if (sw_header)
dba_full_exit(FINI_OK, tddba);
/* print continuation file sequence */
FPRINTF(sw_outfile, "\n\nDatabase file sequence:\n");
for (current = tddba->files; current->fil_next;
current =
current->fil_next)
{
FPRINTF(sw_outfile, "File %s continues as file %s\n",
current->fil_string,
current->fil_next->fil_string);
}
FPRINTF(sw_outfile, "File %s is the %s file\n\n", current->fil_string,
(current == tddba->files) ? "only" : "last");
/* print log page */
page = LOG_PAGE;
do {
const log_info_page* logp = (const log_info_page*) db_read((SLONG) page);
PPG_print_log(logp, page, sw_outfile);
page = logp->log_next_page;
} while (page);
if (sw_log)
dba_full_exit(FINI_OK, tddba);
#if (defined WIN_NT)
UCHAR dpb_string[100];
UCHAR* dpb = dpb_string;
*dpb++ = isc_dpb_version1;
*dpb++ = isc_dpb_user_name;
*dpb++ = strlen(LOCKSMITH_USER);
const TEXT* q = LOCKSMITH_USER;
while (*q)
*dpb++ = *q++;
*dpb++ = isc_dpb_password_enc;
char password_enc[33];
strcpy(password_enc,
(char *) ENC_crypt(LOCKSMITH_PASSWORD, PASSWORD_SALT));
q = password_enc + 2;
*dpb++ = strlen(q);
while (*q)
*dpb++ = *q++;
const SSHORT dpb_length = dpb - dpb_string;
isc_attach_database(status_vector, 0, name, &DB, dpb_length,
(char*) dpb_string);
if (status_vector[1])
dba_full_exit(FINI_ERROR, tddba);
#else
READY name AS DB;
ON_ERROR
dba_full_exit(FINI_ERROR, tddba);
END_ERROR
#endif
if (sw_version)
isc_version(&DB, NULL, NULL);
isc_tr_handle transact1 = 0;
START_TRANSACTION transact1 READ_ONLY;
ON_ERROR
dba_full_exit(FINI_ERROR, tddba);
END_ERROR
isc_req_handle request1 = NULL;
isc_req_handle request2 = NULL;
isc_req_handle request3 = NULL;
FOR(TRANSACTION_HANDLE transact1 REQUEST_HANDLE request1)
X IN RDB$RELATIONS SORTED BY DESC X.RDB$RELATION_NAME
if (!sw_system && X.RDB$SYSTEM_FLAG)
continue;
if (!X.RDB$VIEW_BLR.NULL || !X.RDB$EXTERNAL_FILE.NULL)
continue;
dba_rel* arelation = (dba_rel*) ALLOC(sizeof(struct dba_rel));
arelation->rel_next = tddba->relations;
tddba->relations = arelation;
arelation->rel_id = X.RDB$RELATION_ID;
strcpy(arelation->rel_name, X.RDB$RELATION_NAME);
truncate_name(arelation->rel_name);
FOR(TRANSACTION_HANDLE transact1 REQUEST_HANDLE request2)
Y IN RDB$PAGES WITH Y.RDB$RELATION_ID EQ arelation->rel_id AND
Y.RDB$PAGE_SEQUENCE EQ 0
if (Y.RDB$PAGE_TYPE == pag_pointer)
arelation->rel_pointer_page = Y.RDB$PAGE_NUMBER;
if (Y.RDB$PAGE_TYPE == pag_root)
arelation->rel_index_root = Y.RDB$PAGE_NUMBER;
END_FOR;
ON_ERROR
dba_full_exit(FINI_ERROR, tddba);
END_ERROR
if (sw_index)
FOR(TRANSACTION_HANDLE transact1 REQUEST_HANDLE request3)
Y IN RDB$INDICES WITH Y.RDB$RELATION_NAME EQ arelation->rel_name
SORTED BY DESC Y.RDB$INDEX_NAME
if (Y.RDB$INDEX_INACTIVE)
continue;
idx* aindex = (idx*) ALLOC(sizeof(idx));
aindex->idx_next = arelation->rel_indexes;
arelation->rel_indexes = aindex;
strcpy(aindex->idx_name, Y.RDB$INDEX_NAME);
truncate_name(aindex->idx_name);
aindex->idx_id = Y.RDB$INDEX_ID - 1;
END_FOR;
ON_ERROR
dba_full_exit(FINI_ERROR, tddba);
END_ERROR
END_FOR;
ON_ERROR
dba_full_exit(FINI_ERROR, tddba);
END_ERROR
if (request1)
isc_release_request(status_vector, &request1);
if (request2)
isc_release_request(status_vector, &request2);
if (request3)
isc_release_request(status_vector, &request3);
COMMIT transact1;
ON_ERROR
dba_full_exit(FINI_ERROR, tddba);
END_ERROR
FINISH
ON_ERROR
dba_full_exit(FINI_ERROR, tddba);
END_ERROR
FPRINTF(sw_outfile, "\nAnalyzing database pages ...\n\n");
dba_rel* relation;
for (relation = tddba->relations; relation; relation = relation->rel_next) {
if (sw_data)
analyze_data(relation);
for (idx* index = relation->rel_indexes; index; index = index->idx_next)
analyze_index(relation, index);
}
/* Print results */
for (relation = tddba->relations; relation; relation = relation->rel_next) {
FPRINTF(sw_outfile, "%s (%d)\n", relation->rel_name,
relation->rel_id);
if (sw_data) {
FPRINTF(sw_outfile,
" Primary pointer page: %ld, Index root page: %ld\n",
relation->rel_pointer_page, relation->rel_index_root);
const double average =
(relation->rel_data_pages) ? (double) relation->
rel_total_space * 100 / ((double) relation->rel_data_pages *
(tddba->page_size - DPG_SIZE)) : 0;
FPRINTF(sw_outfile,
" Data pages: %ld, data page slots: %ld, average fill: %.0f%%\n",
relation->rel_data_pages, relation->rel_slots, average);
FPRINTF(sw_outfile, " Fill distribution:\n");
print_distribution("\t", relation->rel_fill_distribution);
}
FPRINTF(sw_outfile, "\n");
for (idx* index = relation->rel_indexes; index; index = index->idx_next)
{
FPRINTF(sw_outfile, " Index %s (%d)\n", index->idx_name,
index->idx_id);
FPRINTF(sw_outfile,
"\tDepth: %d, leaf buckets: %ld, nodes: %ld\n",
index->idx_depth, index->idx_leaf_buckets,
index->idx_nodes);
const double average =
(index->idx_nodes) ? index->idx_data_length /
index->idx_nodes : 0;
FPRINTF(sw_outfile,
"\tAverage data length: %.2f, total dup: %ld, max dup: %ld\n",
average, index->idx_total_duplicates,
index->idx_max_duplicates);
FPRINTF(sw_outfile, "\tFill distribution:\n");
print_distribution("\t ", index->idx_fill_distribution);
FPRINTF(sw_outfile, "\n");
}
}
dba_full_exit(FINI_OK, tddba);
return 0;
}
static char* alloc( SLONG size)
{
/**************************************
*
* a l l o c
*
**************************************
*
* Functional description
* Allocate and zero a piece of memory.
*
**************************************/
char* const block = (char*) gds__alloc(size);
if (!block) {
/* NOMEM: return error */
dba_error(31, 0, 0, 0, 0, 0);
}
char* p = block;
do {
*p++ = 0;
} while (--size);
return block;
}
static void analyze_data( dba_rel* relation)
{
/**************************************
*
* a n a l y z e _ d a t a
*
**************************************
*
* Functional description
* Analyze data pages associated with relation.
*
**************************************/
tdba* tddba = GET_THREAD_DATA;
pointer_page* ptr_page = (pointer_page*) tddba->buffer1;
for (SLONG next_pp = relation->rel_pointer_page; next_pp;
next_pp = ptr_page->ppg_next)
{
move((const SCHAR*) db_read(next_pp), (SCHAR*) ptr_page, tddba->page_size);
const SLONG* ptr = ptr_page->ppg_page;
for (const SLONG* const end =
ptr + ptr_page->ppg_count; ptr < end; ptr++)
{
++relation->rel_slots;
if (*ptr) {
++relation->rel_data_pages;
if (!analyze_data_page(relation, (const data_page*) db_read(*ptr)))
FPRINTF(tddba->sw_outfile,
" Expected data on page %ld\n", *ptr);
}
}
}
}
static bool analyze_data_page( dba_rel* relation, const data_page* page)
{
/**************************************
*
* a n a l y z e _ d a t a _ p a g e
*
**************************************
*
* Functional description
* Analyze space utilization for data page.
*
**************************************/
tdba* tddba = GET_THREAD_DATA;
if (page->pag_type != pag_data)
return false;
SSHORT space = page->dpg_count * sizeof(data_page::dpg_repeat);
const data_page::dpg_repeat* tail = page->dpg_rpt;
for (const data_page::dpg_repeat* const end = tail + page->dpg_count; tail < end;
tail++)
{
if (tail->dpg_offset && tail->dpg_length)
space += tail->dpg_length;
}
relation->rel_total_space += space;
SSHORT bucket = (space * BUCKETS) / (tddba->page_size - DPG_SIZE);
if (bucket == BUCKETS)
--bucket;
++relation->rel_fill_distribution[bucket];
return true;
}
static void analyze_index( dba_rel* relation, idx* index)
{
/**************************************
*
* a n a l y z e _ i n d e x
*
**************************************
*
* Functional description
*
**************************************/
SSHORT n, space, duplicates, key_length, l, page_count;
UCHAR key[256], *p, *q;
SLONG number, page, prior_page, node_count;
tdba* tddba = GET_THREAD_DATA;
index_root_page* index_root =
(const index_root_page*) db_read(relation->rel_index_root);
if (index_root->irt_count <= index->idx_id ||
!(page = index_root->irt_rpt[index->idx_id].irt_root))
{
return;
}
const btree_page* bucket = (const btree_page*) db_read(page);
if (bucket->btr_length > tddba->page_size) {
FPRINTF(tddba->sw_outfile,
"\n***ERROR: page size is too large: %ld\n",
bucket->btr_length);
return;
}
index->idx_depth = bucket->btr_level + 1;
/*** go down through the levels, printing all pages ***/
while (bucket->btr_level) {
FPRINTF(tddba->sw_outfile, "\nlevel %ld:\n", bucket->btr_level);
SLONG right_sibling = page;
move((SCHAR*) bucket->btr_nodes[0].btn_number, (SCHAR*) &page, sizeof(page));
node_count = 0;
prior_page = 0;
for (page_count = 0; right_sibling;
right_sibling = bucket->btr_sibling, page_count++)
{
FPRINTF(tddba->sw_outfile, "page: %ld\n", right_sibling);
bucket = (const btree_page*) db_read(right_sibling);
if (bucket->btr_left_sibling != prior_page) {
FPRINTF(tddba->sw_outfile,
"\n***ERROR: left sibling is %ld\n",
bucket->btr_left_sibling);
return;
}
if (bucket->btr_length > tddba->page_size) {
FPRINTF(tddba->sw_outfile,
"\n***ERROR: page size is too large: %ld\n",
bucket->btr_length);
return;
}
prior_page = right_sibling;
if (bucket->pag_flags & btr_not_propogated) {
FPRINTF(tddba->sw_outfile,
"\n***ERROR: non-leaf bucket marked not propogated\n");
return;
}
/* count the nodes on page as well */
SLONG node_page_count = 0;
FPRINTF(tddba->sw_outfile, "\nnodes:");
for (const btn* node = bucket->btr_nodes; true;
node = (btn*) (node->btn_data + node->btn_length))
{
move((SCHAR*) node->btn_number, (SCHAR*) &number, sizeof(number));
if (!(node_page_count % 4))
FPRINTF(tddba->sw_outfile, "\n");
FPRINTF(tddba->sw_outfile, "[%ld:%d,%d,", number,
node->btn_prefix, node->btn_length);
/* print out the prefix */
if (l = node->btn_prefix) {
p = key;
FPRINTF(tddba->sw_outfile, "(");
do {
FPRINTF(tddba->sw_outfile, "\'%d\'", *p);
p++;
} while (--l);
FPRINTF(tddba->sw_outfile, ")");
}
if (l = node->btn_length) {
p = key + node->btn_prefix;
q = node->btn_data;
do {
FPRINTF(tddba->sw_outfile, "\'%d\'", *q);
*p++ = *q++;
} while (--l);
}
FPRINTF(tddba->sw_outfile, "] ");
if (number < 0)
break;
if (!node->btn_length && !node->btn_prefix &&
(bucket->btr_left_sibling || node > bucket->btr_nodes))
{
FPRINTF(tddba->sw_outfile,
"\n***ERROR: node prefix and length 0\n");
return;
}
node_page_count++;
}
if (!node_page_count) {
FPRINTF(tddba->sw_outfile, "\n***ERROR: page empty\n");
return;
}
FPRINTF(tddba->sw_outfile, "\nnodes on page: %d\n",
node_page_count);
node_count += node_page_count;
}
FPRINTF(tddba->sw_outfile, "\npage count: %d\n", page_count);
FPRINTF(tddba->sw_outfile, "node count: %d\n", node_count);
bucket = (const btree_page*) db_read(page);
if (bucket->btr_length > tddba->page_size) {
FPRINTF(tddba->sw_outfile,
"\n***ERROR: page size is too large: %ld\n",
bucket->btr_length);
return;
}
}
duplicates = 0;
key_length = 0;
FPRINTF(tddba->sw_outfile, "\nlevel 0:");
for (;;) {
/*** print the pages involved at the leaf ***/
if (bucket->pag_flags & btr_not_propogated)
FPRINTF(tddba->sw_outfile, "\n\n(page %ld):", page);
else
FPRINTF(tddba->sw_outfile, "\n\npage %ld:", page);
++index->idx_leaf_buckets;
SLONG node_page_count = 0;
for (node = bucket->btr_nodes;;
node = (BTN) (node->btn_data + node->btn_length))
{
move((SCHAR*) node->btn_number, (SCHAR*) &number, sizeof(number));
if (!(node_page_count % 4))
FPRINTF(tddba->sw_outfile, "\n");
FPRINTF(tddba->sw_outfile, "[%ld:%d,%d,", number,
node->btn_prefix, node->btn_length);
/* print out the prefix */
if (l = node->btn_prefix) {
p = key;
FPRINTF(tddba->sw_outfile, "(");
do {
FPRINTF(tddba->sw_outfile, "\'%d\'", *p++);
} while (--l);
FPRINTF(tddba->sw_outfile, ")");
}
/* print out the node data */
if (l = node->btn_length) {
q = node->btn_data;
do {
FPRINTF(tddba->sw_outfile, "\'%d\'", *q++);
} while (--l);
}
FPRINTF(tddba->sw_outfile, "] ");
if (number < 0)
break;
++index->idx_nodes;
index->idx_data_length += node->btn_length;
l = node->btn_length + node->btn_prefix;
bool dup;
if (node == bucket->btr_nodes)
dup = key_equality(key_length, (SCHAR*) key, node);
else
dup = !node->btn_length && l == key_length;
if (dup) {
++index->idx_total_duplicates;
++duplicates;
}
else {
if (duplicates > index->idx_max_duplicates)
index->idx_max_duplicates = duplicates;
duplicates = 0;
}
/* save the key */
key_length = l;
if (l = node->btn_length) {
p = key + node->btn_prefix;
q = node->btn_data;
do {
*p++ = *q++;
} while (--l);
}
node_page_count++;
}
FPRINTF(tddba->sw_outfile, "\nnode count: %ld", node_page_count);
if (duplicates > index->idx_max_duplicates)
index->idx_max_duplicates = duplicates;
space = bucket->btr_length - OFFSETA(btree_page*, btr_nodes);
n = (space * BUCKETS) / (tddba->page_size - OFFSETA(btree_page*, btr_nodes));
if (n == BUCKETS)
--n;
++index->idx_fill_distribution[n];
if (number == END_LEVEL)
break;
number = page;
page = bucket->btr_sibling;
bucket = (const btree_page*) db_read(page);
if (bucket->pag_type != pag_index) {
FPRINTF(tddba->sw_outfile,
"\n***ERROR: Expected b-tree bucket on page %ld from %ld\n",
page, number);
break;
}
if (bucket->btr_length > tddba->page_size) {
FPRINTF(tddba->sw_outfile,
"\n***ERROR: page size is too large: %ld\n",
bucket->btr_length);
return;
}
if (bucket->btr_left_sibling != number) {
FPRINTF(tddba->sw_outfile, "\n***ERROR: left sibling is %ld\n",
bucket->btr_left_sibling);
return;
}
}
FPRINTF(tddba->sw_outfile, "\n");
}
#ifdef WIN_NT
static void db_error( SLONG status)
{
/**************************************
*
* d b _ e r r o r ( W I N _ N T )
*
**************************************
*
* Functional description
*
**************************************/
TEXT s[128];
tdba* tddba = GET_THREAD_DATA;
if (!FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM,
NULL,
status,
GetUserDefaultLangID(),
s,
sizeof(s),
NULL))
sprintf(s, "unknown Windows NT error %ld", status);
FPRINTF(tddba->sw_outfile, "%s\n", s);
dba_full_exit(FINI_ERROR, tddba);
}
// CVC: This function was using cast to char* for the first param always
// and the callers had to cast their char*'s to UCHAR*, too. Since the
// real parameter is char* and always the usage is char*, I changed signature.
static dba_fil* db_open(const char* file_name, USHORT file_length)
{
/**************************************
*
* d b _ o p e n ( W I N _ N T )
*
**************************************
*
* Functional description
* Open a database file.
*
**************************************/
dba_fil* fil;
tdba* tddba = GET_THREAD_DATA;
if (tddba->files) {
for (fil = tddba->files; fil->fil_next; fil = fil->fil_next);
fil->fil_next =
(dba_fil*) ALLOC(sizeof(dba_fil) + strlen(file_name) + 1);
fil->fil_next->fil_min_page = fil->fil_max_page + 1;
fil = fil->fil_next;
}
else { /* empty list */
fil = tddba->files =
(dba_fil*) ALLOC(sizeof(dba_fil) + strlen(file_name) + 1);
fil->fil_min_page = 0L;
}
fil->fil_next = NULL;
strcpy(fil->fil_string, file_name);
fil->fil_length = strlen(file_name);
fil->fil_fudge = 0;
fil->fil_max_page = 0L;
if ((fil->fil_desc = CreateFile(file_name,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL |
FILE_FLAG_RANDOM_ACCESS,
0)) ==
INVALID_HANDLE_VALUE) db_error(GetLastError());
return fil;
}
static const pag* db_read( SLONG page_number)
{
/**************************************
*
* d b _ r e a d ( W I N _ N T )
*
**************************************
*
* Functional description
* Read a database page.
*
**************************************/
tdba* tddba = GET_THREAD_DATA;
dba_fil* fil;
for (fil = tddba->files;
page_number > fil->fil_max_page
&& fil->fil_next;)
{
fil = fil->fil_next;
}
page_number -= fil->fil_min_page - fil->fil_fudge;
LARGE_INTEGER liOffset;
liOffset.QuadPart =
UInt32x32To64((DWORD) page_number, (DWORD) tddba->page_size);
if (SetFilePointer
(fil->fil_desc, (LONG) liOffset.LowPart, &liOffset.HighPart,
FILE_BEGIN) == -1)
{
db_error(GetLastError());
}
SLONG actual_length;
if (!ReadFile(fil->fil_desc, tddba->global_buffer, tddba->page_size,
reinterpret_cast<LPDWORD>(&actual_length), NULL))
{
db_error(GetLastError());
}
if (actual_length != tddba->page_size) {
FPRINTF(tddba->sw_outfile, "Unexpected end of database file.\n");
dba_full_exit(FINI_ERROR, tddba);
}
return tddba->global_buffer;
}
#else
// now, on non WinNT
static void db_error( int status)
{
/**************************************
*
* d b _ e r r o r
*
**************************************
*
* Functional description
*
**************************************/
tdba* tddba = GET_THREAD_DATA;
/* FIXME: The strerror() function returns the appropriate description
string, or an unknown error message if the error code is unknown.
EKU: p cannot be NULL! */
#if 1
FPRINTF(tddba->sw_outfile, "%s\n", strerror(status));
#else
#ifndef VMS
FPRINTF(tddba->sw_outfile, "%s\n", strerror(status));
#else
const SCHAR* p;
if ((p = strerror(status)) || (p = strerror(EVMSERR, status)))
FPRINTF(tddba->sw_outfile, "%s\n", p);
else
FPRINTF(tddba->sw_outfile, "uninterpreted code %x\n", status);
#endif
#endif
dba_full_exit(FINI_ERROR, tddba);
}
// CVC: This function was using cast to char* for the first param always
// and the callers had to cast their char*'s to UCHAR*, too. Since the
// real parameter is char* and always the usage is char*, I changed signature.
static dba_fil* db_open(const char* file_name, USHORT file_length)
{
/**************************************
*
* d b _ o p e n
*
**************************************
*
* Functional description
* Open a database file.
* Put the file on an ordered list.
*
**************************************/
dba_fil* fil;
tdba* tddba = GET_THREAD_DATA;
if (tddba->files) {
for (fil = tddba->files; fil->fil_next; fil = fil->fil_next);
fil->fil_next =
(dba_fil*) ALLOC(sizeof(dba_fil) + strlen(file_name) + 1);
fil->fil_next->fil_min_page = fil->fil_max_page + 1;
fil = fil->fil_next;
}
else { /* empty list */
fil = tddba->files =
(dba_fil*) ALLOC(sizeof(dba_fil) + strlen(file_name) + 1);
fil->fil_min_page = 0L;
}
fil->fil_next = NULL;
strcpy(fil->fil_string, file_name);
fil->fil_length = strlen(file_name);
fil->fil_fudge = 0;
fil->fil_max_page = 0L;
if ((fil->fil_desc = open(file_name, 2)) == -1)
db_error(errno);
return fil;
}
static const pag* db_read( SLONG page_number)
{
/**************************************
*
* d b _ r e a d
*
**************************************
*
* Functional description
* Read a database page.
*
**************************************/
tdba* tddba = GET_THREAD_DATA;
dba_fil* fil;
for (fil = tddba->files;
page_number > fil->fil_max_page
&& fil->fil_next;)
{
fil = fil->fil_next;
}
page_number -= fil->fil_min_page - fil->fil_fudge;
const UINT64 offset = ((UINT64)page_number) * ((UINT64)tddba->page_size);
if (lseek (fil->fil_desc, offset, 0) == -1)
db_error(errno);
SSHORT length = tddba->page_size;
for (SCHAR* p = (SCHAR *) tddba->global_buffer; length > 0;)
{
const SSHORT l = read(fil->fil_desc, p, length);
if (l < 0)
db_error(errno);
if (!l) {
FPRINTF(tddba->sw_outfile, "Unexpected end of database file.\n");
dba_full_exit(FINI_ERROR, tddba);
}
p += l;
length -= l;
}
return tddba->global_buffer;
}
#endif
static bool key_equality( SSHORT length, const SCHAR* key, const btn* node)
{
/**************************************
*
* k e y _ e q u a l i t y
*
**************************************
*
* Functional description
* Check a B-tree node against a key for equality.
*
**************************************/
if (length != node->btn_length + node->btn_prefix)
return false;
SSHORT l = node->btn_length;
if (!l)
return true;
const SCHAR* p = (SCHAR *) node->btn_data;
key += node->btn_prefix;
do {
if (*p++ != *key++)
return false;
} while (--l);
return true;
}
static void move( const SCHAR* from, SCHAR* to, SSHORT length)
{
/**************************************
*
* m o v e
*
**************************************
*
* Functional description
* Move some stuff.
*
**************************************/
do {
*to++ = *from++;
} while (--length);
}
static void print_distribution( const SCHAR* prefix, const SLONG* vector)
{
/**************************************
*
* p r i n t _ d i s t r i b u t i o n
*
**************************************
*
* Functional description
* Print distribution as percentages.
*
**************************************/
tdba* tddba = GET_THREAD_DATA;
for (SSHORT n = 0; n < BUCKETS; n++) {
FPRINTF(tddba->sw_outfile, "%s%2d - %2d%% = %ld\n",
prefix,
n * 100 / BUCKETS, (n + 1) * 100 / BUCKETS - 1, vector[n]);
}
}
static void print_header( const header_page* header)
{
/**************************************
*
* p r i n t _ h e a d e r
*
**************************************
*
* Functional description
* Print database header page.
*
**************************************/
tdba* tddba = GET_THREAD_DATA;
IB_FILE* sw_outfile = tddba->sw_outfile;
FPRINTF(sw_outfile, "Database header page information:\n");
FPRINTF(sw_outfile, " Page size\t\t%d\n", header->hdr_page_size);
FPRINTF(sw_outfile, " ODS version\t\t%d.%d\n", header->hdr_ods_version,
header->hdr_ods_minor);
FPRINTF(sw_outfile, " Oldest transaction\t%ld\n",
header->hdr_oldest_transaction);
FPRINTF(sw_outfile, " Oldest active\t\t%ld\n",
header->hdr_oldest_active);
FPRINTF(sw_outfile, " Oldest snapshot\t\t%ld\n",
header->hdr_oldest_snapshot);
FPRINTF(sw_outfile, " Next transaction\t%ld\n",
header->hdr_next_transaction);
/*
FPRINTF (sw_outfile, " Sequence number %d\n", header->hdr_sequence);
FPRINTF (sw_outfile, " Creation date \n", header->hdr_creation_date);
*/
FPRINTF(sw_outfile, " Next attachment ID\t%ld\n",
header->hdr_attachment_id);
FPRINTF(sw_outfile, " Implementation ID\t%ld\n",
header->hdr_implementation);
FPRINTF(sw_outfile, " Shadow count\t%ld\n", header->hdr_shadow_count);
tm time;
isc_decode_date((ISC_QUAD*)header->hdr_creation_date, &time);
FPRINTF(sw_outfile, " Creation date\t%s %d, %d %d:%02d:%02d\n",
FB_SHORT_MONTHS[time.tm_mon], time.tm_mday, time.tm_year + 1900,
time.tm_hour, time.tm_min, time.tm_sec);
const SSHORT flags = header->hdr_flags;
if (flags) {
FPRINTF(sw_outfile, " Attributes\t\t");
if (flags & hdr_force_write) {
FPRINTF(sw_outfile, "force write");
if (flags & hdr_no_reserve)
FPRINTF(sw_outfile, ", ");
}
if (flags & hdr_no_reserve)
FPRINTF(sw_outfile, "no reserve");
FPRINTF(sw_outfile, "\n");
}
FPRINTF(sw_outfile, "\n Variable header data:\n");
const UCHAR* p = header->hdr_data;
for (const UCHAR* const end = p + header->hdr_page_size;
p < end && *p != HDR_end; p += 2 + p[1])
{
SLONG number;
switch (*p) {
case HDR_root_file_name:
FPRINTF(sw_outfile, "\tRoot file name: %*s\n", p[1], p + 2);
break;
case HDR_journal_server:
FPRINTF(sw_outfile, "\tJournal server: %*s\n", p[1], p + 2);
break;
case HDR_file:
FPRINTF(sw_outfile, "\tContinuation file: %*s\n", p[1], p + 2);
break;
case HDR_last_page:
move((SCHAR*) (p + 2), (SCHAR*) &number, sizeof(number));
FPRINTF(sw_outfile, "\tLast logical page: %ld\n", number);
break;
case HDR_unlicensed:
move((SCHAR*) (p + 2), (SCHAR*) &number, sizeof(number));
FPRINTF(sw_outfile, "\tUnlicensed accesses: %ld\n", number);
break;
case HDR_sweep_interval:
move((SCHAR*) (p + 2), (SCHAR*) &number, sizeof(number));
FPRINTF(sw_outfile, "\tSweep interval: %ld\n", number);
break;
case HDR_log_name:
FPRINTF(sw_outfile, "\tReplay logging file: %*s\n", p[1], p + 2);
break;
default:
FPRINTF(sw_outfile, "\tUnrecognized option %d, length %d\n", p[0],
p[1]);
}
}
FPRINTF(sw_outfile, "\t*END*\n");
}
// Warning: this function stops at the first blank.
static void truncate_name( SCHAR* string)
{
/**************************************
*
* t r u n c a t e _ n a m e
*
**************************************
*
* Functional description
* Zap trailing blanks.
*
**************************************/
for (; *string; ++string)
if (*string == ' ') {
*string = 0;
return;
}
}