/*
 *	PROGRAM:	DUDLEY (ddl compiler)
 *	MODULE:		extract.epp
 *	DESCRIPTION:	definition extract routines
 *
 * 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): ______________________________________.
 */

#include "firebird.h"
#include <stdio.h>
#include <string.h>
#include "../jrd/ibase.h"
#include "../dudley/ddl.h"
#include "../jrd/acl.h"
#include "../jrd/flags.h"
#include "../jrd/obj.h"
#include "../dudley/ddl_proto.h"
#include "../dudley/extra_proto.h"
#include "../jrd/gds_proto.h"
#include "../jrd/gdsassert.h"
#include "../common/classes/MsgPrint.h"

using MsgFormat::SafeArg;


DATABASE DB = EXTERN FILENAME "yachts.lnk" RUNTIME dudleyGlob.DB_file_name;

static const char* const FOPEN_WRITE_TYPE	= "w";

inline bool blob_null(const ISC_QUAD id)
{
	return !id.gds_quad_low && !id.gds_quad_high;
}

struct view {
	view *view_next;
	TEXT view_name[1];
};

static void binary_to_ascii(SLONG, SSHORT, TEXT *);
static void decompile_blr_literal(const SCHAR*);
static void extract_acls();
static void extract_computed(const TEXT*, bool);
static void extract_database(const TEXT*);
static void extract_fields();
static void extract_filters();
static void extract_functions();
static void extract_func_args(const TEXT *);
static void extract_generators();
static void extract_grants(const TEXT *);
static void extract_indexes();
static void extract_relations();
static void extract_rfr(const TEXT*);
static void extract_security();
static void extract_triggers();
static void extract_trig_msgs(const TEXT *);
static void extract_view(const TEXT*, ISC_QUAD*, ISC_QUAD*, bool, bool, bool, bool*);
static void extract_views();
static void field_attributes(ISC_QUAD*, const TEXT*, ISC_QUAD*, const TEXT*, SSHORT);
static void format_acl(const SCHAR *);
static char name_trunc(const TEXT *, TEXT *);
static void print_blob(ISC_QUAD*, TEXT);
static void put_comment(USHORT);
static void put_error(USHORT, const TEXT*);
static void set_capabilities();
static void type_scalar(USHORT, SSHORT, USHORT, SSHORT, USHORT, const TEXT*);
static void view_fields(const TEXT*);

#ifdef NOT_USED_OR_REPLACED
static void wal_info(const UCHAR*, SSHORT*, SLONG*, SLONG*, SLONG*);
#endif

static FILE* output_file;
static const SCHAR db_items[] = { isc_info_page_size };
static const SCHAR db_info[] = {
	isc_info_num_wal_buffers, isc_info_wal_buffer_size,
	isc_info_wal_ckpt_length, isc_info_wal_grpc_wait_usecs
};
static const TEXT acl_privs[] = "?CGDRWP???";

static SLONG EXT_capabilities;

enum EXT_flags {
	EXT_security = 1,
	EXT_files = 2,
	EXT_external = 4,
	EXT_idx_inactive = 8,
	EXT_triggers = 16,	// Obsolete - 1996-Aug-05
	EXT_context_name = 32,
	EXT_db_description = 64,
	EXT_v3 = 128,	// rdb$dependencies, rdb$functions, rdb$filters, rdb$user_privileges
					// rdb$triggers, rdb$types, rdb$indices.rdb$index_type
					// rdb$fields.rdb$dimensions, rdb$files.rdb$shadow_number
					// rdb$generators
	EXT_v4 = 256
};

/* table used to determine capabilities, checking for specific
   fields in system relations */

struct rfr_tab_t {
	const TEXT* relation;
	const TEXT* field;
	int bit_mask;
};

static const rfr_tab_t rfr_table[] = {
	{ "RDB$INDICES", "RDB$INDEX_INACTIVE", EXT_idx_inactive },
/* Obsolete - 1996-Aug-05 David Schnepper
	{ "RDB$RELATIONS",	"RDB$STORE_TRIGGER",	EXT_triggers },
*/
	{ "RDB$RELATIONS", "RDB$EXTERNAL_FILE", EXT_external },
	{ "RDB$SECURITY_CLASSES", "RDB$SECURITY_CLASS", EXT_security },
	{ "RDB$FILES", "RDB$FILE_NAME", EXT_files },
	{ "RDB$VIEW_RELATIONS", "RDB$CONTEXT_NAME", EXT_context_name },
	{ "RDB$DATABASE", "RDB$DESCRIPTION", EXT_db_description },
	{ "RDB$FUNCTIONS", "RDB$FUNCTION_NAME", EXT_v3 },
//	{ "RDB$LOG_FILES", "RDB$FILE_PARTITIONS", EXT_v4 },
	{ 0, 0, 0 }
};


void DDL_ext()
{
/**************************************
 *
 *	D D L _  e x t
 *
 **************************************
 *
 * Functional description
 *	Create an output text file and dump data
 *	definitions into it.
 *
 **************************************/

	output_file = NULL;

	if (*dudleyGlob.DDL_file_name) {
		if (!(output_file = fopen(dudleyGlob.DDL_file_name, FOPEN_WRITE_TYPE))) {
			DDL_err(248, SafeArg() << dudleyGlob.DDL_file_name);
			/* msg 248: ddl: can't open %s */
			DDL_exit(FINI_ERROR);
		}
	}
	else
		output_file = stdout;

	if (!(dudleyGlob.DDL_default_user || dudleyGlob.DDL_default_password))
	{
#ifdef TRUSTED_AUTH
		if (dudleyGlob.DDL_trusted)
		{
			char dpb[] = {isc_dpb_version1, isc_dpb_trusted_auth, 0};
			isc_attach_database (NULL, 0, dudleyGlob.DB_file_name, &DB, sizeof dpb, dpb);
		}
		else
#endif
			READY DB;
	}
	else
		READY DB USER dudleyGlob.DDL_default_user PASSWORD dudleyGlob.DDL_default_password;

/* the database version number is useful during extract - this goes
   to stdout, not to the output_file */
	if (dudleyGlob.DDL_version) {
		DDL_msg_put(249, SafeArg() << dudleyGlob.DB_file_name);	/* msg 249: Version(s) for database \"%s\" */
		isc_version(&DB, NULL, NULL);
	}

	START_TRANSACTION;

	set_capabilities();

	extract_database(dudleyGlob.DB_file_name);
	if (EXT_capabilities & EXT_v3)
		extract_functions();
	extract_fields();
	extract_relations();
	extract_views();
	extract_indexes();
	if (EXT_capabilities & EXT_security) {
		extract_security();
		extract_acls();
	}
	if (EXT_capabilities & EXT_v3) {
		extract_filters();
		extract_generators();
		extract_triggers();
	}

	fprintf(output_file, "\n");
	fclose(output_file);
	COMMIT;
	FINISH;
}


static void binary_to_ascii(SLONG value, SSHORT scale, TEXT * buffer)
{
/**************************************
 *
 *	b i n a r y _ t o _ a s c i i
 *
 **************************************
 *
 * Functional description
 *	Convert a binary number to ascii and
 *	return it in the user supplied buffer;
 *
 **************************************/
	TEXT temp[32];

	TEXT *p = buffer;

/* Handle non-negative scale factor in straightforward manner */

	if (scale >= 0) {
		sprintf(p, "%"SLONGFORMAT, value);
		while (*p)
			p++;
		if (scale)
		{
			do
			{
				*p++ = '0';
			} while (--scale);
		}
		return;
	}

	if (value < 0) {
		*p++ = '-';
		value = -value;
	}

	TEXT* q;
	for (q = temp; value || scale < 0;) {
		*q++ = value % 10 + '0';
		value /= 10;
		if (!++scale) {
			*q++ = '.';
			if (!value)
				*q++ = '0';
		}
	}

	while (q > temp)
		*p++ = *--q;

	*p = 0;
}


