8
0
mirror of https://github.com/FirebirdSQL/firebird.git synced 2025-01-24 03:23:03 +01:00
firebird-mirror/src/qli/format.cpp
2003-10-16 08:51:06 +00:00

1446 lines
34 KiB
C++

/*
* PROGRAM: JRD Command Oriented Query Language
* MODULE: format.cpp
* DESCRIPTION: Print planner and formatter
*
* 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 "../jrd/ib_stdio.h"
#include <string.h>
#include "../jrd/jrd_time.h"
#include "../qli/dtr.h"
#include "../qli/exe.h"
#include "../jrd/gds.h"
#include "../qli/compile.h"
#include "../qli/report.h"
#include "../qli/format.h"
#include "../qli/all_proto.h"
#include "../qli/err_proto.h"
#include "../qli/eval_proto.h"
#include "../qli/exe_proto.h"
#include "../qli/forma_proto.h"
#include "../qli/mov_proto.h"
#include "../qli/picst_proto.h"
extern USHORT QLI_columns, QLI_lines;
#ifdef DEV_BUILD
extern USHORT QLI_hex_output;
inline bool is_printable(const char x)
{
return ((x >= ' ') && (x <= 127)) ||
(x == '\n') ||
(x == '\t') ||
(x == '\r') ||
(x == '\f');
}
#endif
static USHORT decompose_header(SCHAR*, SCHAR**, USHORT*);
static void format_index(ITM, QLI_NOD, const bool);
static TEXT* format_report(VEC, USHORT, USHORT*);
static void format_value(ITM, int);
static TEXT* get_buffer(STR*, TEXT*, USHORT);
static bool match_expr(const qli_nod*, const qli_nod*);
static void print_blobs(PRT, itm**, itm**);
static int print_line(itm*, TEXT**);
static void put_line(PRT, TEXT**, TEXT*, TEXT);
static void report_break(BRK, VEC*, const bool);
static void report_item(ITM, VEC*, USHORT*);
static void report_line(QLI_NOD, VEC*);
static STR global_fmt_buffer, global_blob_buffer;
#define BOTTOM_INIT get_buffer (&global_fmt_buffer, NULL, 1024)
#define BOTTOM_CHECK(ptr, length) ptr = get_buffer (&global_fmt_buffer, ptr, length)
#define BOTTOM_LINE global_fmt_buffer->str_data
#define BUFFER_INIT get_buffer (&global_fmt_buffer, NULL, 1024)
#define BUFFER_CHECK(ptr, length) ptr = get_buffer (&global_fmt_buffer, ptr, length)
#define BUFFER_BEGINNING global_fmt_buffer->str_data
#define BUFFER_REMAINING(ptr) (global_fmt_buffer->str_length - (ptr - global_fmt_buffer->str_data))
int FMT_expression( QLI_NOD node)
{
/**************************************
*
* F M T _ e x p r e s s i o n
*
**************************************
*
* Functional description
* Handle formatting for FORMAT expression. Return editted
* length.
*
**************************************/
QLI_NOD sub = node->nod_arg[e_fmt_value];
PICS picture = PIC_analyze((TEXT*) node->nod_arg[e_fmt_edit], &sub->nod_desc);
node->nod_arg[e_fmt_picture] = (QLI_NOD) picture;
if (node->nod_type == nod_reference)
node = node->nod_arg[0];
QLI_FLD field;
if (!(picture->pic_missing) && (node->nod_type == nod_field) &&
(field = (QLI_FLD) node->nod_arg[e_fld_field]) && field->fld_missing)
PIC_missing(field->fld_missing, picture);
return picture->pic_length;
}
TEXT* FMT_format(LLS stack)
{
/**************************************
*
* F M T _ f o r m a t
*
**************************************
*
* Functional description
* Format a print line. Figure out spacing, print formats, and
* headers. Return a pointer to the header string.
*
**************************************/
ITM item, item2;
QLI_NOD value;
SSHORT segment;
USHORT j, l, n, lengths[10], *ptr;
TEXT *p, *q, *segments[10];
// Start by inverting the item stack into an item que
LLS temp = stack;
stack = NULL;
if (global_fmt_buffer) {
ALL_release((FRB) global_fmt_buffer);
global_fmt_buffer = NULL;
}
while (temp) {
item = (ITM) LLS_POP(&temp);
LLS_PUSH(item, &stack);
}
/* Make a pass thru print items computing print lengths and header
lengths, and the number of header segments. */
USHORT offset, max_offset, number_segments;
number_segments = offset = max_offset = 0;
TEXT* bottom = BOTTOM_INIT;
for (temp = stack; temp; temp = temp->lls_next) {
item = (ITM) temp->lls_object;
switch (item->itm_type) {
case item_column:
offset = (item->itm_count) ? item->itm_count - 1 : 0;
continue;
case item_skip:
case item_new_page:
offset = 0;
continue;
case item_space:
offset += item->itm_count;
continue;
case item_tab:
offset = (offset & ~7) + item->itm_count * 8;
continue;
}
if (item->itm_type == item_value && (value = item->itm_value)) {
if (value->nod_type == nod_reference)
value = value->nod_arg[0];
format_index(item, value, true);
}
if (item->itm_query_header)
if (*item->itm_query_header == '-')
item->itm_query_header = NULL;
else {
n =
decompose_header(item->itm_query_header, segments,
lengths);
number_segments = MAX(n, number_segments);
for (j = 0, ptr = lengths; j < n; j++, ptr++)
item->itm_header_length =
MAX(item->itm_header_length, *ptr);
}
format_value(item, 0);
// If the item would overflow the line, reset to beginning of line
if (offset + MAX(item->itm_print_length, item->itm_header_length) >
QLI_columns) offset = 0;
/* Before we blindly format the header, make sure there already isn't
header information in the same location */
if (item->itm_query_header) {
n = MAX(item->itm_print_length, item->itm_header_length);
BOTTOM_CHECK(bottom, offset);
q = BOTTOM_LINE + offset;
while (bottom < q)
*bottom++ = ' ';
bool flag = true;
if (offset && q[-1] != ' ')
flag = false;
else if (l = MIN(n, bottom - q)) {
p = bottom;
while (--l)
if (*p++ != ' ') {
flag = false;
break;
}
if (flag && p < bottom && *p != ' ')
flag = false;
}
if (flag && (l = n)) {
BOTTOM_CHECK(bottom, bottom - BOTTOM_LINE + n);
do {
*bottom++ = '=';
} while (--l);
}
else {
item->itm_query_header = NULL;
item->itm_header_length = 0;
}
}
/* Now that have settled the issue of header, decide where to put
the field and header */
n = MAX(item->itm_print_length, item->itm_header_length);
item->itm_print_offset = offset + (n - item->itm_print_length) / 2;
item->itm_header_offset = offset + n / 2;
offset += n + 1;
max_offset = MAX(max_offset, offset);
}
// Make another pass checking for overlapping fields
for (temp = stack; temp; temp = temp->lls_next) {
item = (ITM) temp->lls_object;
if (item->itm_type != item_value)
continue;
for (LLS temp2 = temp->lls_next; temp2; temp2 = temp2->lls_next) {
item2 = (ITM) temp2->lls_object;
if (item2->itm_type != item_value)
continue;
if (item2->itm_print_offset <
item->itm_print_offset + item->itm_print_length) {
item->itm_flags |= ITM_overlapped;
break;
}
}
}
if (number_segments == 0)
return NULL;
// Allocate a string block big enough to hold all lines of the print header
const ULONG size = (max_offset + 1) * (number_segments + 1) + 2;
if (size >= 60000)
ERRQ_print_error(482, (TEXT *)(ULONG) max_offset,
(TEXT *) (number_segments + 1), NULL, NULL, NULL);
STR header = (STR) ALLOCDV(type_str, size);
p = header->str_data;
// Generate the various lines of the header line at a time.
for (j = 0; j < number_segments; j++) {
*p++ = '\n';
TEXT* const line = p;
for (temp = stack; temp; temp = temp->lls_next) {
item = (ITM) temp->lls_object;
if (item->itm_type != item_value)
continue;
n = decompose_header(item->itm_query_header, segments, lengths);
segment = j - (number_segments - n);
if (segment < 0)
continue;
l = lengths[segment];
q = line + item->itm_header_offset - l / 2;
while (p < q)
*p++ = ' ';
q = segments[segment];
if (l)
do {
*p++ = *q++;
} while (--l);
}
}
// Make one last pass to put in underlining of headers
if (l = bottom - BOTTOM_LINE) {
*p++ = '\n';
bottom = BOTTOM_LINE;
do {
*p++ = *bottom++;
} while (--l);
}
*p++ = '\n';
*p++ = '\n';
*p = 0;
return header->str_data;
}
QLI_NOD FMT_list(QLI_NOD list)
{
/**************************************
*
* F M T _ l i s t
*
**************************************
*
* Functional description
* Rebuild and format a list of stuff for vertical formatting.
*
**************************************/
ITM new_item, *item, *end;
SYM name;
QLI_FLD field;
QLI_NOD value;
QLI_NOD new_nod = (QLI_NOD) ALLOCDV(type_nod, list->nod_count * 2 + 1);
new_nod->nod_type = nod_list;
ITM* new_ptr = (ITM*) new_nod->nod_arg;
USHORT column = 0;
for (item = (ITM*) list->nod_arg, end = item + list->nod_count;
item < end; item++)
{
if ((*item)->itm_type != item_value || !(value = (*item)->itm_value))
continue;
(*item)->itm_flags |= ITM_overlapped;
format_value(*item, PIC_suppress_blanks);
if (value->nod_type == nod_reference)
value = value->nod_arg[0];
bool expression = true;
if (value->nod_type == nod_field ||
value->nod_type == nod_variable ||
value->nod_type == nod_function)
{
expression = false;
if (value->nod_type != nod_function) {
field = (QLI_FLD) value->nod_arg[e_fld_field];
name = field->fld_name;
format_index(*item, value, false);
}
else
name = ((FUN) value->nod_arg[e_fun_function])->fun_symbol;
}
*new_ptr++ = new_item = (ITM) ALLOCD(type_itm);
new_item->itm_type = item_value;
new_item->itm_value = value = (QLI_NOD) ALLOCDV(type_nod, 0);
value->nod_type = nod_constant;
value->nod_flags |= NOD_local;
value->nod_desc.dsc_dtype = dtype_text;
TEXT* q;
if (!expression && (!(q = (*item)->itm_query_header) || *q != '-')) {
if (q) {
if (*q != '"' && *q != '\'')
value->nod_desc.dsc_address = (UCHAR *) q;
else {
STR header = (STR) ALLOCDV(type_str, strlen(q));
TEXT* p = header->str_data;
value->nod_desc.dsc_address = (UCHAR*) p;
TEXT c;
while (c = *q++) {
while (*q != c)
*p++ = *q++;
*p++ = ' ';
q++;
}
p[-1] = 0;
}
value->nod_desc.dsc_length =
strlen((char*) value->nod_desc.dsc_address);
}
else {
value->nod_desc.dsc_length = name->sym_length;
value->nod_desc.dsc_address = (UCHAR *) name->sym_string;
}
column = MAX(column, value->nod_desc.dsc_length);
new_item->itm_picture = PIC_analyze(0, &value->nod_desc);
}
else {
const dsc* desc = EVAL_value(value);
new_item->itm_picture = PIC_analyze(0, desc);
}
if (!new_item->itm_picture->pic_missing &&
value->nod_type == nod_field && field->fld_missing)
PIC_missing(field->fld_missing, new_item->itm_picture);
*new_ptr++ = *item;
}
*new_ptr++ = new_item = (ITM) ALLOCD(type_itm);
new_item->itm_type = item_skip;
new_item->itm_count = 1;
column += 2;
for (item = (ITM *) list->nod_arg, end = item + list->nod_count;
item < end; item++)
{
if ((*item)->itm_type != item_value || !(value = (*item)->itm_value))
continue;
if (value->nod_type == nod_reference)
value = value->nod_arg[0];
(*item)->itm_print_offset = column;
}
new_nod->nod_count = new_ptr - (ITM *) new_nod->nod_arg;
return new_nod;
}
void FMT_print( QLI_NOD list, PRT print)
{
/**************************************
*
* F M T _ p r i n t
*
**************************************
*
* Functional description
* Format a print line. Return the number of lines printed.
*
**************************************/
USHORT l;
QLI_NOD* ptr;
RPT report;
// Now go thru and make up the first line
if (!list)
return;
TEXT* buffer = NULL;
TEXT* p = BUFFER_INIT;
qli_nod** const end = list->nod_arg + list->nod_count;
for (ptr = list->nod_arg; ptr < end; ptr++) {
ITM item = (ITM) *ptr;
/* Handle formating directives. Most have been translated into
column assignments and are no-ops. */
buffer = BUFFER_BEGINNING;
switch (item->itm_type) {
case item_value:
break;
case item_new_page:
if (print->prt_new_page) {
put_line(print, &p, buffer, '\n');
(*print->prt_new_page) (print, false);
}
else {
put_line(print, &p, buffer, '\f');
QLI_skip_line = FALSE;
}
continue;
case item_skip:
put_line(print, &p, buffer, '\n');
print_blobs(print, (ITM*) list->nod_arg, (ITM*) ptr);
for (l = item->itm_count - 1; l > 0; --l)
put_line(print, &p, buffer, '\n');
QLI_skip_line = FALSE;
continue;
case item_column_header:
if ((report = print->prt_report) && report->rpt_column_header)
FMT_put(report->rpt_column_header, print);
continue;
case item_report_header:
if ((report = print->prt_report) && report->rpt_header)
FMT_put(report->rpt_header, print);
continue;
case item_column:
case item_tab:
case item_space:
default:
continue;
}
/* Handle print items. Start by by spacing out to the correct column,
forcing a new line if required. */
BUFFER_CHECK(p, item->itm_print_offset + item->itm_print_length + 2);
buffer = BUFFER_BEGINNING;
const TEXT* const q = buffer + item->itm_print_offset;
if (p > q) {
put_line(print, &p, buffer, '\n');
print_blobs(print, (ITM*) list->nod_arg, (ITM*) ptr);
}
while (p < q)
*p++ = ' ';
// Next, handle simple formated values
if (item->itm_dtype != dtype_blob) {
const dsc* desc = EVAL_value(item->itm_value);
if (!(desc->dsc_missing & DSC_missing))
PIC_edit(desc, item->itm_picture, &p, BUFFER_REMAINING(p));
else if (item->itm_picture->pic_missing)
PIC_edit(desc, item->itm_picture->pic_missing, &p,
BUFFER_REMAINING(p));
continue;
}
// Finally, handle blobs
if (!(item->itm_stream = EXEC_open_blob(item->itm_value)))
continue;
if (print_line(item, &p) != EOF)
if (item->itm_flags & ITM_overlapped)
for (;;) {
put_line(print, &p, buffer, '\n');
while (p < q)
*p++ = ' ';
if (print_line(item, &p) == EOF)
break;
}
}
put_line(print, &p, buffer, '\n');
// Now go back until all blobs have been fetched
print_blobs(print, (ITM*) list->nod_arg, (ITM*) end);
// Finish by closing all blobs
ISC_STATUS_ARRAY status_vector;
for (ptr = list->nod_arg; ptr < end; ptr++) {
ITM item = (ITM) *ptr;
if (item->itm_dtype == dtype_blob && item->itm_stream)
gds__close_blob(status_vector, &item->itm_stream);
}
}
void FMT_put(const TEXT* line, PRT print)
{
/**************************************
*
* F M T _ p u t
*
**************************************
*
* Functional description
* Write out an output file. Write
* fewer than 256 characters at a time
* to avoid annoying VMS.
*
**************************************/
TEXT buffer[256];
for (const TEXT* pnewline = line; *pnewline; pnewline++)
if (*pnewline == '\n' || *pnewline == '\f')
--print->prt_lines_remaining;
TEXT* const end = buffer + sizeof(buffer) - 1;
const TEXT* q = line;
TEXT* p;
if (print && print->prt_file)
while (*q) {
for (p = buffer; p < end && *q;)
*p++ = *q++;
*p = 0;
ib_fprintf((IB_FILE *) print->prt_file, "%s", buffer);
}
else {
while (*q) {
for (p = buffer; p < end && *q;)
*p++ = *q++;
*p = 0;
#ifdef DEV_BUILD
if (QLI_hex_output) {
// Hex mode output to assist debugging of multicharset work
for (p = buffer; p < end && *p; p++)
if (is_printable(*p))
ib_fprintf(ib_stdout, "%c", *p);
else
ib_fprintf(ib_stdout, "[%2.2X]", *(UCHAR *) p);
}
else
#endif
ib_fprintf(ib_stdout, "%s", buffer);
}
QLI_skip_line = TRUE;
}
}
void FMT_report( RPT report)
{
/**************************************
*
* F M T _ r e p o r t
*
**************************************
*
* Functional description
* Format a report.
*
**************************************/
if (global_fmt_buffer) {
ALL_release((FRB) global_fmt_buffer);
global_fmt_buffer = NULL;
}
USHORT width = report->rpt_columns;
vec* columns_vec = (VEC) ALLOCDV(type_vec, 256);
columns_vec->vec_count = 256;
columns_vec->vec_object[0] = NULL;
report_break(report->rpt_top_rpt, &columns_vec, false);
report_break(report->rpt_top_page, &columns_vec, false);
report_break(report->rpt_top_breaks, &columns_vec, false);
QLI_NOD list = report->rpt_detail_line;
if (list)
report_line(list, &columns_vec);
report_break(report->rpt_bottom_breaks, &columns_vec, true);
report_break(report->rpt_bottom_page, &columns_vec, true);
report_break(report->rpt_bottom_rpt, &columns_vec, true);
report->rpt_column_header = format_report(columns_vec, width, &width);
// Handle report name, if any
if (report->rpt_name) {
USHORT lengths[16];
TEXT* segments[16];
const USHORT n =
decompose_header(report->rpt_name, segments, lengths);
USHORT i;
for (i = 0; i < n; i++)
width = MAX(width, lengths[i] + 15);
STR string = (STR) ALLOCDV(type_str, width * n);
TEXT* p = string->str_data;
report->rpt_header = p;
for (i = 0; i < n; i++) {
USHORT column = (width - lengths[i]) / 2;
if (column > 0)
do {
*p++ = ' ';
} while (--column);
const TEXT* q = segments[i];
const TEXT* const end = q + lengths[i];
while (q < end)
*p++ = *q++;
*p++ = '\n';
}
}
}
static USHORT decompose_header(SCHAR* string,
SCHAR** segments,
USHORT* lengths)
{
/**************************************
*
* d e c o m p o s e _ h e a d e r
*
**************************************
*
* Functional description
* Decompose a header string (aka field name) into segments.
* Return the address of and length of each segment (in arrays)
* and the number of segments.
*
**************************************/
TEXT c;
if (!string)
return 0;
USHORT n = 0;
// Handle simple name first
if (*string != '"' && *string != '\'')
while (*string) {
*segments = string;
while (*string && *string != '_')
string++;
*lengths++ = string - *segments++;
++n;
if (*string == '_')
string++;
}
else
while (c = *string++) {
*segments = string;
while (*string++ != c);
*lengths++ = string - *segments++ - 1;
++n;
}
return n;
}
static void format_index( ITM item, QLI_NOD field, const bool print_flag)
{
/**************************************
*
* f o r m a t _ i n d e x
*
**************************************
*
* Functional description
* Format the label of a subscripted item.
*
**************************************/
QLI_NOD args;
/* Don't bother with anything except non-indexed fields. Also
ignore subscripted fields with user specified query headers. */
{
const TEXT* qh;
if (field->nod_type != nod_field ||
!(args = field->nod_arg[e_fld_subs]) ||
((qh = item->itm_query_header) && (*qh == '"' || *qh == '\'')))
return;
}
// Start the label with the current query header, if any
USHORT l;
const TEXT* q;
if (item->itm_query_header) {
q = item->itm_query_header;
l = strlen(item->itm_query_header);
}
else {
q = ((QLI_FLD) field->nod_arg[e_fld_field])->fld_name->sym_string;
l = ((QLI_FLD) field->nod_arg[e_fld_field])->fld_name->sym_length;
}
USHORT length = l + 2;
STR str = NULL;
TEXT* p = get_buffer(&str, NULL, length + 32);
while (l--)
*p++ = *q++;
// Loop through the subscripts, adding to the label
const TEXT* r;
if (print_flag) {
r = "_[";
length++;
}
else
r = "[";
TEXT s[32];
QLI_NOD *ptr, *end;
for (ptr = args->nod_arg, end = ptr + args->nod_count; ptr < end; ptr++) {
QLI_NOD subscript = *ptr;
switch (subscript->nod_type) {
case nod_constant:
sprintf(s, "%ld", MOVQ_get_long(&subscript->nod_desc, 0));
q = s;
l = strlen(s);
break;
case nod_variable:
case nod_field:
q = ((QLI_FLD) subscript->nod_arg[e_fld_field])->fld_name->sym_string;
l = ((QLI_FLD) subscript->nod_arg[e_fld_field])->fld_name->sym_length;
break;
default:
// Punt on anything but constants, fields, and variables
ALL_release((FRB) str);
return;
}
length += l + 1;
p = get_buffer(&str, p, length);
while (*r)
*p++ = *r++;
while (l--)
*p++ = *q++;
r = ",";
}
if (*r == ',')
*p++ = ']';
*p = 0;
item->itm_query_header = str->str_data;
}
static TEXT* format_report( VEC columns_vec, USHORT width, USHORT* max_width)
{
/**************************************
*
* f o r m a t _ r e p o r t
*
**************************************
*
* Functional description
* Format a report. Figure out spacing, print formats, and
* headers. Return a pointer to the header string.
*
**************************************/
ITM item;
QLI_NOD node;
USHORT j, l, n, lengths[10], *ptr;
TEXT *p, *q, *segments[10];
/* Make a pass thru print items computing print lengths and header
lengths, and the number of header segments. */
USHORT number_segments, offset, max_offset;
number_segments = offset = max_offset = 0;
TEXT* bottom = BOTTOM_INIT;
LLS* col = (LLS*) columns_vec->vec_object;
LLS* col_end;
LLS temp;
for (col_end = col + columns_vec->vec_count; col < col_end && *col; col++) {
USHORT column_width = 0, max_print_width = 0;
bool right_adjust = false;
for (temp = *col; temp; temp = temp->lls_next) {
item = (ITM) temp->lls_object;
switch (item->itm_type) {
case item_column:
offset = (item->itm_count) ? item->itm_count - 1 : 0;
continue;
case item_skip:
case item_new_page:
case item_report_header:
case item_column_header:
offset = 0;
continue;
case item_space:
offset += item->itm_count;
continue;
case item_tab:
offset = (offset & ~7) + item->itm_count * 8;
continue;
case item_value:
max_print_width =
MAX(max_print_width, item->itm_print_length);
node = item->itm_value;
if (node->nod_desc.dsc_dtype >= dtype_short &&
node->nod_desc.dsc_dtype <= dtype_double)
right_adjust = true;
}
if (item->itm_query_header) {
n =
decompose_header(item->itm_query_header, segments,
lengths);
number_segments = MAX(n, number_segments);
for (j = 0, ptr = lengths; j < n; j++, ptr++)
item->itm_header_length =
MAX(item->itm_header_length, *ptr);
}
format_value(item, 0);
n = MAX(item->itm_print_length, item->itm_header_length);
column_width = MAX(column_width, n);
}
if (offset + column_width > width)
offset = 0;
const USHORT right_offset = column_width - max_print_width / 2;
for (temp = *col; temp; temp = temp->lls_next) {
item = (ITM) temp->lls_object;
if (item->itm_type != item_value)
continue;
if (right_adjust)
item->itm_print_offset =
offset + right_offset - item->itm_print_length;
else
item->itm_print_offset =
offset + (column_width - item->itm_print_length) / 2;
item->itm_header_offset = offset + column_width / 2;
/* Before we blindly format the header, make sure there already isn't
header information in the same location */
if (item->itm_query_header) {
BOTTOM_CHECK(bottom, offset);
q = BOTTOM_LINE + offset;
while (bottom < q)
*bottom++ = ' ';
bool flag = true;
if (offset && q[-1] != ' ')
flag = false;
else if (l = MIN(column_width, bottom - q)) {
p = bottom;
while (--l)
if (*p++ != ' ') {
flag = false;
break;
}
if (flag && p < bottom && *p != ' ')
flag = false;
}
if (flag) {
BOTTOM_CHECK(bottom, offset + column_width);
for (q = BOTTOM_LINE + offset + column_width; bottom < q;)
*bottom++ = '=';
}
else
item->itm_query_header = NULL;
}
}
offset += column_width + 1;
max_offset = MAX(max_offset, offset);
}
*max_width = max_offset;
if (number_segments == 0)
return NULL;
// Allocate a string block big enough to hold all lines of the print header
l = bottom - BOTTOM_LINE;
STR header = (STR) ALLOCDV(type_str,
(max_offset + 1) * (number_segments + 1) + 2 + l);
p = header->str_data;
// Generate the various lines of the header line at a time.
for (j = 0; j < number_segments; j++) {
*p++ = '\n';
TEXT* const line = p;
col = (LLS*) columns_vec->vec_object;
for (col_end = col + columns_vec->vec_count; col < col_end && *col;
col++)
{
for (temp = *col; temp; temp = temp->lls_next) {
item = (ITM) temp->lls_object;
if (item->itm_type != item_value)
continue;
n =
decompose_header(item->itm_query_header, segments,
lengths);
SSHORT segment = j - (number_segments - n);
if (segment < 0)
continue;
l = lengths[segment];
q = line + item->itm_header_offset - l / 2;
while (p < q)
*p++ = ' ';
q = segments[segment];
if (l)
do {
*p++ = *q++;
} while (--l);
}
}
}
// Make one last pass to put in underlining of headers
if (l = bottom - BOTTOM_LINE) {
*p++ = '\n';
bottom = BOTTOM_LINE;
do {
*p++ = *bottom++;
} while (--l);
}
*p++ = '\n';
*p++ = '\n';
*p = 0;
return header->str_data;
}
static void format_value( ITM item, int flags)
{
/**************************************
*
* f o r m a t _ v a l u e
*
**************************************
*
* Functional description
*
**************************************/
QLI_NOD node = item->itm_value;
dsc* desc = &node->nod_desc;
item->itm_dtype = desc->dsc_dtype;
item->itm_sub_type = desc->dsc_sub_type;
if (desc->dsc_dtype == dtype_blob) {
item->itm_print_length = 40;
if (node->nod_type == nod_reference)
node = node->nod_arg[0];
if (node->nod_type == nod_field) {
const qli_fld* field = (QLI_FLD) node->nod_arg[e_fld_field];
if (field->fld_segment_length)
item->itm_print_length = field->fld_segment_length;
}
}
else {
PICS picture = PIC_analyze(item->itm_edit_string, desc);
item->itm_picture = picture;
if (node->nod_type == nod_reference)
node = node->nod_arg[0];
const qli_fld* field;
if (node->nod_type == nod_field) {
field = (QLI_FLD) node->nod_arg[e_fld_field];
if ((field->fld_flags & FLD_array) && !node->nod_arg[e_fld_subs])
ERRQ_print_error(480, field->fld_name->sym_string, NULL, NULL,
NULL, NULL); // msg 480 can not format unsubscripted array %s
}
if (!(item->itm_picture->pic_missing) &&
(node->nod_type == nod_field) &&
(field = (QLI_FLD) node->nod_arg[e_fld_field]) && field->fld_missing)
{
PIC_missing(field->fld_missing, picture);
}
item->itm_print_length = picture->pic_length;
picture->pic_flags |= flags;
}
}
static TEXT* get_buffer(STR* str, TEXT* ptr, USHORT length)
{
/**************************************
*
* g e t _ b u f f e r
*
**************************************
*
* Functional description
* Make sure we have a large enough buffer.
* If the current one is too short, copy the
* current buffer to the new one.
*
**************************************/
USHORT l;
if (!*str) {
*str = (STR) ALLOCPV(type_str, length);
(*str)->str_length = length;
return (*str)->str_data;
}
if (length <= (*str)->str_length)
return (ptr) ? ptr : (*str)->str_data;
STR temp_str = (STR) ALLOCPV(type_str, length);
temp_str->str_length = length;
TEXT* p = temp_str->str_data;
const TEXT* q = (*str)->str_data;
if (ptr && (l = ptr - q))
do {
*p++ = *q++;
} while (--l);
ALL_release((FRB) *str);
*str = temp_str;
return p;
}
static bool match_expr(const qli_nod* node1, const qli_nod* node2)
{
/**************************************
*
* m a t c h _ e x p r
*
**************************************
*
* Functional description
* Compare two nodes for equality of value.
*
**************************************/
// If either is missing, they can't match.
if (!node1 || !node2)
return false;
if (node1->nod_type == nod_reference)
node1 = node1->nod_arg[0];
if (node2->nod_type == nod_reference)
node2 = node2->nod_arg[0];
// A constant more or less matches anything
if (node1->nod_type == nod_constant)
return true;
// Hasn't matched yet. Check for statistical expression
switch (node1->nod_type) {
case nod_average:
case nod_max:
case nod_min:
case nod_total:
case nod_rpt_average:
case nod_rpt_max:
case nod_rpt_min:
case nod_rpt_total:
case nod_running_total:
case nod_agg_average:
case nod_agg_max:
case nod_agg_min:
case nod_agg_total:
return match_expr(node1->nod_arg[e_stt_value], node2);
}
switch (node2->nod_type) {
case nod_average:
case nod_max:
case nod_min:
case nod_total:
case nod_rpt_average:
case nod_rpt_max:
case nod_rpt_min:
case nod_rpt_total:
case nod_running_total:
case nod_agg_average:
case nod_agg_max:
case nod_agg_min:
case nod_agg_total:
return match_expr(node1, node2->nod_arg[e_stt_value]);
}
if (node1->nod_type == node2->nod_type) {
if (node1->nod_type == nod_field) {
if (node1->nod_arg[e_fld_field] != node2->nod_arg[e_fld_field] ||
node1->nod_arg[e_fld_context] !=
node2->nod_arg[e_fld_context])
{
return false;
}
return true;
}
const qli_nod* const* ptr1 = node1->nod_arg;
const qli_nod* const* ptr2 = node2->nod_arg;
for (const qli_nod* const* end = ptr1 + node1->nod_count; ptr1 < end;
++ptr1, ++ptr2)
{
if (!match_expr(*ptr1, *ptr2))
return false;
}
return true;
}
return false;
}
static void print_blobs( PRT print, itm** first, itm** last)
{
/**************************************
*
* p r i n t _ b l o b s
*
**************************************
*
* Functional description
* Print any blobs still active in item list.
*
**************************************/
if (QLI_abort)
return;
itm** ptr;
USHORT length = 0;
for (ptr = first; ptr < last; ptr++) {
const itm* item = *ptr;
if (item->itm_dtype == dtype_blob && item->itm_stream)
length =
MAX(length,
item->itm_print_offset + item->itm_print_length + 2);
}
TEXT* buffer = get_buffer(&global_blob_buffer, NULL, length);
while (!QLI_abort) {
bool blob_active = false;
TEXT* p = buffer;
bool do_line = false;
for (ptr = first; ptr < last; ptr++) {
itm* item = *ptr;
if (item->itm_dtype != dtype_blob || !item->itm_stream)
continue;
const TEXT* const end = buffer + item->itm_print_offset;
while (p < end)
*p++ = ' ';
const TEXT* const pp = p;
const int c = print_line(item, &p);
if (c != EOF)
blob_active = true;
if (pp != p || c == '\n')
do_line = true;
}
if (do_line)
put_line(print, &p, buffer, '\n');
if (!blob_active)
break;
}
}
static int print_line( itm* item, TEXT** ptr)
{
/**************************************
*
* p r i n t _ l i n e
*
**************************************
*
* Functional description
* Print a line of a blob or scratch file. The
* last thing printed.
*
**************************************/
EXEC_poll_abort();
// If we're already at end of stream, there's nothing to do
if (!item->itm_stream)
return EOF;
TEXT* p = *ptr;
const USHORT l = item->itm_print_length;
USHORT length;
ISC_STATUS_ARRAY status_vector;
const ISC_STATUS status = gds__get_segment(status_vector, &item->itm_stream,
&length, l, p);
if (status && status != gds_segment) {
ISC_STATUS* null_status = 0;
gds__close_blob(null_status, &item->itm_stream);
if (status != gds_segstr_eof)
ERRQ_database_error(0, status_vector);
return EOF;
}
/* If this is not a partial segment and the last character
is a newline, throw away the newline */
if (!status && length && p[length - 1] == '\n')
if (length > 1)
--length;
else
p[0] = ' ';
*ptr = p + length;
/* Return the last character in the segment.
If the segment is null, return a newline. */
return (length) ? p[length - 1] : '\n';
}
static void put_line( PRT print, TEXT** ptr, TEXT* buffer, TEXT terminator)
{
/**************************************
*
* p u t _ l i n e
*
**************************************
*
* Functional description
* Given a file descriptor, a running output pointer, and the address
* of the original buffer, write out the current line and reset the
* pointer.
*
**************************************/
*(*ptr)++ = terminator;
**ptr = 0;
FMT_put(buffer, print);
*ptr = buffer;
}
static void report_break( BRK control, VEC* columns_vec, const bool bottom_flag)
{
/**************************************
*
* r e p o r t _ b r e a k
*
**************************************
*
* Functional description
* Handle formatting for a chain of control breaks.
*
**************************************/
if (!control)
return;
if (bottom_flag) {
if (control->brk_next)
report_break(control->brk_next, columns_vec, bottom_flag);
if (control->brk_line)
report_line((QLI_NOD) control->brk_line, columns_vec);
return;
}
for (; control; control = control->brk_next)
if (control->brk_line)
report_line((QLI_NOD) control->brk_line, columns_vec);
}
static void report_item( ITM item, VEC* columns_vec, USHORT* col_ndx)
{
/**************************************
*
* r e p o r t _ i t e m
*
**************************************
*
* Functional description
* Insert a report item into a logical column. It it fits
* someplace reasonable, stick it there.
*
**************************************/
if (item->itm_query_header && *item->itm_query_header == '-')
item->itm_query_header = NULL;
// If it's a constant, dump it in the next logical column
QLI_NOD node;
VEC columns = *columns_vec;
if (columns->vec_object[*col_ndx] &&
(node = item->itm_value) && node->nod_type == nod_constant)
{
LLS_PUSH(item, (LLS*) (columns->vec_object + *col_ndx));
return;
}
/* Loop thru remaining logical columns looking for an equivalent
expression. If we find one, the item beSLONGs in that column;
otherwise, someplace else. */
LLS* col = (LLS*) (columns->vec_object + *col_ndx);
LLS* const col_end = (LLS*) (columns->vec_object + columns->vec_count);
for (; col < col_end && *col; col++)
for (LLS temp = *col; temp; temp = temp->lls_next) {
ITM item2 = (ITM) temp->lls_object;
if (match_expr(item->itm_value, item2->itm_value)) {
LLS_PUSH(item, col);
*col_ndx = col - (LLS *) columns->vec_object;
return;
}
}
// Didn't fit -- make a new logical column
const USHORT new_index = col - (LLS *) columns->vec_object;
*col_ndx = new_index;
if (new_index >= columns->vec_count) {
ALLQ_extend((BLK*) columns_vec, new_index + 16);
(*columns_vec)->vec_count = new_index + 16;
}
LLS_PUSH(item, (LLS*) ((*columns_vec)->vec_object + new_index));
}
static void report_line( QLI_NOD list, VEC * columns_vec)
{
/**************************************
*
* r e p o r t _ l i n e
*
**************************************
*
* Functional description
* Process a report line.
*
**************************************/
USHORT col_ndx = 0;
ITM* ptr = (ITM *) list->nod_arg;
for (ITM* const end = ptr + list->nod_count; ptr < end; ptr++) {
ITM item = *ptr;
report_item(item, columns_vec, &col_ndx);
switch (item->itm_type) {
case item_skip:
case item_new_page:
col_ndx = 0;
break;
}
}
}