static void decompile_blr_literal(const SCHAR* string)
{
/**************************************
 *
 *	d e c o m p i l e _ b l r _ l i t e r a l
 *
 **************************************
 *
 * Functional description
 *	decompile a blr literal
 *	and write it to the output file.
 *
 **************************************/
	SCHAR scale;
	TEXT buffer[256];
	SSHORT size;
	SLONG value;

	const UCHAR* p = reinterpret_cast<const UCHAR*>(string);

	if ((*p++ != blr_version4) || (*p++ != blr_literal)) {
		put_comment(250);		/* msg 250: **** unable to decompile missing value *** */
		return;
	}

	static const SafeArg dummy;

	TEXT* b = buffer;

	switch (*p++)
	{
	case blr_text:
		size = gds__vax_integer(p, 2);
		p += 2;
		*b++ = '"';
		while (size--)
			*b++ = *p++;
		*b++ = '"';
		*b = 0;
		break;

	case blr_long:
		scale = *p++;
		value = gds__vax_integer(p, 4);
		binary_to_ascii(value, scale, buffer);
		break;

	case blr_short:
		scale = *p++;
		value = gds__vax_integer(p, 2);
		binary_to_ascii(value, scale, buffer);
		break;

	default:
		fb_msg_format(NULL, DDL_MSG_FAC, 250, sizeof(buffer), buffer, dummy);
		/* msg 250: **** unable to decompile missing value *** */
	}

	fprintf(output_file, "%s", buffer);
}


static void extract_acls()
{
/**************************************
 *
 *	e x t r a c t _ a c l s
 *
 **************************************
 *
 * Functional description
 *	Read security class records and
 *	and write them to the output file.
 *	For each security class record determine if there is
 *	any GRANT user privileges associated with it.  If so,
 *	extract GRANT statement rather than a define security class
 *	statement.
 *
 **************************************/
	TEXT s[32];
	const TEXT* relname = NULL;

	bool first = true;

    FOR S IN RDB$SECURITY_CLASSES
        if (first) {
            put_comment(251);       /* msg 251: \n\n\tSecurity Class Definitions / GRANT statements\t */
            first = false;
        }
	    USHORT count = 0;

	    /* if there's any rdb$user_privileges entry then we can use GRANT statement */
	    /* resort to parsing rdb$security_class for SQL$<relation-name> instead of
	       just checking for rdb$security_classes.rdb$relation_name since it's
	       possible that the security class was used on some other relation than
	       the one GRANTed ON */

		// CVC: it's not clear how this works. Any relation with len(name) > 27
		// will have its name truncated and any pair of relations whose first 27
		// characters are the same will have a conflict, hence the latest to be
		// defined or changed WRT rights will stomp over the other's settings, so
		// both relations become tied to the same security class.

	    if (EXT_capabilities & EXT_v3) {
	        if (strncmp(S.RDB$SECURITY_CLASS, "SQL$", 4) == 0) {
	            strcpy(s, S.RDB$SECURITY_CLASS);
	            relname = &s[4];
	            if (EXT_capabilities & EXT_v4) {
	                FOR U IN RDB$USER_PRIVILEGES WITH
	                    U.RDB$RELATION_NAME = relname AND
	                    U.RDB$OBJECT_TYPE = obj_relation
	                    count++;
	                END_FOR;
	            }
	            else {
	                FOR U IN RDB$USER_PRIVILEGES WITH U.RDB$RELATION_NAME = relname
	                    count++;
	                END_FOR;
	            }

	            /* if there's no rdb$user_privileges entry, but we still find a
	               SQL$ security class, that's the relation owner's privilege list.
	               Ignore this as the new database created will have a new owner
	               and should start fresh with no security class defined for
	               owner */

	            if (!count)
	                continue;
	        }
	    }
	    if (count)
	        extract_grants(relname);
	    else {
	        fprintf(output_file, "\ndefine security_class %s",
	                   S.RDB$SECURITY_CLASS);
	        if (!blob_null(S.RDB$DESCRIPTION)) {
	            fprintf(output_file, "\n\t{");
	            print_blob(&S.RDB$DESCRIPTION, 'u');
	            fprintf(output_file, "}");
	        }
	        if (!blob_null(S.RDB$ACL))
	            print_blob(&S.RDB$ACL, 'a');
	        fprintf(output_file, ";\n");
	    }
    END_FOR;
}


static void extract_computed(const TEXT * relation_name, bool first)
{
/**************************************
 *
 *	e x t r a c t _ c o m p u t e d
 *
 **************************************
 *
 * Functional description
 *	Read definitions of computed fields
 *	and write them to the output file.
 *
 *	Check on the way by that we're not
 *	trying to expand a computed field
 *	that's been copied whole into a
 *	view.  Meaning that if there is a
 *	view context, skip this field
 *
 **************************************/
	TEXT s[32];

    FOR RFR IN RDB$RELATION_FIELDS CROSS F IN RDB$FIELDS
        WITH RFR.RDB$RELATION_NAME = relation_name
        AND F.RDB$FIELD_NAME = RFR.RDB$FIELD_SOURCE
        AND F.RDB$COMPUTED_BLR NOT MISSING
        SORTED BY RFR.RDB$FIELD_POSITION

        if (RFR.RDB$VIEW_CONTEXT)
            continue;

        if (!first)
            fprintf(output_file, ",\n");

        first = false;
        name_trunc(RFR.RDB$FIELD_NAME, s);
        if (F.RDB$COMPUTED_SOURCE.NULL) {
            put_error(252, s);      /* msg 252: **** field %s can not be extracted, computed source missing ***  */
            continue;
        }
        fprintf(output_file, "    %s", s);

        strcpy(s, F.RDB$FIELD_NAME);
        if (EXT_capabilities & EXT_v3) {
            FOR FNDA IN RDB$FIELDS WITH FNDA.RDB$FIELD_NAME = s
                type_scalar(FNDA.RDB$FIELD_TYPE, FNDA.RDB$FIELD_SCALE,
	                       FNDA.RDB$FIELD_LENGTH,
	                       FNDA.RDB$FIELD_SUB_TYPE, FNDA.RDB$DIMENSIONS, s);
	       END_FOR;
        }
        else {
            FOR FNDA IN RDB$FIELDS WITH FNDA.RDB$FIELD_NAME = s
                type_scalar(FNDA.RDB$FIELD_TYPE, FNDA.RDB$FIELD_SCALE,
                            FNDA.RDB$FIELD_LENGTH,
                            FNDA.RDB$FIELD_SUB_TYPE, 0, NULL);
            END_FOR;
        }
        fprintf(output_file, " computed by (");

        print_blob(&F.RDB$COMPUTED_SOURCE, 'u');
        fprintf(output_file, ")");
        if (!RFR.RDB$FIELD_POSITION.NULL)
            fprintf(output_file, "\tposition %d", RFR.RDB$FIELD_POSITION);
        field_attributes(&RFR.RDB$DESCRIPTION, RFR.RDB$QUERY_NAME,
                         &RFR.RDB$QUERY_HEADER, RFR.RDB$EDIT_STRING,
                         RFR.RDB$SYSTEM_FLAG);

    END_FOR;
}


static void extract_database(const TEXT * DB_file_nameL)
{
/**************************************
 *
 *	e x t r a c t _ d a t a b a s e
 *
 **************************************
 *
 * Functional description
 *	Get the essential database information
 *	and write it to the output file.
 *
 **************************************/
	//const int OneK = 1024;
	TEXT s[32], buffer[256];
	SLONG page_size;
	ISC_STATUS_ARRAY status_vector;

	fprintf(output_file, "define database \"%s\"", DB_file_nameL);

	if (EXT_capabilities & EXT_security) {
	       FOR D IN RDB$DATABASE
               if (!blob_null(D.RDB$DESCRIPTION)) {
                   fprintf(output_file, "\n\t{");
                   print_blob(&D.RDB$DESCRIPTION, 'u');
                   fprintf(output_file, "}");
               }
               if (!D.RDB$SECURITY_CLASS.NULL) {
                   name_trunc(D.RDB$SECURITY_CLASS, s);
                   if (*s)
                       fprintf(output_file, "\n\tsecurity_class %s", s);
               }
	       END_FOR;
	}
	else if (EXT_capabilities & EXT_db_description)
        FOR D IN RDB$DATABASE
            if (!blob_null(D.RDB$DESCRIPTION)) {
                fprintf(output_file, "\n\t{");
                print_blob(&D.RDB$DESCRIPTION, 'u');
                fprintf(output_file, "}");
            }
        END_FOR;

		if (isc_database_info(status_vector, &DB, sizeof(db_items),
							   db_items, sizeof(buffer), buffer)) {
			put_error(254, 0);
			/* msg 254: ***isc_database_info failed*** */
			gds__print_status(status_vector);
		}

        TEXT* d = buffer;
        if (*d++ == isc_info_page_size) {
            SSHORT length = gds__vax_integer(reinterpret_cast<const UCHAR*>(d), 2);
            d += 2;
            page_size = gds__vax_integer(reinterpret_cast<const UCHAR*>(d), length);
            if (page_size)
                fprintf(output_file, "\n\tpage_size %"SLONGFORMAT, page_size);
        }

        if (EXT_capabilities & EXT_files)
		{
            if (EXT_capabilities & EXT_v3) {
                FOR F IN RDB$FILES WITH F.RDB$SHADOW_NUMBER MISSING OR
                    F.RDB$SHADOW_NUMBER EQ 0
                    name_trunc(F.RDB$FILE_NAME, buffer);
                    fprintf(output_file, "\n\tfile \"%s\" starting at page %"SLONGFORMAT,
                               buffer, F.RDB$FILE_START);
                END_FOR;
            }
            else {
                FOR F IN RDB$FILES
					name_trunc(F.RDB$FILE_NAME, buffer);
                    fprintf(output_file, "\n\tfile \"%s\" starting at page %"SLONGFORMAT,
                               buffer, F.RDB$FILE_START);
                END_FOR;
			}
		}

/* print log info. */

#ifdef NOT_USED_OR_REPLACED
// code commented in addition to ifdef to avoid warnings during compilation
/*
		if (EXT_capabilities & EXT_v4) {
			bool first_time = true;
			bool need_close = false;

	       FOR F IN RDB$LOG_FILES
	       if (F.RDB$FILE_FLAGS & LOG_overflow)
	             continue;

	       if (first_time) {
	           first_time = false;
	           fprintf(output_file, "\n\tlogfile");

	           if (F.RDB$FILE_FLAGS & LOG_default)
	               break;
	           else if (F.RDB$FILE_FLAGS & LOG_serial)
	               fprintf(output_file, " base_name");
	           else {
	               need_close = true;
	               fprintf(output_file, "\n\t\t(");
	           }

	           fprintf(output_file, "\n\t\t\"%s\"", F.RDB$FILE_NAME);
	       }
	       else
	           fprintf(output_file, ",\n\t\t\"%s\"", F.RDB$FILE_NAME);

	       if (F.RDB$FILE_LENGTH)
	           fprintf(output_file, " size = %"SLONGFORMAT, F.RDB$FILE_LENGTH);

	       if (F.RDB$FILE_PARTITIONS)
	           fprintf(output_file, " partitions = %d",
	                      F.RDB$FILE_PARTITIONS);
	       if (F.RDB$FILE_FLAGS & LOG_raw)
	           fprintf(output_file, " raw");

	       END_FOR;

			if (need_close)
				fprintf(output_file, "\n\t\t)");

	       FOR F IN RDB$LOG_FILES
		   if (!(F.RDB$FILE_FLAGS & LOG_overflow))
	             continue;

	       fprintf(output_file, "\n\toverflow");
	       fprintf(output_file, " \"%s\"", F.RDB$FILE_NAME);

	       if (F.RDB$FILE_LENGTH)
	           fprintf(output_file, " size = %"SLONGFORMAT, F.RDB$FILE_LENGTH);

	       if (F.RDB$FILE_PARTITIONS)
	           fprintf(output_file, " partitions = %d",
	                      F.RDB$FILE_PARTITIONS);
	       END_FOR;

			SCHAR db_info_buffer[64];
			if (isc_database_info(gds_status, &DB, sizeof(db_info), db_info,
								   sizeof(db_info_buffer), db_info_buffer)) {
				put_error(254, 0);	// msg 254: ***isc_database_info failed***
				gds__print_status(status_vector);
			}

            SSHORT num_buf = 0;
			SLONG buf_size = 0, chkpt_len = 0, grp_commit= 0;
			wal_info(reinterpret_cast<const UCHAR*>(db_info_buffer), &num_buf,
					&buf_size, &chkpt_len, &grp_commit);

			if (num_buf)
				fprintf(output_file, "\n\tnum_log_buffers %d", num_buf);
			if (buf_size)
				fprintf(output_file, "\n\tlog_buffer_size %"SLONGFORMAT, buf_size);
			if (chkpt_len)
				fprintf(output_file, "\n\tcheck_point_length %"SLONGFORMAT,
						   chkpt_len / OneK);
			if (grp_commit)
				fprintf(output_file, "\n\tgroup_commit_wait_time %"SLONGFORMAT,
						   grp_commit);
		}
 */
#endif

	fprintf(output_file, ";\n");

	if (EXT_capabilities & EXT_v3) {
		bool any_shadows = false;

			FOR F IN RDB$FILES WITH F.RDB$SHADOW_NUMBER GT 0
				SORTED BY F.RDB$SHADOW_NUMBER, F.RDB$FILE_START
				name_trunc(F.RDB$FILE_NAME, buffer);
				if (F.RDB$FILE_START)
					fprintf(output_file, "\n\tfile \"%s\" starting at page %"SLONGFORMAT,
							   buffer, F.RDB$FILE_START);
				else
					fprintf(output_file, "%s\ndefine shadow %d \"%s\"",
							   any_shadows ? ";\n" : "",
							   F.RDB$SHADOW_NUMBER,
							   buffer);
				any_shadows = true;
			END_FOR;

		if (any_shadows)
			fprintf(output_file, ";\n");
	}
}


static void extract_fields()
{
/**************************************
 *
 *	e x t r a c t _ f i e l d s
 *
 **************************************
 *
 * Functional description
 *	Read the global field definitions
 *	and write them to the output file.
 *
 **************************************/
	TEXT s[32];

	bool first = true;
    FOR F IN RDB$FIELDS WITH
        (F.RDB$SYSTEM_FLAG NE 1 OR F.RDB$SYSTEM_FLAG MISSING) AND
        F.RDB$COMPUTED_BLR MISSING SORTED BY F.RDB$FIELD_NAME

        if (first) {
            put_comment(255);       /*msg 255: \n\n\tGlobal Field Definitions\t */
            first = false;
        }
        name_trunc(F.RDB$FIELD_NAME, s);
        fprintf(output_file, "define field %s", s);
        if (F.RDB$FIELD_TYPE == blr_blob) {
            fprintf(output_file, " blob");
            if (F.RDB$SEGMENT_LENGTH)
                fprintf(output_file, " segment_length %u",
                           (USHORT) F.RDB$SEGMENT_LENGTH);
            switch (F.RDB$FIELD_SUB_TYPE)
            {
            case 1:
                fprintf(output_file, " sub_type text");
                break;
            case 2:
                fprintf(output_file, " sub_type BLR");
                break;
            case 3:
                fprintf(output_file, " sub_type ACL");
                break;
            default:
            	if (F.RDB$FIELD_SUB_TYPE)
                	fprintf(output_file, " sub_type %d", F.RDB$FIELD_SUB_TYPE);
                break;
            }
        }
        else {
            strcpy(s, F.RDB$FIELD_NAME);
            if (EXT_capabilities & EXT_v3) {
                FOR FNDA IN RDB$FIELDS WITH FNDA.RDB$FIELD_NAME = s
                    type_scalar(FNDA.RDB$FIELD_TYPE, FNDA.RDB$FIELD_SCALE,
                                FNDA.RDB$FIELD_LENGTH,
                                FNDA.RDB$FIELD_SUB_TYPE, FNDA.RDB$DIMENSIONS, s);
                END_FOR;
            }
            else {
                FOR FNDA IN RDB$FIELDS WITH FNDA.RDB$FIELD_NAME = s
                    type_scalar(FNDA.RDB$FIELD_TYPE, FNDA.RDB$FIELD_SCALE,
                                FNDA.RDB$FIELD_LENGTH,
                                FNDA.RDB$FIELD_SUB_TYPE, 0, NULL);
                END_FOR;
            }

        }

        if (!blob_null(F.RDB$VALIDATION_SOURCE)) {
            fprintf(output_file, "\n\tvalid if (");
            print_blob(&F.RDB$VALIDATION_SOURCE, 'u');
            fprintf(output_file, ")");
        }

        if (!blob_null(F.RDB$MISSING_VALUE)) {
            fprintf(output_file, "\n\tmissing_value is ");
            print_blob(&F.RDB$MISSING_VALUE, 'm');
        }

        /*
          if (!blob_null (F.RDB$DEFAULT_VALUE))
          default_value (&F.RDB$DEFAULT_VALUE);
        */

        field_attributes(&F.RDB$DESCRIPTION, F.RDB$QUERY_NAME,
                         &F.RDB$QUERY_HEADER, F.RDB$EDIT_STRING,
                         F.RDB$SYSTEM_FLAG);
        fprintf(output_file, ";\n");

    END_FOR;
}


static void extract_filters()
{
/**************************************
 *
 *	e x t r a c t _ f i l t e r s
 *
 **************************************
 *
 * Functional description
 *	Read filter definitions
 *	and write them to the output file.
 *
 **************************************/
	TEXT s[32];

	bool first = true;

	FOR F IN RDB$FILTERS SORTED BY F.RDB$FUNCTION_NAME
		if (first) {
		   put_comment(256);       /* msg 256: \n\n\tFilter Definitions\t */
		   first = false;
		}
		name_trunc(F.RDB$FUNCTION_NAME, s);
		fprintf(output_file, "\ndefine filter %s", s);
		fprintf(output_file, "\n\tinput_type %d", F.RDB$INPUT_SUB_TYPE);
		fprintf(output_file, "\n\toutput_type %d", F.RDB$OUTPUT_SUB_TYPE);
		if (!F.RDB$MODULE_NAME.NULL && name_trunc(F.RDB$MODULE_NAME, s))
		   fprintf(output_file, "\n\tmodule_name '%s'", s);
		name_trunc(F.RDB$ENTRYPOINT, s);
		fprintf(output_file, "\n\tentry_point '%s'", s);
		if (!blob_null(F.RDB$DESCRIPTION)) {
		   fprintf(output_file, "\n\t{");
		   print_blob(&F.RDB$DESCRIPTION, 'u');
		   fprintf(output_file, "}");
		}
		fprintf(output_file, ";\n");
	END_FOR;
}


static void extract_functions()
{
/**************************************
 *
 *	e x t r a c t _ f u n c t i o n s
 *
 **************************************
 *
 * Functional description
 *	Read function definitions
 *	and write them to the output file.
 *
 **************************************/
	TEXT s[32];

	bool first = true;

	FOR FUNC IN RDB$FUNCTIONS SORTED BY FUNC.RDB$FUNCTION_NAME
		if (first) {
		   put_comment(257);       /* msg 257: \n\n\tFunction Definitions\t */
		   first = false;
		}
		name_trunc(FUNC.RDB$FUNCTION_NAME, s);
		fprintf(output_file, "\ndefine function %s", s);
		if (!FUNC.RDB$QUERY_NAME.NULL && name_trunc(FUNC.RDB$QUERY_NAME, s))
			fprintf(output_file, "\n\tquery_name %s", s);
		if (!FUNC.RDB$MODULE_NAME.NULL && name_trunc(FUNC.RDB$MODULE_NAME, s))
			fprintf(output_file, "\n\tmodule_name '%s'", s);
		name_trunc(FUNC.RDB$ENTRYPOINT, s);
		fprintf(output_file, "\n\tentry_point '%s'", s);
		if (!blob_null(FUNC.RDB$DESCRIPTION)) {
			fprintf(output_file, "\n\t{");
			print_blob(&FUNC.RDB$DESCRIPTION, 'u');
			fprintf(output_file, "}");
		}

		extract_func_args(FUNC.RDB$FUNCTION_NAME);

		fprintf(output_file, ";\n");
	END_FOR;
}


static void extract_func_args(const TEXT * function_name)
{
/**************************************
 *
 *	e x t r a c t _ f u n c _ a r g s
 *
 **************************************
 *
 * Functional description
 *	Read the argument descriptions for a function
 *	and write them to the output file.
 *
 **************************************/
	bool first = true;
	const TEXT* p = "";

    FOR FARG IN RDB$FUNCTION_ARGUMENTS
        WITH FARG.RDB$FUNCTION_NAME = function_name
        SORTED BY FARG.RDB$ARGUMENT_POSITION
        fprintf(output_file, "%s\n", p);

        if (first) {
            p = ",";
            first = false;
        }

        fprintf(output_file, "\t\t");
        if (FARG.RDB$FIELD_TYPE == blr_blob)
            fprintf(output_file, " blob");
        else {
            type_scalar(FARG.RDB$FIELD_TYPE, FARG.RDB$FIELD_SCALE,
                        FARG.RDB$FIELD_LENGTH, FARG.RDB$FIELD_SUB_TYPE, 0, NULL);
        }
        fprintf(output_file, " by ");
        if (FARG.RDB$MECHANISM == FUNCARG_mechanism_value)
            fprintf(output_file, "value");
        else if ((FARG.RDB$MECHANISM == FUNCARG_mechanism_reference) ||
                 (FARG.RDB$MECHANISM == FUNCARG_mechanism_blob_struc))
            fprintf(output_file, "reference");
        else if (FARG.RDB$MECHANISM == FUNCARG_mechanism_sc_array_desc)
            fprintf(output_file, "scalar_array_descriptor");

        FOR FUNC IN RDB$FUNCTIONS
            WITH FUNC.RDB$FUNCTION_NAME = function_name
            if (FARG.RDB$ARGUMENT_POSITION == FUNC.RDB$RETURN_ARGUMENT) {
                if (FARG.RDB$ARGUMENT_POSITION)
                fprintf(output_file, " return_argument");
                else
                fprintf(output_file, " return_value");
            }
        END_FOR;

    END_FOR;
}


static void extract_generators()
{
/**************************************
 *
 *	e x t r a c t _ g e n e r a t o r s
 *
 **************************************
 *
 * Functional description
 *	Get generator definitions from rdb$generators.
 *
 **************************************/
	TEXT s[32];

	bool first = true;

    FOR G IN RDB$GENERATORS WITH
        (G.RDB$SYSTEM_FLAG NE 1 OR G.RDB$SYSTEM_FLAG MISSING)
        SORTED BY G.RDB$GENERATOR_NAME
        if (first) {
            put_comment(290);       /* msg 290: \n\n\tGenerator Definitions\t */
            first = false;
        }
        name_trunc(G.RDB$GENERATOR_NAME, s);
        fprintf(output_file, "\ndefine generator %s;", s);
    END_FOR;
}


static void extract_grants(const TEXT * relname)
{
/**************************************
 *
 *	e x t r a c t _ g r a n t s
 *
 **************************************
 *
 * Functional description
 *	Build as many GRANT statements as needed
 *	for the relation from reading user privilge records.
 *
 **************************************/
	TEXT relation_name[32], user_name[32], field_name[32];
	const char* user_priv;

	if (EXT_capabilities & EXT_v4) {
        FOR U IN RDB$USER_PRIVILEGES WITH U.RDB$RELATION_NAME EQ relname
            AND U.RDB$OBJECT_TYPE EQ obj_relation
            SORTED BY U.RDB$USER, U.RDB$PRIVILEGE

            switch (U.RDB$PRIVILEGE[0]) {
            case 'S':
                user_priv = "SELECT";
                break;

            case 'U':
                user_priv = "UPDATE";
                break;

            case 'D':
                user_priv = "DELETE";
                break;

            case 'I':
	           user_priv = "INSERT";
	           break;

            case 'R':
                /* None of the interfaces knows about the references privilege.
                   Ignore it for now. */

                continue;

	        default:
	           user_priv = "**unknown**";
	           break;
            }

            fprintf(output_file, "\ngrant %s ", user_priv);
            if (!U.RDB$FIELD_NAME.NULL) {
                name_trunc(U.RDB$FIELD_NAME, field_name);
                fprintf(output_file, "(%s) ", field_name);
            }
            name_trunc(U.RDB$RELATION_NAME, relation_name);
            name_trunc(U.RDB$USER, user_name);
            fprintf(output_file, "on %s to %s", relation_name, user_name);
            if (U.RDB$GRANT_OPTION)
                fprintf(output_file, " with grant option");
            fprintf(output_file, ";\n");

        END_FOR;
	}
	else {
        FOR U IN RDB$USER_PRIVILEGES WITH U.RDB$RELATION_NAME EQ relname
            SORTED BY U.RDB$USER, U.RDB$PRIVILEGE switch (U.RDB$PRIVILEGE[0]) {
            case 'S':
                user_priv = "SELECT";
                break;

            case 'U':
                user_priv = "UPDATE";
                break;

            case 'D':
                user_priv = "DELETE";
                break;

            case 'I':
                user_priv = "INSERT";
                break;

            case 'R':
                /* None of the interfaces knows about the references privilege.
                   Ignore it for now. */

                continue;

            default:
                user_priv = "**unknown**";
                break;
            }

            fprintf(output_file, "\ngrant %s ", user_priv);
            if (!U.RDB$FIELD_NAME.NULL) {
                name_trunc(U.RDB$FIELD_NAME, field_name);
                fprintf(output_file, "(%s) ", field_name);
            }
            name_trunc(U.RDB$RELATION_NAME, relation_name);
            name_trunc(U.RDB$USER, user_name);
            fprintf(output_file, "on %s to %s", relation_name, user_name);
            if (U.RDB$GRANT_OPTION)
                fprintf(output_file, " with grant option");
            fprintf(output_file, ";\n");

	    END_FOR;
	}
}


static void extract_indexes()
{
/**************************************
 *
 *	e x t r a c t _ i n d e x e s
 *
 **************************************
 *
 * Functional description
 *	Read definitions of indexes
 *	and write them to the output file.
 *
 **************************************/
	TEXT index[32], relation[32], field[32];
	const TEXT* p = "";
	bool first = true;

/* this query gets all IDX which donot have the systemflag set and
   further sorts them by realtion name and index name */
    FOR I IN RDB$INDICES CROSS R IN RDB$RELATIONS
        OVER RDB$RELATION_NAME
        WITH(R.RDB$SYSTEM_FLAG NE 1 OR R.RDB$SYSTEM_FLAG MISSING)
        SORTED BY I.RDB$RELATION_NAME, I.RDB$INDEX_NAME
        if (first) {
            put_comment(291);       /* msg 291: \n\n\tIndex Definitions\t */
            first = false;
        }
        if (!I.RDB$UNIQUE_FLAG.NULL && I.RDB$UNIQUE_FLAG == 1)
            p = "unique";
        else
            p = "";
        name_trunc(I.RDB$INDEX_NAME, index);
        name_trunc(I.RDB$RELATION_NAME, relation);
        fprintf(output_file, "\ndefine index %s for %s %s",
                   index, relation, p);

        if (!blob_null(I.RDB$DESCRIPTION)) {
            fprintf(output_file, "\n\t{");
               print_blob(&I.RDB$DESCRIPTION, 'u');
               fprintf(output_file, "}");
        }

        if (EXT_capabilities & EXT_idx_inactive)
            FOR ACT IN RDB$INDICES WITH ACT.RDB$INDEX_NAME = index
                if (ACT.RDB$INDEX_INACTIVE)
                    fprintf(output_file, "\ninactive");
        END_FOR;

        if (EXT_capabilities & EXT_v3)
            FOR ACT IN RDB$INDICES WITH ACT.RDB$INDEX_NAME = index
                   if (ACT.RDB$INDEX_TYPE)
                       fprintf(output_file, "\ndescending");
        END_FOR;


        p = "";

        FOR I_S IN RDB$INDEX_SEGMENTS WITH
            I_S.RDB$INDEX_NAME = I.RDB$INDEX_NAME
            SORTED BY I_S.RDB$FIELD_POSITION
            name_trunc(I_S.RDB$FIELD_NAME, field);
            fprintf(output_file, "%s\n\t%s", p, field);
            p = ",";

        END_FOR;

        fprintf(output_file, ";\n");
    END_FOR;
}


static void extract_relations()
{
/**************************************
 *
 *	e x t r a c t _ r e l a t i o n s
 *
 **************************************
 *
 * Functional description
 *	Read relation  definitions
 *	and write them to the output file.
 *
 **************************************/
	TEXT s[256];

	bool first = true;

	if (EXT_capabilities & EXT_external) {
        FOR R IN RDB$RELATIONS WITH
            (R.RDB$SYSTEM_FLAG NE 1 OR R.RDB$SYSTEM_FLAG MISSING) AND
            R.RDB$VIEW_BLR MISSING SORTED BY R.RDB$RELATION_NAME
            if (first) {
                put_comment(258);   /* msg 258: \n\n\tRelation Definitions\t */
                first = false;
            }
            name_trunc(R.RDB$RELATION_NAME, s);
            fprintf(output_file, "\ndefine relation %s", s);

            if (!R.RDB$EXTERNAL_FILE.NULL && name_trunc(R.RDB$EXTERNAL_FILE, s))
                fprintf(output_file, "\n\texternal_file \"%s\"", s);

            if (R.RDB$SYSTEM_FLAG)
                fprintf(output_file, "\n\tsystem_flag %d", R.RDB$SYSTEM_FLAG);
            if (!blob_null(R.RDB$DESCRIPTION)) {
                fprintf(output_file, "\n\t{");
                print_blob(&R.RDB$DESCRIPTION, 'u');
                fprintf(output_file, "}");
            }
            extract_rfr(R.RDB$RELATION_NAME);
            fprintf(output_file, ";\n");
        END_FOR;
	}
	else {
        FOR R IN RDB$RELATIONS WITH
            (R.RDB$SYSTEM_FLAG NE 1 OR R.RDB$SYSTEM_FLAG MISSING) AND
            R.RDB$VIEW_BLR MISSING SORTED BY R.RDB$RELATION_NAME
            if (first) {
                put_comment(258);   /* msg 258: \n\n\tRelation Definitions\t */
                first = false;
            }
            name_trunc(R.RDB$RELATION_NAME, s);
            fprintf(output_file, "\ndefine relation %s", s);

            if (R.RDB$SYSTEM_FLAG)
                fprintf(output_file, "\n\tsystem_flag %d", R.RDB$SYSTEM_FLAG);
            if (!blob_null(R.RDB$DESCRIPTION)) {
                fprintf(output_file, "\n\t{");
                print_blob(&R.RDB$DESCRIPTION, 'u');
                fprintf(output_file, "}");
            }
            extract_rfr(R.RDB$RELATION_NAME);
            fprintf(output_file, ";\n");
        END_FOR;
	}

}


static void extract_rfr(const TEXT * relation_name)
{
/**************************************
 *
 *	e x t r a c t _ r f r
 *
 **************************************
 *
 * Functional description
 *	Read the field descriptions for a relation
 *	and write them to the output file.
 *
 **************************************/
	TEXT s[32];

	bool first = true;
	const TEXT* p = "";

	FOR RFR IN RDB$RELATION_FIELDS CROSS F IN RDB$FIELDS
        WITH RFR.RDB$RELATION_NAME = relation_name
        AND F.RDB$FIELD_NAME = RFR.RDB$FIELD_SOURCE
        AND F.RDB$COMPUTED_BLR MISSING
        SORTED BY RFR.RDB$FIELD_POSITION

        fprintf(output_file, "%s\n", p);
        if (first) {
            p = ",";
            first = false;
        }

        name_trunc(RFR.RDB$FIELD_NAME, s);
        fprintf(output_file, "    %s", s);
        if (strcmp(RFR.RDB$FIELD_NAME, RFR.RDB$FIELD_SOURCE)) {
            name_trunc(RFR.RDB$FIELD_SOURCE, s);
            fprintf(output_file, " based on %s", s);
        }

        if (!RFR.RDB$FIELD_POSITION.NULL)
            fprintf(output_file, "\tposition %d", RFR.RDB$FIELD_POSITION);

        field_attributes(&RFR.RDB$DESCRIPTION, RFR.RDB$QUERY_NAME,
                         &RFR.RDB$QUERY_HEADER, RFR.RDB$EDIT_STRING,
                         RFR.RDB$SYSTEM_FLAG);
    END_FOR;

	extract_computed(relation_name, first);
}


static void extract_security()
{
/**************************************
 *
 *	e x t r a c t _ s e c u r i t y
 *
 **************************************
 *
 * Functional description
 *	Get security class information for
 *	relations, views, and fields.
 *
 **************************************/
	TEXT r[32], s[32], f[32], rel1[32], rel2[32];

	bool first = true;

    FOR R IN RDB$RELATIONS
		bool first_field = true;
		bool modify = false;
		name_trunc(R.RDB$RELATION_NAME, r);
		const TEXT* p = (R.RDB$VIEW_BLR.NULL) ? "relation" : "view";
		if (!R.RDB$SECURITY_CLASS.NULL && name_trunc(R.RDB$SECURITY_CLASS, s))
		{
			/* skip modify relation/view add security class if we have just a stub -
			   i.e. security_class is 'SQL$..' and has no rdb$user_privilege entry and
			   relation_name matches 'SQL$relation_name'  */
			/* doesn't hurt to extract even if redundant with GRANT statement - i.e.
			   security_class is 'SQL$..' and has rdb$user_privilege entries */
			/* extract if security_class is 'SQL$..' but relation_name doesn't match
			   'SQL$relation_name' */
			/* extract if security_class doesn't start with 'SQL$' */

			if (EXT_capabilities & EXT_v3) {
				if (strncmp(R.RDB$SECURITY_CLASS, "SQL$", 4) == 0) {
					int count = 0;
					name_trunc(&R.RDB$SECURITY_CLASS[4], rel1);
					name_trunc(R.RDB$RELATION_NAME, rel2);
					if (strcmp(rel1, rel2) == 0) {
						FOR U IN RDB$USER_PRIVILEGES
						WITH U.RDB$RELATION_NAME EQ R.RDB$RELATION_NAME
							count++;
						END_FOR;
					}
					else
						count = 1;
					if (!count)
						continue;
				}
			}

            if (first) {
	           put_comment(259);   /* msg 259: \n\tAdd Security Classes to Defined Objects\t\n  */
	           first = false;
            }

	       fprintf(output_file, "\nmodify %s %s\n\tsecurity_class %s", p, r,
	                  s);
	       modify = true;
        }

		FOR RFR IN RDB$RELATION_FIELDS
			WITH RFR.RDB$RELATION_NAME EQ R.RDB$RELATION_NAME AND
			RFR.RDB$SECURITY_CLASS NOT MISSING
			name_trunc(RFR.RDB$FIELD_NAME, f);
			if (name_trunc(RFR.RDB$SECURITY_CLASS, s)) {
				if (first) {
			    	put_comment(259);   /* msg 259: \n\tAdd Security Classes to Defined Objects\t\n  */
					first = false;
				}

				if (!modify) {
					fprintf(output_file, "\nmodify %s %s", p, r);
					modify = true;
				}

				if (!first_field)
					fprintf(output_file, ",");
				first_field = false;
				fprintf(output_file, "\n    modify field %s security_class %s", f,
			               s);
			}

		END_FOR;
		if (modify)
			fprintf(output_file, ";\n");
	END_FOR;
}


static void extract_triggers()
{
/**************************************
 *
 *	e x t r a c t _ t r i g g e r s
 *
 **************************************
 *
 * Functional description
 *	Get trigger definitions from
 *	rdb$triggers for each relation.
 *
 **************************************/
	TEXT s[32], st[32];

	bool first = true;

    FOR T IN RDB$TRIGGERS WITH
        (T.RDB$SYSTEM_FLAG NE 1 OR T.RDB$SYSTEM_FLAG MISSING)
        SORTED BY T.RDB$TRIGGER_NAME
        if (first) {
            put_comment(260);       /* msg 260: \n\n\tTrigger Definitions\t */
            first = false;
        }
        name_trunc(T.RDB$TRIGGER_NAME, st);
        fprintf(output_file, "\n\ndefine trigger %s", st);
        name_trunc(T.RDB$RELATION_NAME, s);
        fprintf(output_file, " for %s\n", s);

        if (T.RDB$TRIGGER_INACTIVE)
            fprintf(output_file, "\tinactive\n");

        switch (T.RDB$TRIGGER_TYPE) {
        case trg_store:
            fprintf(output_file, "\tpre store");
            break;

        case trg_post_store:
            fprintf(output_file, "\tpost store");
            break;

        case trg_modify:
	       fprintf(output_file, "\tpre modify");
	       break;

        case trg_post_modify:
	       fprintf(output_file, "\tpost modify");
	       break;

        case trg_pre_erase:
            fprintf(output_file, "\tpre erase");
            break;

        case trg_erase:
            fprintf(output_file, "\tpost erase");
            break;

        default:
            put_comment(261);       /* msg 261: ***** trigger type not understood **** */
	   }

        fprintf(output_file, " %d:\n", T.RDB$TRIGGER_SEQUENCE);
        if (T.RDB$TRIGGER_SOURCE.NULL) {
            put_error(262, st);
            /* msg 262: **** trigger source for trigger %s must be recreated **** */
        }
        else {
            print_blob(&T.RDB$TRIGGER_SOURCE, 'u');
        }
        fprintf(output_file, "\n\tend_trigger");
        if (!blob_null(T.RDB$DESCRIPTION)) {
            fprintf(output_file, "\n\t{");
            print_blob(&T.RDB$DESCRIPTION, 'u');
            fprintf(output_file, "}");
        }
        extract_trig_msgs(T.RDB$TRIGGER_NAME);

        fprintf(output_file, ";");
    END_FOR;
}


static void extract_trig_msgs(const TEXT * trigger_name)
{
/**************************************
 *
 *	e x t r a c t _ t r i g _ m s g s
 *
 **************************************
 *
 * Functional description
 *	Read the trigger messages as part of the trigger definition
 *	and write them to the output file.
 *
 **************************************/
	bool first = true;
	const TEXT* p = " ";

/* delimit message with single quote by default */

	TEXT quote_char = 39;

    FOR TM IN RDB$TRIGGER_MESSAGES
        WITH TM.RDB$TRIGGER_NAME = trigger_name
        SORTED BY TM.RDB$MESSAGE_NUMBER
        /* If message has a ' in it, delimit with " instead */
        if (strchr(TM.RDB$MESSAGE, 39) != NULL)
            quote_char = 34;
        fprintf(output_file, "%s\n", p);
        if (first) {
            p = ",";
            first = false;
        }
        fprintf(output_file, "\tmessage ");
        fprintf(output_file, " %d: ", TM.RDB$MESSAGE_NUMBER);
        fprintf(output_file, " %c%s%c", quote_char, TM.RDB$MESSAGE,
	              quote_char);
    END_FOR;
}


static void extract_view(const TEXT * rel_name,
						 ISC_QUAD* source,
						 ISC_QUAD* description,
						 bool source_null,
						 bool desc_null,
						 bool system_flag,
						 bool * first)
{
/**************************************
 *
 *	e x t r a c t _ v i e w
 *
 **************************************
 *
 * Functional description
 *	Get definitions of a view and its fields
 *	and write it to the output file.
 *
 **************************************/
	TEXT s[32];

	if (*first) {
		*first = false;
		put_comment(266);		/* msg 266: \n\n\tView Definitions\t */
	}

	name_trunc(rel_name, s);
	fprintf(output_file, "\ndefine view %s of ", s);
	if (source_null) {
		put_error(267, s);		/* msg 267: **** view definition %s must be recreated **** */
		return;
	}

	print_blob(source, 'u');

	if (system_flag)
		fprintf(output_file, "\n\tsystem_flag %d", system_flag);

	if (!desc_null) {
		fprintf(output_file, "\t{");
		print_blob(description, 'u');
		fprintf(output_file, "}");
	}

	view_fields(rel_name);
	fprintf(output_file, ";\n");
}


static void extract_views()
{
/**************************************
 *
 *	e x t r a c t _ v i e w s
 *
 **************************************
 *
 * Functional description
 *	Get definitions of views and their fields
 *	and write it to the output file.
 *
 **************************************/
	bool source_null;
	bool desc_null;
	TEXT s[32];

	bool first = true;

	if (!(EXT_capabilities & EXT_v3)) {
		/* In V2 we have no choice but to output views in the order they appear */

		FOR V IN RDB$RELATIONS WITH
			(V.RDB$SYSTEM_FLAG NE 1 OR V.RDB$SYSTEM_FLAG MISSING) AND
			V.RDB$VIEW_BLR NOT MISSING
			SORTED BY V.RDB$RELATION_NAME
			source_null = (V.RDB$VIEW_SOURCE.NULL);
			desc_null = (blob_null(V.RDB$DESCRIPTION));
			extract_view(V.RDB$RELATION_NAME, &V.RDB$VIEW_SOURCE,
						 &V.RDB$DESCRIPTION, source_null, desc_null,
						 (V.RDB$SYSTEM_FLAG), &first);

		END_FOR;
		return;
	}

// In V3 there are dependencies that can help us to output the views in
// the correct order.  But first let's just save the names of the views.

	view* view_list = NULL;

	FOR V IN RDB$RELATIONS WITH
		(V.RDB$SYSTEM_FLAG NE 1 OR V.RDB$SYSTEM_FLAG MISSING) AND
		V.RDB$VIEW_BLR NOT MISSING
		SORTED BY DESC V.RDB$RELATION_NAME
		name_trunc(V.RDB$RELATION_NAME, s);
		view* viewp = (view*) DDL_alloc(sizeof(view) + strlen(s));
		strcpy(viewp->view_name, s);
		viewp->view_next = view_list;
		view_list = viewp;

	END_FOR;

	if (!view_list)
		return;

    /* For each view that was found, look for any views in the list that it is
   dependent on.  When a view is found that isn't dependent on any views
   still in the list, dump it out and remove it from the list.  Continue
   going through the list until nothing more can be done. */

	bool did_any;
	do {
		did_any = false;
		for (view **view_ptr = &view_list; *view_ptr;) {
			bool do_this_one = true;
			const TEXT* view_name = (*view_ptr)->view_name;

			// fields of rdb$dependencies have been renamed for V4.0
			// This means if we are extracting a pre V3 definition, views
			// will not be spit out in the optimal order. */

			if (EXT_capabilities & EXT_v4) {
				FOR D IN RDB$DEPENDENCIES WITH
					D.RDB$DEPENDENT_NAME EQ view_name AND
					D.RDB$DEPENDENT_TYPE EQ 0 AND
					D.RDB$DEPENDED_ON_TYPE EQ 0
					name_trunc(D.RDB$DEPENDED_ON_NAME, s);
					for (view* viewp = view_list; viewp; viewp = viewp->view_next)
						if (!strcmp(s, viewp->view_name)) {
							do_this_one = false;
							break;
						}
				END_FOR;
			}
			if (do_this_one) {
				FOR V IN RDB$RELATIONS WITH
					V.RDB$RELATION_NAME EQ view_name
					source_null = (V.RDB$VIEW_SOURCE.NULL);
					desc_null = (blob_null(V.RDB$DESCRIPTION));
					extract_view(V.RDB$RELATION_NAME, &V.RDB$VIEW_SOURCE,
								 &V.RDB$DESCRIPTION, source_null, desc_null,
								 (V.RDB$SYSTEM_FLAG), &first);

				END_FOR;
				did_any = true;
				*view_ptr = (*view_ptr)->view_next;
			}
			else
				view_ptr = &(*view_ptr)->view_next;
		}
	} while (did_any);

	// If any views remain, we can't figure out an order so just dump them

	for (view* viewp = view_list; viewp; viewp = viewp->view_next) {
		FOR V IN RDB$RELATIONS WITH
			V.RDB$RELATION_NAME EQ viewp->view_name
			source_null = (V.RDB$VIEW_SOURCE.NULL);
			desc_null = (blob_null(V.RDB$DESCRIPTION));
			extract_view(V.RDB$RELATION_NAME, &V.RDB$VIEW_SOURCE,
						 &V.RDB$DESCRIPTION, source_null, desc_null,
						 (V.RDB$SYSTEM_FLAG), &first);

		END_FOR;
	}
}


static void field_attributes(ISC_QUAD* descr,
							 const TEXT* query_name,
							 ISC_QUAD* query_header,
							 const TEXT* edit_string, SSHORT system_flag)
{
/**************************************
 *
 *	f i e l d _ a t t r i b u t e s
 *
 **************************************
 *
 * Functional description
 *	Print miscellaneous field attributes.
 *
 **************************************/
	TEXT s[256];

	if (descr && (descr->gds_quad_low || descr->gds_quad_high)) {
		fprintf(output_file, "\n\t{");
		print_blob(descr, 'u');
		fprintf(output_file, "}");
	}

	if (system_flag)
		fprintf(output_file, "\n\tsystem_flag %d", system_flag);

	if (query_name && name_trunc(query_name, s))
		fprintf(output_file, "\n\tquery_name %s", s);

	if (query_header && (query_header->gds_quad_low || query_header->gds_quad_high))
	{
		fprintf(output_file, "\n\tquery_header ");
		print_blob(query_header, 'h');
	}

	if (edit_string && name_trunc(edit_string, s))
		if (strchr(s, '"'))
			fprintf(output_file, "\n\tedit_string \'%s\'", s);
		else
			fprintf(output_file, "\n\tedit_string \"%s\"", s);
}


static void format_acl(const SCHAR * string)
{
/**************************************
 *
 *	f o r m a t _ a c l
 *
 **************************************
 *
 * Functional description
 * 	Format an access control list.
 *
 **************************************/
	SCHAR c;
	TEXT view_name[32], group[32], user[32], person[41],
		project[41], organization[41], node[41], temp[40];
	int l;

	const SCHAR* p = string;
	bool first = true;

	if (*p++ != ACL_version)
		put_comment(268);		/* msg 268: ***** ACL not understood ***** */
	else
		fprintf(output_file, "\n");

	while (*p)
		switch (*p++) {
		case ACL_id_list:
			*view_name = *user = *group = '\0';
			*person = *project = *organization = *node = '\0';
			if (!first)
				fprintf(output_file, ",\n");
			else
				first = false;

			fputc('\t', output_file);
			while (c = *p++) {
				switch (c) {
				case id_view:
					l = *p++;
					strncpy(view_name, p, l);
					view_name[l] = 0;
					break;

				case id_group:
					l = *p++;
					strncpy(group, p, l);
					group[l] = 0;
					break;

				case id_user:
					l = *p++;
					strncpy(user, p, l);
					user[l] = 0;
					break;

				case id_person:
					l = *p++;
					strncpy(person, p, l);
					person[l] = 0;
					break;

				case id_project:
					l = *p++;
					strncpy(temp, p, l);
					temp[l] = 0;
					sprintf(project, ".%s", temp);
					break;

				case id_organization:
					l = *p++;
					strncpy(temp, p, l);
					temp[l] = 0;
					sprintf(organization, ".%s", temp);
					break;

				case id_node:
					l = *p++;
					strncpy(temp, p, l);
					temp[l] = 0;
					sprintf(node, ".%s", temp);
					break;
				default:
					// l not assigned
					fb_assert(false);
					return;
				}
				p += l;
			}

/*
   Here we try to decompose the identifier.  Views are easy, they're just views.
   If its not a view, it may be a user/group pair or just the user or just the group.
   Those are operating system dependant.  Otherwise, its an Apollo identifier.
*/

			if (*view_name)
				fprintf(output_file, "view %s ", view_name);
			else
				fprintf(output_file, "<%s,%s> ",
						   (*group) ? group : (TEXT *) "*",
						   (*user) ? user : (TEXT *) "*");
			break;

		case ACL_priv_list:
			while (c = *p++)
				fputc(acl_privs[static_cast<UCHAR>(c)], output_file);
			break;
		}
}


static char name_trunc(const TEXT * in, TEXT * out)
{
/**************************************
 *
 *	n a m e _ t r u n c
 *
 **************************************
 *
 * Functional description
 * 	Copy a name (or other single word)
 *	to the output string, elmininating
 *	trailing blanks.
 *	CVC: This function truncates at the first blank. Not suited for dialect 3.
 *
 **************************************/
	if (!in || *in == ' ')
		return 0;

	const TEXT* const start = out;

	while (*in && *in != ' ')
		*out++ = *in++;

	*out = 0;

	return *start;
}


static void print_blob(ISC_QUAD* blob_id, TEXT type)
{
/**************************************
 *
 *	p r i n t _ b l o b
 *
 **************************************
 *
 * Functional description
 *	Read the contents of an blob
 *	and write them to the output file.
 *	Blobs are characterized as "u" for
 *	unspecified, "m" for missing values
 *	to decode, "h" meaning query
 *	headers that need formatting and "a"
 *	for acls.
 *
 **************************************/
	TEXT buffer[4096];
	SCHAR blr_buffer[2048], *q;
	SCHAR *p = NULL; // silence non initialized warning
	SSHORT length;
	ISC_STATUS_ARRAY status_vector;
	ISC_STATUS status;
	UCHAR bpb_buffer[20], *bpb, *r;

	FB_API_HANDLE blob = 0;
	// This logic was never finished: bpb_length (6th param) is always zero,
	// so this call might be reduced to isc_open_blob().
	bpb = r = bpb_buffer;

	if (isc_open_blob2(status_vector, &DB, &gds_trans, &blob,
						blob_id, (USHORT) (r - bpb),
						bpb))
	{
		gds__print_status(status_vector);
		DDL_err(269);
		/* msg 269: isc_open_blob failed */
		return;
	}

	bool first = true;
	if (type == 'm' || type == 'a') {
		p = blr_buffer;
		*blr_buffer = 0;
	}

	while (!(status = isc_get_segment(status_vector, &blob, (USHORT*) &length,
									   (USHORT) (sizeof(buffer) - 1),
									   buffer)) || status == isc_segment)
	{
		buffer[length] = 0;
		switch (type) {
		case 'h':
			if (q = (SCHAR *) strchr(buffer, '\n')) {
				p = buffer;
				do {
					*q = 0;
					fprintf(output_file, "\"%s\" / ", p);
					p = q + 1;
				} while (q = (SCHAR *) strchr(p, '\n'));
				fprintf(output_file, "\"%s\"", p);
				first = false;
				break;
			}
			if (!first)
				fprintf(output_file, " / ");
			first = false;
			fprintf(output_file, "\"%s\"", buffer);
			break;

		case 'a':
		case 'm':
			q = (SCHAR *) buffer;
			while (length--)
				*p++ = *q++;
			break;

		case 'u':
			for (p = (SCHAR *) buffer; *p; p++)
				putc(*p, output_file);
			break;

		default:
			put_comment(270);	/* msg 270: ***** blob option not understood **** */
		}
	}

	if (status_vector[1] != isc_segstr_eof) {
		gds__print_status(status_vector);
		DDL_err(271);	/* msg 271: isc_get_segment failed  */
		return;
	}

	if (isc_close_blob(status_vector, &blob)) {
		gds__print_status(status_vector);
		DDL_err(272);	/* msg 272: isc_close_blob failed */
		return;
	}

	if (type == 'm')
		decompile_blr_literal(blr_buffer);

	if (type == 'a')
		format_acl(blr_buffer);
}


static void put_comment(USHORT number)
{
/**************************************
 *
 *	p u t _ c o m m e n t
 *
 **************************************
 *
 * Functional description
 *	Retrieve a message from the error file,
 *	format it, and print it in the output file
 *	but not on the screen
 *
 **************************************/
	TEXT message[256];
	static const SafeArg dummy;

	fb_msg_format(NULL, DDL_MSG_FAC, number, sizeof(message), message, dummy);
	fprintf(output_file, "%s\n\n", message);
}


static void put_error(USHORT number, const TEXT* arg1)
{
/**************************************
 *
 *	p u t _ e r r o r
 *
 **************************************
 *
 * Functional description
 *	Retrieve a message from the error file,
 *	format it, and print it in the output file
 *	and on the screen
 *
 **************************************/
	TEXT message[256];

	fb_msg_format(NULL, DDL_MSG_FAC, number, sizeof(message), message,
					SafeArg() << arg1);
	fprintf(output_file, "%s\n\n", message);
	printf("%s\n", message);
}


static void set_capabilities()
{
/**************************************
 *
 *	s e t _ c a p a b i l i t i e s
 *
 **************************************
 *
 * Functional description
 *	set the capabilities bits for the
 *	database being extracted to avoid
 *	unpleasantness later.
 *
 **************************************/
	FB_API_HANDLE req = 0;

/* Look for desireable fields in system relations */

	for (const rfr_tab_t* rel_field_table = rfr_table; rel_field_table->relation;
		rel_field_table++)
	{
		FOR(REQUEST_HANDLE req) x IN RDB$RELATION_FIELDS
		WITH x.RDB$RELATION_NAME = rel_field_table->relation
		AND x.RDB$FIELD_NAME = rel_field_table->field
			EXT_capabilities |= rel_field_table->bit_mask;
		END_FOR;
	}

	isc_release_request(gds_status, &req);
}


static void type_scalar(USHORT dtype,
						SSHORT scale,
						USHORT length,
						SSHORT subtype, USHORT dim, const TEXT * field_name)
{
/**************************************
 *
 *	t y p e _ s c a l a r
 *
 **************************************
 *
 * Functional description
 *	Finish out the kernel field definition of a
 *	non-blob type field.
 *
 **************************************/
	switch (dtype) {
	case blr_cstring:
		fprintf(output_file, " cstring [%d]", length);
		break;

	case blr_text:
		fprintf(output_file, " char [%d]", length);
		break;

	case blr_varying:
		fprintf(output_file, " varying [%d]", length);
		break;

	case blr_short:
		fprintf(output_file, " short");
		break;

	case blr_long:
		fprintf(output_file, " long");
		break;

	case blr_quad:
		fprintf(output_file, " quad");
		break;

	case blr_timestamp:
		fprintf(output_file, " date");
		break;

	case blr_sql_date:
		fprintf(output_file, " date sub_type sql_date");
		break;

	case blr_sql_time:
		fprintf(output_file, " date sub_type sql_time");
		break;

	case blr_float:
		fprintf(output_file, " float");
		break;

	case blr_double:
		fprintf(output_file, " double");
		break;

	case blr_int64:
		fprintf(output_file, " integer64");
		break;

	default:
		fprintf(output_file, " UNKNOWN");
		break;
	}

	if (dim) {
		TEXT c = '(';
        FOR D IN RDB$FIELD_DIMENSIONS WITH
            D.RDB$FIELD_NAME EQ field_name
            SORTED BY D.RDB$DIMENSION
            if (D.RDB$LOWER_BOUND  == 1)
                fprintf(output_file, "%c%"SLONGFORMAT, c, D.RDB$UPPER_BOUND);
            else
                fprintf(output_file, "%c%"SLONGFORMAT":%"SLONGFORMAT, c, D.RDB$LOWER_BOUND,
                           D.RDB$UPPER_BOUND);
            c = ',';
	    END_FOR;
        fprintf(output_file, ")");
	}

	if (scale)
		fprintf(output_file, " scale %d", scale);

	if (subtype && (dtype == blr_text || dtype == blr_varying || dtype == blr_cstring))
	{
		if (subtype == 1)
			fprintf(output_file, " sub_type fixed");
		else
			fprintf(output_file, " sub_type %d", subtype);
	}
}


static void view_fields(const TEXT * view_name)
{
/**************************************
 *
 *	v i e w _ f i e l d s
 *
 **************************************
 *
 * Functional description
 *	Write out the view fields, including
 *	such oddities as context variables.
 *
 **************************************/
	TEXT s[32], context[32], temp[32];

	bool first = true;
	context[0] = 0;

    FOR RFR IN RDB$RELATION_FIELDS CROSS
        VR IN RDB$VIEW_RELATIONS
        WITH RFR.RDB$RELATION_NAME = view_name
        AND RFR.RDB$VIEW_CONTEXT != 0 AND
        VR.RDB$VIEW_NAME = RFR.RDB$RELATION_NAME AND
        VR.RDB$VIEW_CONTEXT = RFR.RDB$VIEW_CONTEXT
        SORTED BY RFR.RDB$FIELD_POSITION
        if (!first)
            fprintf(output_file, ",\n");
        else
            first = false;
        name_trunc(RFR.RDB$FIELD_NAME, s);
        if (EXT_capabilities & EXT_context_name) {
            FOR VR2 IN RDB$VIEW_RELATIONS WITH
                VR2.RDB$VIEW_NAME = VR.RDB$VIEW_NAME AND
                VR2.RDB$VIEW_CONTEXT = VR.RDB$VIEW_CONTEXT
                name_trunc(VR2.RDB$CONTEXT_NAME, context);
            END_FOR;
            if (!strcmp(RFR.RDB$FIELD_NAME, RFR.RDB$BASE_FIELD))
                fprintf(output_file, "    %s.%s", context, s);
            else {
                name_trunc(RFR.RDB$BASE_FIELD, temp);
                fprintf(output_file, "    %s FROM %s.%s", s, context, temp);
            }
            if (!RFR.RDB$FIELD_POSITION.NULL)
                fprintf(output_file, "\tposition %d", RFR.RDB$FIELD_POSITION);

            field_attributes(&RFR.RDB$DESCRIPTION, RFR.RDB$QUERY_NAME,
                             &RFR.RDB$QUERY_HEADER, RFR.RDB$EDIT_STRING,
                             RFR.RDB$SYSTEM_FLAG);
        }
	END_FOR;

	extract_computed(view_name, first);
}

#ifdef NOT_USED_OR_REPLACED
static void wal_info(const UCHAR* db_info_buffer,
					 SSHORT* num_buf,
					 SLONG* buf_size, SLONG* chkpt_len, SLONG* grp_commit)
{
/**************************************
 *
 *	w a l _ i n f o
 *
 **************************************
 *
 * Functional description
 *
 **************************************/
	UCHAR item;
	const UCHAR* p = db_info_buffer;

	while ((item = *p++) != isc_info_end) {
		USHORT length = gds__vax_integer(p, 2);
		p += 2;
		switch (item) {
		case isc_info_num_wal_buffers:
			*num_buf = gds__vax_integer(p, length);
			break;

		case isc_info_wal_buffer_size:
			*buf_size = gds__vax_integer(p, length);
			break;

		case isc_info_wal_ckpt_length:
			*chkpt_len = gds__vax_integer(p, length);
			break;

		case isc_info_wal_grpc_wait_usecs:
			*grp_commit = gds__vax_integer(p, length);
			break;

		default:
			;
		}
		p += length;
	}
}
#endif
