Thread: NetBSD/MIPS supports dlopen
Hi, Recent version of NetBSD/MIPS support dlopen. This patch makes the netbsd dynloader tests availability of dlopen on HAVE_DLOPEN rather than on __mips__ Tested on NetBSD 4.99.20 on mips I plan on registering a buildfarm member once this patch is in (and maybe after upgrading to a more current NetBSD-current). Regards, Rémi Zara
Attachment
Rémi Zara wrote: > Hi, > > Recent version of NetBSD/MIPS support dlopen. This patch makes the > netbsd dynloader tests availability of dlopen on HAVE_DLOPEN rather than > on __mips__ Applied, thanks. -- Alvaro Herrera http://www.CommandPrompt.com/ PostgreSQL Replication, Consulting, Custom Development, 24x7 support
Alvaro Herrera <alvherre@commandprompt.com> writes: > R�mi Zara wrote: >> Recent version of NetBSD/MIPS support dlopen. This patch makes the >> netbsd dynloader tests availability of dlopen on HAVE_DLOPEN rather than >> on __mips__ > Applied, thanks. Weird, I haven't seen the commit message come through here. Anyway: (1) this should probably be back-patched; (2) please clean up the ugly double negative here: ! #if !defined(HAVE_DLOPEN) #else dlclose(handle); #endif regards, tom lane
I've attached a patch, against current 8.4 cvs, which optionally sets a maximum width for psql output: # \pset format aligned-wrapped # \pset border 2 # select * from distributors order by did; +------+--------------------+---------------------+---------------+ | did | name | descr | long_col_name | +------+--------------------+---------------------+---------------+ | 1 | Food fish and wine | default | | | 2 | Cat Food Heaven 2 | abcdefghijklmnopqrs ! | | | | tuvwxyz | | | 3 | Cat Food Heaven 3 | default | | | 10 | Lah | default | | | 12 | name | line one | | | 2892 ! short name | short | | | 8732 | | | | +------+--------------------+---------------------+---------------+ (8 rows) The interactive terminal column width comes from char * temp = getenv("COLUMNS"); Which has the strong advantage of great simplicity and portability. But it may not be 1000% universal. If $COLUMNS is not defined, the code bails to assuming an infinitely wide terminal. I will also backport this to Postgres 8.1, for my own use. Though the code is almost totally different in structure. Bryce Nesbitt City CarShare San Francisco ? psql ? psql_wrapping.patch Index: command.c =================================================================== RCS file: /projects/cvsroot/pgsql/src/bin/psql/command.c,v retrieving revision 1.186 diff -c -r1.186 command.c *** command.c 1 Jan 2008 19:45:55 -0000 1.186 --- command.c 5 Mar 2008 20:57:05 -0000 *************** *** 1526,1531 **** --- 1526,1534 ---- case PRINT_ALIGNED: return "aligned"; break; + case PRINT_ALIGNEDWRAP: + return "aligned-wrapped"; + break; case PRINT_HTML: return "html"; break; *************** *** 1559,1564 **** --- 1562,1569 ---- popt->topt.format = PRINT_UNALIGNED; else if (pg_strncasecmp("aligned", value, vallen) == 0) popt->topt.format = PRINT_ALIGNED; + else if (pg_strncasecmp("aligned-wrapped", value, vallen) == 0) + popt->topt.format = PRINT_ALIGNEDWRAP; else if (pg_strncasecmp("html", value, vallen) == 0) popt->topt.format = PRINT_HTML; else if (pg_strncasecmp("latex", value, vallen) == 0) *************** *** 1567,1573 **** popt->topt.format = PRINT_TROFF_MS; else { ! psql_error("\\pset: allowed formats are unaligned, aligned, html, latex, troff-ms\n"); return false; } --- 1572,1578 ---- popt->topt.format = PRINT_TROFF_MS; else { ! psql_error("\\pset: allowed formats are unaligned, aligned, aligned-wrapped, html, latex, troff-ms\n"); return false; } Index: mbprint.c =================================================================== RCS file: /projects/cvsroot/pgsql/src/bin/psql/mbprint.c,v retrieving revision 1.29 diff -c -r1.29 mbprint.c *** mbprint.c 1 Jan 2008 19:45:56 -0000 1.29 --- mbprint.c 5 Mar 2008 20:57:06 -0000 *************** *** 205,211 **** * pg_wcssize takes the given string in the given encoding and returns three * values: * result_width: Width in display character of longest line in string ! * result_hieght: Number of lines in display output * result_format_size: Number of bytes required to store formatted representation of string */ int --- 205,211 ---- * pg_wcssize takes the given string in the given encoding and returns three * values: * result_width: Width in display character of longest line in string ! * result_height: Number of lines in display output * result_format_size: Number of bytes required to store formatted representation of string */ int *************** *** 279,284 **** --- 279,288 ---- return width; } + /* + ** Filter out unprintable characters, companion to wcs_size. + ** Break input into lines (based on \n or \r). + */ void pg_wcsformat(unsigned char *pwcs, size_t len, int encoding, struct lineptr * lines, int count) *************** *** 353,364 **** } len -= chlen; } ! *ptr++ = '\0'; lines->width = linewidth; ! lines++; ! count--; ! if (count > 0) ! lines->ptr = NULL; } unsigned char * --- 357,370 ---- } len -= chlen; } ! *ptr++ = '\0'; // Terminate formatted string lines->width = linewidth; ! // Fill remaining array slots with null ! while(--count) { ! lines++; ! lines->ptr = NULL; ! lines->width = 0; ! } } unsigned char * Index: print.c =================================================================== RCS file: /projects/cvsroot/pgsql/src/bin/psql/print.c,v retrieving revision 1.96 diff -c -r1.96 print.c *** print.c 1 Jan 2008 19:45:56 -0000 1.96 --- print.c 5 Mar 2008 20:57:07 -0000 *************** *** 398,403 **** --- 398,406 ---- } + // + // Prety pretty boxes around cells. + // static void print_aligned_text(const char *title, const char *const * headers, const char *const * cells, const char *const * footers, *************** *** 406,431 **** { bool opt_tuples_only = opt->tuples_only; bool opt_numeric_locale = opt->numericLocale; - unsigned short int opt_border = opt->border; int encoding = opt->encoding; unsigned int col_count = 0; ! unsigned int cell_count = 0; ! unsigned int i; int tmp; ! unsigned int *widths, ! total_w; ! unsigned int *heights; ! unsigned int *format_space; unsigned char **format_buf; const char *const * ptr; ! struct lineptr **col_lineptrs; /* pointers to line pointer for each ! * column */ ! struct lineptr *lineptr_list; /* complete list of linepointers */ int *complete; /* Array remembering which columns have * completed output */ if (cancel_pressed) return; --- 409,436 ---- { bool opt_tuples_only = opt->tuples_only; bool opt_numeric_locale = opt->numericLocale; int encoding = opt->encoding; + unsigned short int opt_border = opt->border; + unsigned int col_count = 0; ! ! unsigned int i,j; int tmp; ! ! unsigned int *width_header, *width_max, *width_wrap, *width_average; ! unsigned int *heights, *format_space; unsigned char **format_buf; + unsigned int total_w; const char *const * ptr; ! struct lineptr **col_lineptrs; /* pointers to line pointer per column */ ! struct lineptr *lineptr_list; /* complete list of linepointers */ int *complete; /* Array remembering which columns have * completed output */ + int tcolumns = 0; // Width of interactive console + int rows=0; // SQL rows in result (calculated) if (cancel_pressed) return; *************** *** 439,446 **** if (col_count > 0) { ! widths = pg_local_calloc(col_count, sizeof(*widths)); ! heights = pg_local_calloc(col_count, sizeof(*heights)); col_lineptrs = pg_local_calloc(col_count, sizeof(*col_lineptrs)); format_space = pg_local_calloc(col_count, sizeof(*format_space)); format_buf = pg_local_calloc(col_count, sizeof(*format_buf)); --- 444,454 ---- if (col_count > 0) { ! width_header = pg_local_calloc(col_count, sizeof(*width_header)); ! width_average = pg_local_calloc(col_count, sizeof(*width_average)); ! width_max = pg_local_calloc(col_count, sizeof(*width_max)); ! width_wrap = pg_local_calloc(col_count, sizeof(*width_wrap)); ! heights = pg_local_calloc(col_count, sizeof(*heights)); col_lineptrs = pg_local_calloc(col_count, sizeof(*col_lineptrs)); format_space = pg_local_calloc(col_count, sizeof(*format_space)); format_buf = pg_local_calloc(col_count, sizeof(*format_buf)); *************** *** 448,466 **** } else { ! widths = NULL; ! heights = NULL; col_lineptrs = NULL; format_space = NULL; format_buf = NULL; complete = NULL; } ! /* count cells (rows * cols) */ ! for (ptr = cells; *ptr; ptr++) ! cell_count++; ! ! /* calc column widths */ for (i = 0; i < col_count; i++) { /* Get width & height */ --- 456,473 ---- } else { ! width_header = NULL; ! width_average = NULL; ! width_max = NULL; ! width_wrap = NULL; ! heights = NULL; col_lineptrs = NULL; format_space = NULL; format_buf = NULL; complete = NULL; } ! /* scan all column headers, find maximum width */ for (i = 0; i < col_count; i++) { /* Get width & height */ *************** *** 468,503 **** space; pg_wcssize((unsigned char *) headers[i], strlen(headers[i]), encoding, &tmp, &height, &space); ! if (tmp > widths[i]) ! widths[i] = tmp; if (height > heights[i]) heights[i] = height; if (space > format_space[i]) format_space[i] = space; } for (i = 0, ptr = cells; *ptr; ptr++, i++) { ! int numeric_locale_len; ! int height, ! space; ! ! if (opt_align[i % col_count] == 'r' && opt_numeric_locale) ! numeric_locale_len = additional_numeric_locale_len(*ptr); ! else ! numeric_locale_len = 0; /* Get width, ignore height */ pg_wcssize((unsigned char *) *ptr, strlen(*ptr), encoding, &tmp, &height, &space); ! tmp += numeric_locale_len; ! if (tmp > widths[i % col_count]) ! widths[i % col_count] = tmp; if (height > heights[i % col_count]) heights[i % col_count] = height; if (space > format_space[i % col_count]) format_space[i % col_count] = space; } if (opt_border == 0) total_w = col_count - 1; else if (opt_border == 1) --- 475,518 ---- space; pg_wcssize((unsigned char *) headers[i], strlen(headers[i]), encoding, &tmp, &height, &space); ! if (tmp > width_max[i]) ! width_max[i] = tmp; if (height > heights[i]) heights[i] = height; if (space > format_space[i]) format_space[i] = space; + + width_header[i] = tmp; } + /* scan all rows, find maximum width */ for (i = 0, ptr = cells; *ptr; ptr++, i++) { ! int height, space; /* Get width, ignore height */ pg_wcssize((unsigned char *) *ptr, strlen(*ptr), encoding, &tmp, &height, &space); ! if (opt_numeric_locale && opt_align[i % col_count] == 'r') { ! tmp += additional_numeric_locale_len(*ptr); ! space += additional_numeric_locale_len(*ptr); ! } ! ! if (tmp > width_max[i % col_count]) ! width_max[i % col_count] = tmp; if (height > heights[i % col_count]) heights[i % col_count] = height; if (space > format_space[i % col_count]) format_space[i % col_count] = space; + + width_average[i % col_count] += tmp; } + rows=(i / col_count); + for (i = 0; i < col_count; i++) { + if( rows ) + width_average[i % col_count] /= rows; + } + /* fiddle total display width based on border style */ if (opt_border == 0) total_w = col_count - 1; else if (opt_border == 1) *************** *** 505,518 **** else total_w = col_count * 3 + 1; ! for (i = 0; i < col_count; i++) ! total_w += widths[i]; /* ! * At this point: widths contains the max width of each column heights ! * contains the max height of a cell of each column format_space contains ! * maximum space required to store formatted string so we prepare the ! * formatting structures */ if (col_count > 0) { --- 520,536 ---- else total_w = col_count * 3 + 1; ! for (i = 0; i < col_count; i++) { ! total_w += width_max[i]; ! } /* ! * At this point: ! * width_max[] contains the max width of each column ! * heights[] contains the max number of lines in each column ! * format_space[] contains the maximum storage space for formatting strings ! * total_w contains the giant width sum ! * Now we allocate some memory... */ if (col_count > 0) { *************** *** 529,535 **** col_lineptrs[i] = lineptr; lineptr += heights[i]; ! format_buf[i] = pg_local_malloc(format_space[i]); col_lineptrs[i]->ptr = format_buf[i]; } --- 547,553 ---- col_lineptrs[i] = lineptr; lineptr += heights[i]; ! format_buf[i] = pg_local_malloc(format_space[i]+1); col_lineptrs[i]->ptr = format_buf[i]; } *************** *** 537,555 **** else lineptr_list = NULL; if (opt->start_table) { /* print title */ if (title && !opt_tuples_only) { - /* Get width & height */ int height; - pg_wcssize((unsigned char *) title, strlen(title), encoding, &tmp, &height, NULL); if (tmp >= total_w) ! fprintf(fout, "%s\n", title); else ! fprintf(fout, "%-*s%s\n", (total_w - tmp) / 2, "", title); } /* print headers */ --- 555,635 ---- else lineptr_list = NULL; + + /* + ** Default the word wrap to the full width (e.g. no word wrap) + */ + for (i = 0; i < col_count; i++) + { + width_wrap[i] = width_max[i]; + } + + /* + ** Optional optimized word wrap. + ** Shrink columns with a high max/avg ratio. + ** Slighly bias against wider columns (increasnig chance + ** a narrow column will fit in its cell) + ** + ** Created March 2008, Bryce Nesbitt + */ + if( opt->format == PRINT_ALIGNEDWRAP ) + { + // If we can get the terminal width + char * temp = getenv("COLUMNS"); + if( temp != NULL ) + { + tcolumns = atoi(temp); + + // Shink high ratio columns + while( total_w > tcolumns) + { + double ratio = 0; + double temp = 0; + int worstcol = -1; + for (i = 0; i < col_count; i++) + if( width_average[i] ) + if( width_wrap[i] > width_header[i] ) + { + temp =(double)width_wrap[i]/width_average[i]; + temp +=width_max[i] * .01; // Penalize wide columns + if( temp > ratio ) + ratio = temp, worstcol=i; + } + + // Debugging -- please leave in source! + if( true ) + { + fprintf(fout, "Wrap ratio="); + for (i = 0; i < col_count; i++) + fprintf(fout, "%f %f ",(double)width_wrap[i]/width_average[i] + width_max[i] * .01, width_max[i] *.01); + fprintf(fout, "\n"); + } + + // Exit loop if we can't squeeze any more. + if( worstcol < 0 ) + break; + + // Squeeze the worst column. Lather, rinse, repeat + width_wrap[worstcol]--; + total_w--; + } + } + } + + /* + ** Woo, hoo, time to output + */ if (opt->start_table) { /* print title */ if (title && !opt_tuples_only) { int height; pg_wcssize((unsigned char *) title, strlen(title), encoding, &tmp, &height, NULL); if (tmp >= total_w) ! fprintf(fout, "%s\n", title); // Aligned else ! fprintf(fout, "%-*s%s\n", (total_w - tmp) / 2, "", title); // Centered } /* print headers */ *************** *** 559,565 **** int line_count; if (opt_border == 2) ! _print_horizontal_line(col_count, widths, opt_border, fout); for (i = 0; i < col_count; i++) pg_wcsformat((unsigned char *) headers[i], strlen(headers[i]), encoding, col_lineptrs[i], heights[i]); --- 639,645 ---- int line_count; if (opt_border == 2) ! _print_horizontal_line(col_count, width_wrap, opt_border, fout); for (i = 0; i < col_count; i++) pg_wcsformat((unsigned char *) headers[i], strlen(headers[i]), encoding, col_lineptrs[i], heights[i]); *************** *** 582,588 **** if (!complete[i]) { ! nbspace = widths[i] - this_line->width; /* centered */ fprintf(fout, "%-*s%s%-*s", --- 662,668 ---- if (!complete[i]) { ! nbspace = width_wrap[i] - this_line->width; /* centered */ fprintf(fout, "%-*s%s%-*s", *************** *** 595,601 **** } } else ! fprintf(fout, "%*s", widths[i], ""); if (i < col_count - 1) { if (opt_border == 0) --- 675,681 ---- } } else ! fprintf(fout, "%*s", width_wrap[i], ""); if (i < col_count - 1) { if (opt_border == 0) *************** *** 613,713 **** fputc('\n', fout); } ! _print_horizontal_line(col_count, widths, opt_border, fout); } } /* print cells */ for (i = 0, ptr = cells; *ptr; i += col_count, ptr += col_count) { ! int j; ! int cols_todo = col_count; ! int line_count; /* Number of lines output so far in row */ if (cancel_pressed) break; for (j = 0; j < col_count; j++) pg_wcsformat((unsigned char *) ptr[j], strlen(ptr[j]), encoding, col_lineptrs[j], heights[j]); ! line_count = 0; memset(complete, 0, col_count * sizeof(int)); ! while (cols_todo) ! { ! /* beginning of line */ ! if (opt_border == 2) ! fputs("| ", fout); ! else if (opt_border == 1) ! fputc(' ', fout); ! ! for (j = 0; j < col_count; j++) ! { ! struct lineptr *this_line = col_lineptrs[j] + line_count; ! bool finalspaces = (opt_border == 2 || j != col_count - 1); ! ! if (complete[j]) /* Just print spaces... */ ! { ! if (finalspaces) ! fprintf(fout, "%*s", widths[j], ""); ! } ! else ! { ! /* content */ ! if (opt_align[j] == 'r') ! { ! if (opt_numeric_locale) ! { ! /* ! * Assumption: This code used only on strings ! * without multibyte characters, otherwise ! * this_line->width < strlen(this_ptr) and we get ! * an overflow ! */ ! char *my_cell = format_numeric_locale((char *) this_line->ptr); ! ! fprintf(fout, "%*s%s", ! (int) (widths[i % col_count] - strlen(my_cell)), "", ! my_cell); ! free(my_cell); ! } ! else ! fprintf(fout, "%*s%s", ! widths[j] - this_line->width, "", ! this_line->ptr); ! } ! else ! fprintf(fout, "%-s%*s", this_line->ptr, ! finalspaces ? (widths[j] - this_line->width) : 0, ""); ! /* If at the right height, done this col */ ! if (line_count == heights[j] - 1 || !this_line[1].ptr) ! { ! complete[j] = 1; ! cols_todo--; ! } ! } ! ! /* divider */ ! if ((j + 1) % col_count) ! { ! if (opt_border == 0) ! fputc(' ', fout); ! else if (line_count == 0) ! fputs(" | ", fout); ! else ! fprintf(fout, " %c ", complete[j + 1] ? ' ' : ':'); ! } ! } ! if (opt_border == 2) ! fputs(" |", fout); ! fputc('\n', fout); ! line_count++; ! } ! } if (opt->stop_table) { if (opt_border == 2 && !cancel_pressed) ! _print_horizontal_line(col_count, widths, opt_border, fout); /* print footers */ if (footers && !opt_tuples_only && !cancel_pressed) --- 693,821 ---- fputc('\n', fout); } ! _print_horizontal_line(col_count, width_wrap, opt_border, fout); } } /* print cells */ for (i = 0, ptr = cells; *ptr; i += col_count, ptr += col_count) { ! bool line_todo,cols_todo; ! int line_count; if (cancel_pressed) break; + // Format each cell. Format again, it is a numeric formatting locale (e.g. 123,456 vs. 123456) for (j = 0; j < col_count; j++) + { pg_wcsformat((unsigned char *) ptr[j], strlen(ptr[j]), encoding, col_lineptrs[j], heights[j]); + if (opt_numeric_locale && opt_align[j % col_count] == 'r') + { + char *my_cell; + my_cell = format_numeric_locale((char *)col_lineptrs[j]->ptr); + strcpy((char *)col_lineptrs[j]->ptr, my_cell); // Buffer IS large enough... now + free(my_cell); + } + } ! // Print rows to console memset(complete, 0, col_count * sizeof(int)); ! line_count = 0; ! line_todo = true; ! while (line_todo) ! { ! cols_todo = true; ! while (cols_todo) ! { ! char border_cell = '*'; ! cols_todo = false; ! ! /* left border */ ! if (opt_border == 2) ! fputs("| ", fout); ! else if (opt_border == 1) ! fputc(' ', fout); ! ! /* for each column */ ! for (j = 0; j < col_count; j++) ! { ! struct lineptr *this_line = &col_lineptrs[j][line_count]; ! ! if( heights[j] <= line_count ) { // Blank column content ! fprintf(fout, "%*s", width_wrap[j], ""); ! border_cell = '|'; ! } ! else if (opt_align[j] == 'r') // Right aligned cell ! { ! int strlen_remaining = strlen((char *)this_line->ptr+complete[j]); ! if( strlen_remaining > width_wrap[j] ) ! { ! fprintf(fout, "%.*s", width_wrap[j], this_line->ptr+complete[j] ); ! complete[j] += width_wrap[j]; // We've done THIS much ! cols_todo = true; // And there is more to do... ! border_cell = ':'; ! } else ! { ! fprintf(fout, "%*s", width_wrap[j] - strlen_remaining, ""); ! fprintf(fout, "%-s", this_line->ptr + complete[j] ); ! complete[j] += strlen_remaining; ! border_cell = '|'; ! } ! } ! else // Left aligned cell ! { ! int strlen_remaining = strlen((char *)this_line->ptr+complete[j]); ! if( strlen_remaining > width_wrap[j] ) ! { ! fprintf(fout, "%.*s", width_wrap[j], this_line->ptr+complete[j] ); ! complete[j] += width_wrap[j]; // We've done THIS much ! cols_todo = true; // And there is more to do... ! border_cell = ':'; ! } else ! { ! fprintf(fout, "%-s", this_line->ptr + complete[j] ); ! fprintf(fout, "%*s", width_wrap[j] - strlen_remaining, ""); ! complete[j] += strlen_remaining; ! border_cell = '|'; ! } ! } ! ! /* print a divider, middle of columns only */ ! if ((j + 1) % col_count) ! { ! if (opt_border == 0) ! fputc(' ', fout); ! else ! fprintf(fout, " %c ", border_cell); ! } ! } ! ! /* end of row border */ ! if (opt_border == 2) ! fprintf(fout, " %c", border_cell); ! fputc('\n', fout); ! } ! ! // Check if any columns have line continuations (due to \n in the cell) ! line_count++; ! line_todo = false; ! for (j = 0; j < col_count; j++) ! { ! if( line_count < heights[j]) { ! if( col_lineptrs[j][line_count].ptr ) { ! line_todo = true; ! complete[j]=0; ! } ! } ! } ! } ! } if (opt->stop_table) { if (opt_border == 2 && !cancel_pressed) ! _print_horizontal_line(col_count, width_wrap, opt_border, fout); /* print footers */ if (footers && !opt_tuples_only && !cancel_pressed) *************** *** 724,730 **** } /* clean up */ ! free(widths); free(heights); free(col_lineptrs); free(format_space); --- 832,841 ---- } /* clean up */ ! free(width_header); ! free(width_average); ! free(width_max); ! free(width_wrap); free(heights); free(col_lineptrs); free(format_space); *************** *** 1881,1886 **** --- 1992,1998 ---- opt, output); break; case PRINT_ALIGNED: + case PRINT_ALIGNEDWRAP: if (opt->expanded) print_aligned_vertical(title, headers, cells, footers, align, opt, output); Index: print.h =================================================================== RCS file: /projects/cvsroot/pgsql/src/bin/psql/print.h,v retrieving revision 1.35 diff -c -r1.35 print.h *** print.h 1 Jan 2008 19:45:56 -0000 1.35 --- print.h 5 Mar 2008 20:57:07 -0000 *************** *** 21,26 **** --- 21,27 ---- PRINT_NOTHING = 0, /* to make sure someone initializes this */ PRINT_UNALIGNED, PRINT_ALIGNED, + PRINT_ALIGNEDWRAP, PRINT_HTML, PRINT_LATEX, PRINT_TROFF_MS
Tom Lane wrote: > Alvaro Herrera <alvherre@commandprompt.com> writes: > > R�mi Zara wrote: > >> Recent version of NetBSD/MIPS support dlopen. This patch makes the > >> netbsd dynloader tests availability of dlopen on HAVE_DLOPEN rather than > >> on __mips__ > > > Applied, thanks. > > Anyway: (1) this should probably be back-patched; (2) please clean up > the ugly double negative here: Both done -- I backpatched all the way down to 7.4 (and later I noticed that the only 7.3 BF members are NetBSD). > Weird, I haven't seen the commit message come through here. Yeah, that's strange -- the (2) commit message got to me, but this one hasn't. -- Alvaro Herrera http://www.CommandPrompt.com/ The PostgreSQL Company - Command Prompt, Inc.
Alvaro Herrera <alvherre@commandprompt.com> writes: > Tom Lane wrote: >> Weird, I haven't seen the commit message come through here. > Yeah, that's strange -- the (2) commit message got to me, but this one > hasn't. Moderation filter got it for some reason? None of the back-patch commits came through either, so there's something going on there... regards, tom lane
Alvaro Herrera wrote: > Both done -- I backpatched all the way down to 7.4 (and later I noticed > that the only 7.3 BF members are NetBSD). > > Haven't we declared 7.3 at EOL anyway? cheers andrew
Tom Lane wrote: > Alvaro Herrera <alvherre@commandprompt.com> writes: > > Tom Lane wrote: > >> Weird, I haven't seen the commit message come through here. > > > Yeah, that's strange -- the (2) commit message got to me, but this one > > hasn't. > > Moderation filter got it for some reason? Hmm, not moderation, because I am a moderator and didn't get it. > None of the back-patch > commits came through either, so there's something going on there... Perhaps it's the fact that I used Rémi's name with the accent. I'll check Majordomo logs if it lets me. -- Alvaro Herrera http://www.CommandPrompt.com/ PostgreSQL Replication, Consulting, Custom Development, 24x7 support
Andrew Dunstan wrote: > > > Alvaro Herrera wrote: >> Both done -- I backpatched all the way down to 7.4 (and later I noticed >> that the only 7.3 BF members are NetBSD). > > Haven't we declared 7.3 at EOL anyway? That's why I didn't backpatch it there. But if that's the case, why are we still reporting 7.3 in the buildfarm status page? -- Alvaro Herrera http://www.CommandPrompt.com/ PostgreSQL Replication, Consulting, Custom Development, 24x7 support
Alvaro Herrera wrote: > Tom Lane wrote: > > Alvaro Herrera <alvherre@commandprompt.com> writes: > > > Tom Lane wrote: > > >> Weird, I haven't seen the commit message come through here. > > > > > Yeah, that's strange -- the (2) commit message got to me, but this one > > > hasn't. > > None of the back-patch > > commits came through either, so there's something going on there... > > Perhaps it's the fact that I used Rémi's name with the accent. I'll > check Majordomo logs if it lets me. I checked the Majordomo logs and there's nothing about those patches. I do see one message with the "Subject: pgsql: Clean up double negative, per Tom Lane." line. A message held for moderation shows up in the logs with a "stall" status. So these messages where chopped _before_ they got into Majordomo at all ... Perhaps a bug in the script that sends the email? -- Alvaro Herrera http://www.CommandPrompt.com/ PostgreSQL Replication, Consulting, Custom Development, 24x7 support
Alvaro Herrera wrote: > Alvaro Herrera wrote: >> Tom Lane wrote: >>> Alvaro Herrera <alvherre@commandprompt.com> writes: >>>> Tom Lane wrote: >>>>> Weird, I haven't seen the commit message come through here. >>>> Yeah, that's strange -- the (2) commit message got to me, but this one >>>> hasn't. > >>> None of the back-patch >>> commits came through either, so there's something going on there... >> Perhaps it's the fact that I used Rémi's name with the accent. I'll >> check Majordomo logs if it lets me. > > I checked the Majordomo logs and there's nothing about those patches. > I do see one message with the "Subject: pgsql: Clean up double negative, > per Tom Lane." line. A message held for moderation shows up in the logs > with a "stall" status. So these messages where chopped _before_ they > got into Majordomo at all ... > > Perhaps a bug in the script that sends the email? I see a bunch of emails from you leaving the system today. My first guess for problem location would be the antispam, but before we rule out the sender completely - at exactly what time was the commit(s) that made it through made, and at what time was the commit(s) that didn't make it through? In GMT time, please :-) //Magnus
Alvaro Herrera wrote: > Andrew Dunstan wrote: > >> Alvaro Herrera wrote: >> >>> Both done -- I backpatched all the way down to 7.4 (and later I noticed >>> that the only 7.3 BF members are NetBSD). >>> >> Haven't we declared 7.3 at EOL anyway? >> > > That's why I didn't backpatch it there. But if that's the case, why are > we still reporting 7.3 in the buildfarm status page? > > Because until a couple of weeks ago those two machines were still reporting that branch. When they are 30 days old the reports will drop off the page. (It looks like salamander has stopped altogether, which Tom mentioned the other day would distress him some.) cheers andrew
Magnus Hagander wrote: > Alvaro Herrera wrote: >> I checked the Majordomo logs and there's nothing about those patches. >> I do see one message with the "Subject: pgsql: Clean up double negative, >> per Tom Lane." line. A message held for moderation shows up in the logs >> with a "stall" status. So these messages where chopped _before_ they >> got into Majordomo at all ... >> >> Perhaps a bug in the script that sends the email? > > I see a bunch of emails from you leaving the system today. My first > guess for problem location would be the antispam, but before we rule out > the sender completely - at exactly what time was the commit(s) that made > it through made, and at what time was the commit(s) that didn't make it > through? In GMT time, please :-) Huh, I have zero idea and I had already closed the windows. So, from the CVS logs: revision 1.23 date: 2008/03/05 19:42:11; author: alvherre; state: Exp; lines: +4 -4 Add support for dlopen on recent NetBSD/MIPS, per Rémi Zara. ... revision 1.12.4.1 date: 2008/03/05 21:20:49; author: alvherre; state: Exp; lines: +3 -4 Add support for dlopen on recent NetBSD/MIPS, per Rémi Zara. ---------------------------- revision 1.16.4.1 date: 2008/03/05 21:20:48; author: alvherre; state: Exp; lines: +3 -4 Add support for dlopen on recent NetBSD/MIPS, per Rémi Zara. ---------------------------- revision 1.17.2.1 date: 2008/03/05 21:20:49; author: alvherre; state: Exp; lines: +3 -4 Add support for dlopen on recent NetBSD/MIPS, per Rémi Zara. ---------------------------- revision 1.19.2.1 date: 2008/03/05 21:20:48; author: alvherre; state: Exp; lines: +4 -5 Add support for dlopen on recent NetBSD/MIPS, per Rémi Zara. ---------------------------- revision 1.22.2.1 date: 2008/03/05 21:20:47; author: alvherre; state: Exp; lines: +4 -5 Add support for dlopen on recent NetBSD/MIPS, per Rémi Zara. These are all GMT AFAICT. -- Alvaro Herrera http://www.CommandPrompt.com/ PostgreSQL Replication, Consulting, Custom Development, 24x7 support
On Wed, Mar 05, 2008 at 07:38:21PM -0300, Alvaro Herrera wrote: > Magnus Hagander wrote: > > Alvaro Herrera wrote: > > >> I checked the Majordomo logs and there's nothing about those patches. > >> I do see one message with the "Subject: pgsql: Clean up double negative, > >> per Tom Lane." line. A message held for moderation shows up in the logs > >> with a "stall" status. So these messages where chopped _before_ they > >> got into Majordomo at all ... > >> > >> Perhaps a bug in the script that sends the email? > > > > I see a bunch of emails from you leaving the system today. My first > > guess for problem location would be the antispam, but before we rule out > > the sender completely - at exactly what time was the commit(s) that made > > it through made, and at what time was the commit(s) that didn't make it > > through? In GMT time, please :-) > > Huh, I have zero idea and I had already closed the windows. So, from > the CVS logs: This is enough - I just wanted to be sure which commits we talked about. > revision 1.23 > date: 2008/03/05 19:42:11; author: alvherre; state: Exp; lines: +4 -4 > Add support for dlopen on recent NetBSD/MIPS, per Rémi Zara. 19:42:11 I have one email going out to pgsql-committers from alvherre. 21:14:10 I have another, that doesn't match any of these commits. Did you make anotherone that you didn't include in this list? > ... > revision 1.12.4.1 > date: 2008/03/05 21:20:49; author: alvherre; state: Exp; lines: +3 -4 > Add support for dlopen on recent NetBSD/MIPS, per Rémi Zara. > ---------------------------- > revision 1.16.4.1 > date: 2008/03/05 21:20:48; author: alvherre; state: Exp; lines: +3 -4 > Add support for dlopen on recent NetBSD/MIPS, per Rémi Zara. > ---------------------------- > revision 1.17.2.1 > date: 2008/03/05 21:20:49; author: alvherre; state: Exp; lines: +3 -4 > Add support for dlopen on recent NetBSD/MIPS, per Rémi Zara. > ---------------------------- > revision 1.19.2.1 > date: 2008/03/05 21:20:48; author: alvherre; state: Exp; lines: +4 -5 > Add support for dlopen on recent NetBSD/MIPS, per Rémi Zara. > ---------------------------- > revision 1.22.2.1 > date: 2008/03/05 21:20:47; author: alvherre; state: Exp; lines: +4 -5 > Add support for dlopen on recent NetBSD/MIPS, per Rémi Zara. 21:20:47 I count 1. 21:20:48 I count 2. 21:20:49 I count 2. All these mails have been acknowledged as received by svr1.postgresql.org.- So the script that sends them out is working properly. I'm back at blaming the antispam for eating them before they got out to the list. Especially since you didn't get a boucne (I assume you would've noticed if you did) //Magnus
Magnus Hagander wrote: > On Wed, Mar 05, 2008 at 07:38:21PM -0300, Alvaro Herrera wrote: > > revision 1.23 > > date: 2008/03/05 19:42:11; author: alvherre; state: Exp; lines: +4 -4 > > Add support for dlopen on recent NetBSD/MIPS, per Rémi Zara. > > 19:42:11 I have one email going out to pgsql-committers from alvherre. > > 21:14:10 I have another, that doesn't match any of these commits. Did you > make anotherone that you didn't include in this list? Correct. > So the script that sends them out is working properly. I'm back at blaming > the antispam for eating them before they got out to the list. Especially > since you didn't get a boucne (I assume you would've noticed if you did) Hmm, so most likely they are in Maia's hands and only Marc can rescue them. -- Alvaro Herrera http://www.CommandPrompt.com/ The PostgreSQL Company - Command Prompt, Inc.
Bryce Nesbitt wrote: > I've attached a patch, against current 8.4 cvs, which optionally sets a > maximum width for psql output: I have added this patch to the May commitfest queue, http://wiki.postgresql.org/wiki/CommitFest:May -- Alvaro Herrera http://www.CommandPrompt.com/ PostgreSQL Replication, Consulting, Custom Development, 24x7 support
Bryce Nesbitt wrote: > I've attached a patch, against current 8.4 cvs, which optionally sets a > maximum width for psql output: > > # \pset format aligned-wrapped > # \pset border 2 > # select * from distributors order by did; > +------+--------------------+---------------------+---------------+ > | did | name | descr | long_col_name | > +------+--------------------+---------------------+---------------+ > | 1 | Food fish and wine | default | | > | 2 | Cat Food Heaven 2 | abcdefghijklmnopqrs ! | > | | | tuvwxyz | | > | 3 | Cat Food Heaven 3 | default | | > | 10 | Lah | default | | > | 12 | name | line one | | > | 2892 ! short name | short | | > | 8732 | | | | > +------+--------------------+---------------------+---------------+ > (8 rows) > > The interactive terminal column width comes from > char * temp = getenv("COLUMNS"); > Which has the strong advantage of great simplicity and portability. But > it may not be 1000% universal. If $COLUMNS is not defined, the code > bails to assuming an infinitely wide terminal. > > I will also backport this to Postgres 8.1, for my own use. Though the > code is almost totally different in structure. I spent time reviewing your patch --- quite impressive. I have attached and updated version with mostly stylistic changes. In testing I found the regression tests were failing because of a divide by zero error (fixed), and a missing case where the column delimiter has to be ":". In fact I now see all your line continuation cases using ":" rather than "!". It actually looks better --- "!" was too close to "|" to be easily recognized. (Did you update your patch to use ":". I didn't see "!" in your patch.) I have added an XXX comment questioning whether the loop to find the column to wrap is inefficient because it potentially loops over the length of the longest column and for each character loops over the number of columns. Not sure if that is a problem. I checked the use of COLUMNS and it seems bash updates the environment variable when a window is resized. I added ioctl(TIOCGWINSZ) if COLUMNS isn't set. We already had a call in print.c for detecting the number of rows on the screen to determine if the pager should be used. Seems COLUMNS should take precedence over ioctl(), right? I don't think Win32 supports that ioctl(), does it? I added some comments and clarified some variable names. I also renamed the option to a shorter "wrapped". I added documentation too. For testers compare: \df with: \pset format wrap \df Impressive! -- Bruce Momjian <bruce@momjian.us> http://momjian.us EnterpriseDB http://enterprisedb.com + If your life is a hard drive, Christ can be your backup. + Index: doc/src/sgml/ref/psql-ref.sgml =================================================================== RCS file: /cvsroot/pgsql/doc/src/sgml/ref/psql-ref.sgml,v retrieving revision 1.199 diff -c -c -r1.199 psql-ref.sgml *** doc/src/sgml/ref/psql-ref.sgml 30 Mar 2008 18:10:20 -0000 1.199 --- doc/src/sgml/ref/psql-ref.sgml 17 Apr 2008 02:45:38 -0000 *************** *** 1513,1519 **** <listitem> <para> Sets the output format to one of <literal>unaligned</literal>, ! <literal>aligned</literal>, <literal>html</literal>, <literal>latex</literal>, or <literal>troff-ms</literal>. Unique abbreviations are allowed. (That would mean one letter is enough.) --- 1513,1520 ---- <listitem> <para> Sets the output format to one of <literal>unaligned</literal>, ! <literal>aligned</literal>, <literal>wrapped</literal>, ! <literal>html</literal>, <literal>latex</literal>, or <literal>troff-ms</literal>. Unique abbreviations are allowed. (That would mean one letter is enough.) *************** *** 1525,1531 **** is intended to create output that might be intended to be read in by other programs (tab-separated, comma-separated). <quote>Aligned</quote> mode is the standard, human-readable, ! nicely formatted text output that is default. The <quote><acronym>HTML</acronym></quote> and <quote>LaTeX</quote> modes put out tables that are intended to be included in documents using the respective mark-up --- 1526,1535 ---- is intended to create output that might be intended to be read in by other programs (tab-separated, comma-separated). <quote>Aligned</quote> mode is the standard, human-readable, ! nicely formatted text output that is default. ! <quote>Wrapped</quote> is like <literal>aligned</> but wraps ! the output to fit the screen's width, based on the environment ! variable <envar>COLUMNS</> or the autodetected width. The <quote><acronym>HTML</acronym></quote> and <quote>LaTeX</quote> modes put out tables that are intended to be included in documents using the respective mark-up *************** *** 2708,2713 **** --- 2712,2730 ---- <variablelist> <varlistentry> + <term><envar>COLUMNS</envar></term> + + <listitem> + <para> + The character width to wrap output in <literal>wrapped</> format + mode. Many shells automatically update <envar>COLUMNS</> when + a window is resized. If not set the screen width is automatically + detected, if possible. + </para> + </listitem> + </varlistentry> + + <varlistentry> <term><envar>PAGER</envar></term> <listitem> Index: src/bin/psql/command.c =================================================================== RCS file: /cvsroot/pgsql/src/bin/psql/command.c,v retrieving revision 1.186 diff -c -c -r1.186 command.c *** src/bin/psql/command.c 1 Jan 2008 19:45:55 -0000 1.186 --- src/bin/psql/command.c 17 Apr 2008 02:45:38 -0000 *************** *** 1526,1531 **** --- 1526,1534 ---- case PRINT_ALIGNED: return "aligned"; break; + case PRINT_WRAP: + return "wrapped"; + break; case PRINT_HTML: return "html"; break; *************** *** 1559,1564 **** --- 1562,1569 ---- popt->topt.format = PRINT_UNALIGNED; else if (pg_strncasecmp("aligned", value, vallen) == 0) popt->topt.format = PRINT_ALIGNED; + else if (pg_strncasecmp("wrapped", value, vallen) == 0) + popt->topt.format = PRINT_WRAP; else if (pg_strncasecmp("html", value, vallen) == 0) popt->topt.format = PRINT_HTML; else if (pg_strncasecmp("latex", value, vallen) == 0) *************** *** 1567,1573 **** popt->topt.format = PRINT_TROFF_MS; else { ! psql_error("\\pset: allowed formats are unaligned, aligned, html, latex, troff-ms\n"); return false; } --- 1572,1578 ---- popt->topt.format = PRINT_TROFF_MS; else { ! psql_error("\\pset: allowed formats are unaligned, aligned, wrapped, html, latex, troff-ms\n"); return false; } Index: src/bin/psql/mbprint.c =================================================================== RCS file: /cvsroot/pgsql/src/bin/psql/mbprint.c,v retrieving revision 1.30 diff -c -c -r1.30 mbprint.c *** src/bin/psql/mbprint.c 16 Apr 2008 18:18:00 -0000 1.30 --- src/bin/psql/mbprint.c 17 Apr 2008 02:45:38 -0000 *************** *** 279,284 **** --- 279,288 ---- return width; } + /* + * Filter out unprintable characters, companion to wcs_size. + * Break input into lines (based on \n or \r). + */ void pg_wcsformat(unsigned char *pwcs, size_t len, int encoding, struct lineptr * lines, int count) *************** *** 353,364 **** } len -= chlen; } ! *ptr++ = '\0'; lines->width = linewidth; ! lines++; ! count--; ! if (count > 0) lines->ptr = NULL; } unsigned char * --- 357,373 ---- } len -= chlen; } ! *ptr++ = '\0'; /* Terminate formatted string */ ! lines->width = linewidth; ! ! /* Fill remaining array slots with nulls */ ! while (--count) ! { ! lines++; lines->ptr = NULL; + lines->width = 0; + } } unsigned char * Index: src/bin/psql/print.c =================================================================== RCS file: /cvsroot/pgsql/src/bin/psql/print.c,v retrieving revision 1.97 diff -c -c -r1.97 print.c *** src/bin/psql/print.c 27 Mar 2008 03:57:34 -0000 1.97 --- src/bin/psql/print.c 17 Apr 2008 02:45:38 -0000 *************** *** 396,401 **** --- 396,404 ---- } + /* + * Prety pretty boxes around cells. + */ static void print_aligned_text(const char *title, const char *const * headers, const char *const * cells, const char *const * footers, *************** *** 404,429 **** { bool opt_tuples_only = opt->tuples_only; bool opt_numeric_locale = opt->numericLocale; - unsigned short int opt_border = opt->border; int encoding = opt->encoding; ! unsigned int col_count = 0; ! unsigned int cell_count = 0; ! unsigned int i; ! int tmp; ! unsigned int *widths, ! total_w; ! unsigned int *heights; ! unsigned int *format_space; unsigned char **format_buf; const char *const * ptr; ! struct lineptr **col_lineptrs; /* pointers to line pointer for each ! * column */ struct lineptr *lineptr_list; /* complete list of linepointers */ int *complete; /* Array remembering which columns have * completed output */ if (cancel_pressed) return; --- 407,437 ---- { bool opt_tuples_only = opt->tuples_only; bool opt_numeric_locale = opt->numericLocale; int encoding = opt->encoding; ! unsigned short int opt_border = opt->border; ! ! unsigned int col_count = 0, cell_count = 0; ! ! unsigned int i, ! j; ! ! unsigned int *width_header, ! *width_max, ! *width_wrap, ! *width_average; ! unsigned int *heights, ! *format_space; unsigned char **format_buf; + unsigned int width_total; const char *const * ptr; ! struct lineptr **col_lineptrs; /* pointers to line pointer per column */ struct lineptr *lineptr_list; /* complete list of linepointers */ int *complete; /* Array remembering which columns have * completed output */ + int tcolumns = 0; /* Width of interactive console */ if (cancel_pressed) return; *************** *** 437,443 **** if (col_count > 0) { ! widths = pg_local_calloc(col_count, sizeof(*widths)); heights = pg_local_calloc(col_count, sizeof(*heights)); col_lineptrs = pg_local_calloc(col_count, sizeof(*col_lineptrs)); format_space = pg_local_calloc(col_count, sizeof(*format_space)); --- 445,454 ---- if (col_count > 0) { ! width_header = pg_local_calloc(col_count, sizeof(*width_header)); ! width_average = pg_local_calloc(col_count, sizeof(*width_average)); ! width_max = pg_local_calloc(col_count, sizeof(*width_max)); ! width_wrap = pg_local_calloc(col_count, sizeof(*width_wrap)); heights = pg_local_calloc(col_count, sizeof(*heights)); col_lineptrs = pg_local_calloc(col_count, sizeof(*col_lineptrs)); format_space = pg_local_calloc(col_count, sizeof(*format_space)); *************** *** 446,452 **** } else { ! widths = NULL; heights = NULL; col_lineptrs = NULL; format_space = NULL; --- 457,466 ---- } else { ! width_header = NULL; ! width_average = NULL; ! width_max = NULL; ! width_wrap = NULL; heights = NULL; col_lineptrs = NULL; format_space = NULL; *************** *** 454,533 **** complete = NULL; } ! /* count cells (rows * cols) */ ! for (ptr = cells; *ptr; ptr++) ! cell_count++; ! ! /* calc column widths */ for (i = 0; i < col_count; i++) { /* Get width & height */ ! int height, space; ! pg_wcssize((unsigned char *) headers[i], strlen(headers[i]), encoding, &tmp, &height, &space); ! if (tmp > widths[i]) ! widths[i] = tmp; if (height > heights[i]) heights[i] = height; if (space > format_space[i]) format_space[i] = space; } ! for (i = 0, ptr = cells; *ptr; ptr++, i++) { ! int numeric_locale_len; ! int height, space; - if (opt_align[i % col_count] == 'r' && opt_numeric_locale) - numeric_locale_len = additional_numeric_locale_len(*ptr); - else - numeric_locale_len = 0; - /* Get width, ignore height */ ! pg_wcssize((unsigned char *) *ptr, strlen(*ptr), encoding, &tmp, &height, &space); ! tmp += numeric_locale_len; ! if (tmp > widths[i % col_count]) ! widths[i % col_count] = tmp; if (height > heights[i % col_count]) heights[i % col_count] = height; if (space > format_space[i % col_count]) format_space[i % col_count] = space; } if (opt_border == 0) ! total_w = col_count - 1; else if (opt_border == 1) ! total_w = col_count * 3 - 1; else ! total_w = col_count * 3 + 1; for (i = 0; i < col_count; i++) ! total_w += widths[i]; /* ! * At this point: widths contains the max width of each column heights ! * contains the max height of a cell of each column format_space contains ! * maximum space required to store formatted string so we prepare the ! * formatting structures */ if (col_count > 0) { ! int heights_total = 0; struct lineptr *lineptr; for (i = 0; i < col_count; i++) ! heights_total += heights[i]; ! lineptr = lineptr_list = pg_local_calloc(heights_total, sizeof(*lineptr_list)); for (i = 0; i < col_count; i++) { col_lineptrs[i] = lineptr; lineptr += heights[i]; ! format_buf[i] = pg_local_malloc(format_space[i]); col_lineptrs[i]->ptr = format_buf[i]; } --- 468,560 ---- complete = NULL; } ! /* scan all column headers, find maximum width */ for (i = 0; i < col_count; i++) { /* Get width & height */ ! int width, ! height, space; ! pg_wcssize((unsigned char *) headers[i], strlen(headers[i]), encoding, &width, &height, &space); ! if (width > width_max[i]) ! width_max[i] = width; if (height > heights[i]) heights[i] = height; if (space > format_space[i]) format_space[i] = space; + + width_header[i] = width; } ! /* scan all rows, find maximum width, compute cell_count */ ! for (i = 0, ptr = cells; *ptr; ptr++, i++, cell_count++) { ! int width, ! height, space; /* Get width, ignore height */ ! pg_wcssize((unsigned char *) *ptr, strlen(*ptr), encoding, &width, &height, &space); ! if (opt_numeric_locale && opt_align[i % col_count] == 'r') ! { ! width += additional_numeric_locale_len(*ptr); ! space += additional_numeric_locale_len(*ptr); ! } ! ! if (width > width_max[i % col_count]) ! width_max[i % col_count] = width; if (height > heights[i % col_count]) heights[i % col_count] = height; if (space > format_space[i % col_count]) format_space[i % col_count] = space; + + width_average[i % col_count] += width; + } + + /* If we have rows, compute average */ + if (col_count != 0 && cell_count != 0) + { + int rows = cell_count / col_count; + + for (i = 0; i < col_count; i++) + width_average[i % col_count] /= rows; } + /* adjust the total display width based on border style */ if (opt_border == 0) ! width_total = col_count - 1; else if (opt_border == 1) ! width_total = col_count * 3 - 1; else ! width_total = col_count * 3 + 1; for (i = 0; i < col_count; i++) ! width_total += width_max[i]; /* ! * At this point: width_max[] contains the max width of each column, ! * heights[] contains the max number of lines in each column, ! * format_space[] contains the maximum storage space for formatting ! * strings, width_total contains the giant width sum. Now we allocate ! * some memory... */ if (col_count > 0) { ! int height_total = 0; struct lineptr *lineptr; for (i = 0; i < col_count; i++) ! height_total += heights[i]; ! lineptr = lineptr_list = pg_local_calloc(height_total, sizeof(*lineptr_list)); for (i = 0; i < col_count; i++) { col_lineptrs[i] = lineptr; lineptr += heights[i]; ! format_buf[i] = pg_local_malloc(format_space[i] + 1); col_lineptrs[i]->ptr = format_buf[i]; } *************** *** 535,553 **** else lineptr_list = NULL; if (opt->start_table) { /* print title */ if (title && !opt_tuples_only) { ! /* Get width & height */ ! int height; ! pg_wcssize((unsigned char *) title, strlen(title), encoding, &tmp, &height, NULL); ! if (tmp >= total_w) ! fprintf(fout, "%s\n", title); else ! fprintf(fout, "%-*s%s\n", (total_w - tmp) / 2, "", title); } /* print headers */ --- 562,650 ---- else lineptr_list = NULL; + + /* Default word wrap to the full width, i.e. no word wrap */ + for (i = 0; i < col_count; i++) + width_wrap[i] = width_max[i]; + + /* + * Optional optimized word wrap. Shrink columns with a high max/avg ratio. + * Slighly bias against wider columns (increases chance a narrow column + * will fit in its cell) + */ + if (opt->format == PRINT_WRAP) + { + /* If we can get the terminal width */ + char *env = getenv("COLUMNS"); + + if (env != NULL) + tcolumns = atoi(env); + #ifdef TIOCGWINSZ + else + { + struct winsize screen_size; + + if (ioctl(fileno(stdout), TIOCGWINSZ, &screen_size) != -1) + tcolumns = screen_size.ws_col; + } + #endif + + if (tcolumns > 0) + { + /* Shink high ratio columns */ + while (width_total > tcolumns) + { + double ratio = 0; + double curr_ratio = 0; + int worst_col = -1; + + /* + * Find column that has the highest ratio of its maximum + * width compared to its average width. This tells us which + * column will produce the fewest wrapped values if shortened. + * width_wrap starts as equal to width_max. + */ + for (i = 0; i < col_count; i++) + if (width_average[i] && width_wrap[i] > width_header[i]) + { + curr_ratio = (double) width_wrap[i] / width_average[i]; + curr_ratio += width_max[i] * 0.01; /* Penalize wide columns */ + if (curr_ratio > ratio) + { + ratio = curr_ratio; + worst_col = i; + } + } + + /* Exit loop if we can't squeeze any more. */ + if (worst_col < 0) + break; + + /* Squeeze the worst column. Lather, rinse, repeat */ + /* + * XXX This only reduces one character at a time so might + * be inefficient for very long rows. + */ + width_wrap[worst_col]--; + width_total--; + } + } + } + + /* time to output */ if (opt->start_table) { /* print title */ if (title && !opt_tuples_only) { ! int width, ! height; ! pg_wcssize((unsigned char *) title, strlen(title), encoding, &width, &height, NULL); ! if (width >= width_total) ! fprintf(fout, "%s\n", title); /* Aligned */ else ! fprintf(fout, "%-*s%s\n", (width_total - width) / 2, "", title); /* Centered */ } /* print headers */ *************** *** 557,563 **** int line_count; if (opt_border == 2) ! _print_horizontal_line(col_count, widths, opt_border, fout); for (i = 0; i < col_count; i++) pg_wcsformat((unsigned char *) headers[i], strlen(headers[i]), encoding, col_lineptrs[i], heights[i]); --- 654,660 ---- int line_count; if (opt_border == 2) ! _print_horizontal_line(col_count, width_wrap, opt_border, fout); for (i = 0; i < col_count; i++) pg_wcsformat((unsigned char *) headers[i], strlen(headers[i]), encoding, col_lineptrs[i], heights[i]); *************** *** 580,586 **** if (!complete[i]) { ! nbspace = widths[i] - this_line->width; /* centered */ fprintf(fout, "%-*s%s%-*s", --- 677,683 ---- if (!complete[i]) { ! nbspace = width_wrap[i] - this_line->width; /* centered */ fprintf(fout, "%-*s%s%-*s", *************** *** 593,599 **** } } else ! fprintf(fout, "%*s", widths[i], ""); if (i < col_count - 1) { if (opt_border == 0) --- 690,696 ---- } } else ! fprintf(fout, "%*s", width_wrap[i], ""); if (i < col_count - 1) { if (opt_border == 0) *************** *** 611,711 **** fputc('\n', fout); } ! _print_horizontal_line(col_count, widths, opt_border, fout); } } /* print cells */ for (i = 0, ptr = cells; *ptr; i += col_count, ptr += col_count) { ! int j; ! int cols_todo = col_count; ! int line_count; /* Number of lines output so far in row */ if (cancel_pressed) break; for (j = 0; j < col_count; j++) pg_wcsformat((unsigned char *) ptr[j], strlen(ptr[j]), encoding, col_lineptrs[j], heights[j]); ! line_count = 0; memset(complete, 0, col_count * sizeof(int)); ! while (cols_todo) { ! /* beginning of line */ ! if (opt_border == 2) ! fputs("| ", fout); ! else if (opt_border == 1) ! fputc(' ', fout); ! ! for (j = 0; j < col_count; j++) { ! struct lineptr *this_line = col_lineptrs[j] + line_count; ! bool finalspaces = (opt_border == 2 || j != col_count - 1); ! if (complete[j]) /* Just print spaces... */ ! { ! if (finalspaces) ! fprintf(fout, "%*s", widths[j], ""); ! } ! else { ! /* content */ ! if (opt_align[j] == 'r') { ! if (opt_numeric_locale) { ! /* ! * Assumption: This code used only on strings ! * without multibyte characters, otherwise ! * this_line->width < strlen(this_ptr) and we get ! * an overflow ! */ ! char *my_cell = format_numeric_locale((char *) this_line->ptr); ! ! fprintf(fout, "%*s%s", ! (int) (widths[i % col_count] - strlen(my_cell)), "", ! my_cell); ! free(my_cell); } else ! fprintf(fout, "%*s%s", ! widths[j] - this_line->width, "", ! this_line->ptr); } ! else ! fprintf(fout, "%-s%*s", this_line->ptr, ! finalspaces ? (widths[j] - this_line->width) : 0, ""); ! /* If at the right height, done this col */ ! if (line_count == heights[j] - 1 || !this_line[1].ptr) { ! complete[j] = 1; ! cols_todo--; } } ! /* divider */ ! if ((j + 1) % col_count) { ! if (opt_border == 0) ! fputc(' ', fout); ! else if (line_count == 0) ! fputs(" | ", fout); ! else ! fprintf(fout, " %c ", complete[j + 1] ? ' ' : ':'); } } - if (opt_border == 2) - fputs(" |", fout); - fputc('\n', fout); - line_count++; } } if (opt->stop_table) { if (opt_border == 2 && !cancel_pressed) ! _print_horizontal_line(col_count, widths, opt_border, fout); /* print footers */ if (footers && !opt_tuples_only && !cancel_pressed) --- 708,855 ---- fputc('\n', fout); } ! _print_horizontal_line(col_count, width_wrap, opt_border, fout); } } /* print cells */ for (i = 0, ptr = cells; *ptr; i += col_count, ptr += col_count) { ! bool line_todo, ! cols_todo; ! int line_count; if (cancel_pressed) break; + /* + * Format each cell. Format again, it is a numeric formatting locale + * (e.g. 123,456 vs. 123456) + */ for (j = 0; j < col_count; j++) + { pg_wcsformat((unsigned char *) ptr[j], strlen(ptr[j]), encoding, col_lineptrs[j], heights[j]); + if (opt_numeric_locale && opt_align[j % col_count] == 'r') + { + char *my_cell; ! my_cell = format_numeric_locale((char *) col_lineptrs[j]->ptr); ! strcpy((char *) col_lineptrs[j]->ptr, my_cell); /* Buffer IS large ! * enough... now */ ! free(my_cell); ! } ! } ! ! /* Print rows to console */ memset(complete, 0, col_count * sizeof(int)); ! line_count = 0; ! line_todo = true; ! while (line_todo) { ! cols_todo = true; ! while (cols_todo) { ! char border_cell = '*'; ! cols_todo = false; ! ! /* left border */ ! if (opt_border == 2) ! fputs("| ", fout); ! else if (opt_border == 1) ! fputc(' ', fout); ! ! /* for each column */ ! for (j = 0; j < col_count; j++) { ! struct lineptr *this_line = &col_lineptrs[j][line_count]; ! ! if (heights[j] <= line_count) /* Blank column content */ ! { ! fprintf(fout, "%*s", width_wrap[j], ""); ! border_cell = '|'; ! } ! else if (opt_align[j] == 'r') /* Right aligned cell */ { ! int strlen_remaining = strlen((char *) this_line->ptr + complete[j]); ! ! if (strlen_remaining > width_wrap[j]) { ! fprintf(fout, "%.*s", (int) width_wrap[j], this_line->ptr + complete[j]); ! complete[j] += width_wrap[j]; /* We've done THIS much */ ! cols_todo = true; /* And there is more to do... */ ! border_cell = ':'; } else ! { ! fprintf(fout, "%*s", width_wrap[j] - strlen_remaining, ""); ! fprintf(fout, "%-s", this_line->ptr + complete[j]); ! complete[j] += strlen_remaining; ! border_cell = '|'; ! } } ! else /* Left aligned cell */ { ! int strlen_remaining = strlen((char *) this_line->ptr + complete[j]); ! ! if (strlen_remaining > width_wrap[j]) ! { ! fprintf(fout, "%.*s", (int) width_wrap[j], this_line->ptr + complete[j]); ! complete[j] += width_wrap[j]; /* We've done THIS much */ ! cols_todo = true; /* And there is more to do... */ ! border_cell = ':'; ! } ! else ! { ! fprintf(fout, "%-s", this_line->ptr + complete[j]); ! fprintf(fout, "%*s", width_wrap[j] - strlen_remaining, ""); ! complete[j] += strlen_remaining; ! border_cell = '|'; ! } ! } ! ! /* print a divider, middle of columns only */ ! if ((j + 1) % col_count) ! { ! if (opt_border == 0) ! fputc(' ', fout); ! else if (line_count == 0) ! fprintf(fout, " %c ", border_cell); ! else ! fprintf(fout, " %c ", complete[j + 1] ? ' ' : ':'); } } ! /* end of row border */ ! if (opt_border == 2) ! fprintf(fout, " %c", border_cell); ! fputc('\n', fout); ! } ! ! /* ! * Check if any columns have line continuations due to \n in the ! * cell. ! */ ! line_count++; ! line_todo = false; ! for (j = 0; j < col_count; j++) ! { ! if (line_count < heights[j]) { ! if (col_lineptrs[j][line_count].ptr) ! { ! line_todo = true; ! complete[j] = 0; ! } } } } } if (opt->stop_table) { if (opt_border == 2 && !cancel_pressed) ! _print_horizontal_line(col_count, width_wrap, opt_border, fout); /* print footers */ if (footers && !opt_tuples_only && !cancel_pressed) *************** *** 722,728 **** } /* clean up */ ! free(widths); free(heights); free(col_lineptrs); free(format_space); --- 866,875 ---- } /* clean up */ ! free(width_header); ! free(width_average); ! free(width_max); ! free(width_wrap); free(heights); free(col_lineptrs); free(format_space); *************** *** 754,760 **** dheight = 1, hformatsize = 0, dformatsize = 0; - int tmp = 0; char *divider; unsigned int cell_count = 0; struct lineptr *hlineptr, --- 901,906 ---- *************** *** 779,790 **** /* Find the maximum dimensions for the headers */ for (i = 0; i < col_count; i++) { ! int height, fs; ! pg_wcssize((unsigned char *) headers[i], strlen(headers[i]), encoding, &tmp, &height, &fs); ! if (tmp > hwidth) ! hwidth = tmp; if (height > hheight) hheight = height; if (fs > hformatsize) --- 925,937 ---- /* Find the maximum dimensions for the headers */ for (i = 0; i < col_count; i++) { ! int width, ! height, fs; ! pg_wcssize((unsigned char *) headers[i], strlen(headers[i]), encoding, &width, &height, &fs); ! if (width > hwidth) ! hwidth = width; if (height > hheight) hheight = height; if (fs > hformatsize) *************** *** 799,805 **** for (i = 0, ptr = cells; *ptr; ptr++, i++) { int numeric_locale_len; ! int height, fs; if (opt_align[i % col_count] == 'r' && opt_numeric_locale) --- 946,953 ---- for (i = 0, ptr = cells; *ptr; ptr++, i++) { int numeric_locale_len; ! int width, ! height, fs; if (opt_align[i % col_count] == 'r' && opt_numeric_locale) *************** *** 807,816 **** else numeric_locale_len = 0; ! pg_wcssize((unsigned char *) *ptr, strlen(*ptr), encoding, &tmp, &height, &fs); ! tmp += numeric_locale_len; ! if (tmp > dwidth) ! dwidth = tmp; if (height > dheight) dheight = height; if (fs > dformatsize) --- 955,964 ---- else numeric_locale_len = 0; ! pg_wcssize((unsigned char *) *ptr, strlen(*ptr), encoding, &width, &height, &fs); ! width += numeric_locale_len; ! if (width > dwidth) ! dwidth = width; if (height > dheight) dheight = height; if (fs > dformatsize) *************** *** 1879,1884 **** --- 2027,2033 ---- opt, output); break; case PRINT_ALIGNED: + case PRINT_WRAP: if (opt->expanded) print_aligned_vertical(title, headers, cells, footers, align, opt, output); Index: src/bin/psql/print.h =================================================================== RCS file: /cvsroot/pgsql/src/bin/psql/print.h,v retrieving revision 1.35 diff -c -c -r1.35 print.h *** src/bin/psql/print.h 1 Jan 2008 19:45:56 -0000 1.35 --- src/bin/psql/print.h 17 Apr 2008 02:45:38 -0000 *************** *** 21,26 **** --- 21,27 ---- PRINT_NOTHING = 0, /* to make sure someone initializes this */ PRINT_UNALIGNED, PRINT_ALIGNED, + PRINT_WRAP, PRINT_HTML, PRINT_LATEX, PRINT_TROFF_MS
Bruce Momjian wrote: > I spent time reviewing your patch --- quite impressive. I have attached > and updated version with mostly stylistic changes. > > In testing I found the regression tests were failing because of a divide > by zero error (fixed), and a missing case where the column delimiter has > to be ":". In fact I now see all your line continuation cases using ":" > rather than "!". It actually looks better --- "!" was too close to "|" > to be easily recognized. (Did you update your patch to use ":". I > didn't see "!" in your patch.) Nice! I'll merge with my current version. As you note I changed to ":". I also found that for hugely wide output it was better to give up (do nothing), rather than mangle the output in a futile attempt to squash it to the window width. So there is one more clause in the wrapping if. I have tested on several unix flavors, but not on Windows or cygwin. -Bryce
Bruce Momjian wrote: > In testing I found the regression tests were failing because of a divide > by zero error (fixed), and a missing case where the column delimiter has > to be ":". In fact I now see all your line continuation cases using ":" > rather than "!". It actually looks better --- "!" was too close to "|" > to be easily recognized. (Did you update your patch to use ":". I > didn't see "!" in your patch.) I think we should use a different separator when there is an actual newline in the data. Currently we have a : there, so using a : here is probably not the best idea. -- Alvaro Herrera http://www.CommandPrompt.com/ PostgreSQL Replication, Consulting, Custom Development, 24x7 support
Bruce Momjian wrote: > I checked the use of COLUMNS and it seems bash updates the environment > variable when a window is resized. I added ioctl(TIOCGWINSZ) if COLUMNS > isn't set. We already had a call in print.c for detecting the > number of rows on the screen to determine if the pager should > be used. Seems COLUMNS should take precedence over ioctl(), right? Considering that the code to determine the row count is undisputed so far, the column count detection should work the same. That is, we might not need to look at COLUMNS at all. Unless there is a use case for overriding the column count (instead of just turning off the wrapping).
Bryce Nesbitt wrote: > > > Bruce Momjian wrote: > > I spent time reviewing your patch --- quite impressive. I have attached > > and updated version with mostly stylistic changes. > > > > In testing I found the regression tests were failing because of a divide > > by zero error (fixed), and a missing case where the column delimiter has > > to be ":". In fact I now see all your line continuation cases using ":" > > rather than "!". It actually looks better --- "!" was too close to "|" > > to be easily recognized. (Did you update your patch to use ":". I > > didn't see "!" in your patch.) > Nice! I'll merge with my current version. As you note I changed to ":". Good, I thought so. > I also found that for hugely wide output it was better to give up (do > nothing), rather than mangle the output in a futile attempt to squash it > to the window width. So there is one more clause in the wrapping if. Was it because of performance? I have a way to fix that by decrementing by more than one to shrink a column? I am attaching a new patch with my improved loop. It remembers the previous maximum ratio. > I have tested on several unix flavors, but not on Windows or cygwin. I don't think you need to do that to get it applied --- there is nothing windows-specific in your code. Is this ready to be applied? Do you want to send a final update or are you still testing? -- Bruce Momjian <bruce@momjian.us> http://momjian.us EnterpriseDB http://enterprisedb.com + If your life is a hard drive, Christ can be your backup. + Index: doc/src/sgml/ref/psql-ref.sgml =================================================================== RCS file: /cvsroot/pgsql/doc/src/sgml/ref/psql-ref.sgml,v retrieving revision 1.199 diff -c -c -r1.199 psql-ref.sgml *** doc/src/sgml/ref/psql-ref.sgml 30 Mar 2008 18:10:20 -0000 1.199 --- doc/src/sgml/ref/psql-ref.sgml 17 Apr 2008 14:05:39 -0000 *************** *** 1513,1519 **** <listitem> <para> Sets the output format to one of <literal>unaligned</literal>, ! <literal>aligned</literal>, <literal>html</literal>, <literal>latex</literal>, or <literal>troff-ms</literal>. Unique abbreviations are allowed. (That would mean one letter is enough.) --- 1513,1520 ---- <listitem> <para> Sets the output format to one of <literal>unaligned</literal>, ! <literal>aligned</literal>, <literal>wrapped</literal>, ! <literal>html</literal>, <literal>latex</literal>, or <literal>troff-ms</literal>. Unique abbreviations are allowed. (That would mean one letter is enough.) *************** *** 1525,1531 **** is intended to create output that might be intended to be read in by other programs (tab-separated, comma-separated). <quote>Aligned</quote> mode is the standard, human-readable, ! nicely formatted text output that is default. The <quote><acronym>HTML</acronym></quote> and <quote>LaTeX</quote> modes put out tables that are intended to be included in documents using the respective mark-up --- 1526,1535 ---- is intended to create output that might be intended to be read in by other programs (tab-separated, comma-separated). <quote>Aligned</quote> mode is the standard, human-readable, ! nicely formatted text output that is default. ! <quote>Wrapped</quote> is like <literal>aligned</> but wraps ! the output to fit the screen's width, based on the environment ! variable <envar>COLUMNS</> or the autodetected width. The <quote><acronym>HTML</acronym></quote> and <quote>LaTeX</quote> modes put out tables that are intended to be included in documents using the respective mark-up *************** *** 2708,2713 **** --- 2712,2730 ---- <variablelist> <varlistentry> + <term><envar>COLUMNS</envar></term> + + <listitem> + <para> + The character width to wrap output in <literal>wrapped</> format + mode. Many shells automatically update <envar>COLUMNS</> when + a window is resized. If not set the screen width is automatically + detected, if possible. + </para> + </listitem> + </varlistentry> + + <varlistentry> <term><envar>PAGER</envar></term> <listitem> Index: src/bin/psql/command.c =================================================================== RCS file: /cvsroot/pgsql/src/bin/psql/command.c,v retrieving revision 1.186 diff -c -c -r1.186 command.c *** src/bin/psql/command.c 1 Jan 2008 19:45:55 -0000 1.186 --- src/bin/psql/command.c 17 Apr 2008 14:05:39 -0000 *************** *** 1526,1531 **** --- 1526,1534 ---- case PRINT_ALIGNED: return "aligned"; break; + case PRINT_WRAP: + return "wrapped"; + break; case PRINT_HTML: return "html"; break; *************** *** 1559,1564 **** --- 1562,1569 ---- popt->topt.format = PRINT_UNALIGNED; else if (pg_strncasecmp("aligned", value, vallen) == 0) popt->topt.format = PRINT_ALIGNED; + else if (pg_strncasecmp("wrapped", value, vallen) == 0) + popt->topt.format = PRINT_WRAP; else if (pg_strncasecmp("html", value, vallen) == 0) popt->topt.format = PRINT_HTML; else if (pg_strncasecmp("latex", value, vallen) == 0) *************** *** 1567,1573 **** popt->topt.format = PRINT_TROFF_MS; else { ! psql_error("\\pset: allowed formats are unaligned, aligned, html, latex, troff-ms\n"); return false; } --- 1572,1578 ---- popt->topt.format = PRINT_TROFF_MS; else { ! psql_error("\\pset: allowed formats are unaligned, aligned, wrapped, html, latex, troff-ms\n"); return false; } Index: src/bin/psql/mbprint.c =================================================================== RCS file: /cvsroot/pgsql/src/bin/psql/mbprint.c,v retrieving revision 1.30 diff -c -c -r1.30 mbprint.c *** src/bin/psql/mbprint.c 16 Apr 2008 18:18:00 -0000 1.30 --- src/bin/psql/mbprint.c 17 Apr 2008 14:05:40 -0000 *************** *** 279,284 **** --- 279,288 ---- return width; } + /* + * Filter out unprintable characters, companion to wcs_size. + * Break input into lines (based on \n or \r). + */ void pg_wcsformat(unsigned char *pwcs, size_t len, int encoding, struct lineptr * lines, int count) *************** *** 353,364 **** } len -= chlen; } ! *ptr++ = '\0'; lines->width = linewidth; ! lines++; ! count--; ! if (count > 0) lines->ptr = NULL; } unsigned char * --- 357,373 ---- } len -= chlen; } ! *ptr++ = '\0'; /* Terminate formatted string */ ! lines->width = linewidth; ! ! /* Fill remaining array slots with nulls */ ! while (--count) ! { ! lines++; lines->ptr = NULL; + lines->width = 0; + } } unsigned char * Index: src/bin/psql/print.c =================================================================== RCS file: /cvsroot/pgsql/src/bin/psql/print.c,v retrieving revision 1.97 diff -c -c -r1.97 print.c *** src/bin/psql/print.c 27 Mar 2008 03:57:34 -0000 1.97 --- src/bin/psql/print.c 17 Apr 2008 14:05:40 -0000 *************** *** 43,48 **** --- 43,52 ---- static char *grouping; static char *thousands_sep; + #define COL_RATIO(col_num) \ + ((double) width_wrap[col_num] / width_average[col_num] + \ + width_max[col_num] * 0.01) /* Penalize wide columns */ + static void * pg_local_malloc(size_t size) { *************** *** 396,401 **** --- 400,408 ---- } + /* + * Prety pretty boxes around cells. + */ static void print_aligned_text(const char *title, const char *const * headers, const char *const * cells, const char *const * footers, *************** *** 404,429 **** { bool opt_tuples_only = opt->tuples_only; bool opt_numeric_locale = opt->numericLocale; - unsigned short int opt_border = opt->border; int encoding = opt->encoding; ! unsigned int col_count = 0; ! unsigned int cell_count = 0; ! unsigned int i; ! int tmp; ! unsigned int *widths, ! total_w; ! unsigned int *heights; ! unsigned int *format_space; unsigned char **format_buf; const char *const * ptr; ! struct lineptr **col_lineptrs; /* pointers to line pointer for each ! * column */ struct lineptr *lineptr_list; /* complete list of linepointers */ int *complete; /* Array remembering which columns have * completed output */ if (cancel_pressed) return; --- 411,441 ---- { bool opt_tuples_only = opt->tuples_only; bool opt_numeric_locale = opt->numericLocale; int encoding = opt->encoding; ! unsigned short int opt_border = opt->border; ! ! unsigned int col_count = 0, cell_count = 0; ! ! unsigned int i, ! j; ! ! unsigned int *width_header, ! *width_max, ! *width_wrap, ! *width_average; ! unsigned int *heights, ! *format_space; unsigned char **format_buf; + unsigned int width_total; const char *const * ptr; ! struct lineptr **col_lineptrs; /* pointers to line pointer per column */ struct lineptr *lineptr_list; /* complete list of linepointers */ int *complete; /* Array remembering which columns have * completed output */ + int tcolumns = 0; /* Width of interactive console */ if (cancel_pressed) return; *************** *** 437,443 **** if (col_count > 0) { ! widths = pg_local_calloc(col_count, sizeof(*widths)); heights = pg_local_calloc(col_count, sizeof(*heights)); col_lineptrs = pg_local_calloc(col_count, sizeof(*col_lineptrs)); format_space = pg_local_calloc(col_count, sizeof(*format_space)); --- 449,458 ---- if (col_count > 0) { ! width_header = pg_local_calloc(col_count, sizeof(*width_header)); ! width_average = pg_local_calloc(col_count, sizeof(*width_average)); ! width_max = pg_local_calloc(col_count, sizeof(*width_max)); ! width_wrap = pg_local_calloc(col_count, sizeof(*width_wrap)); heights = pg_local_calloc(col_count, sizeof(*heights)); col_lineptrs = pg_local_calloc(col_count, sizeof(*col_lineptrs)); format_space = pg_local_calloc(col_count, sizeof(*format_space)); *************** *** 446,452 **** } else { ! widths = NULL; heights = NULL; col_lineptrs = NULL; format_space = NULL; --- 461,470 ---- } else { ! width_header = NULL; ! width_average = NULL; ! width_max = NULL; ! width_wrap = NULL; heights = NULL; col_lineptrs = NULL; format_space = NULL; *************** *** 454,533 **** complete = NULL; } ! /* count cells (rows * cols) */ ! for (ptr = cells; *ptr; ptr++) ! cell_count++; ! ! /* calc column widths */ for (i = 0; i < col_count; i++) { /* Get width & height */ ! int height, space; ! pg_wcssize((unsigned char *) headers[i], strlen(headers[i]), encoding, &tmp, &height, &space); ! if (tmp > widths[i]) ! widths[i] = tmp; if (height > heights[i]) heights[i] = height; if (space > format_space[i]) format_space[i] = space; } ! for (i = 0, ptr = cells; *ptr; ptr++, i++) { ! int numeric_locale_len; ! int height, space; - if (opt_align[i % col_count] == 'r' && opt_numeric_locale) - numeric_locale_len = additional_numeric_locale_len(*ptr); - else - numeric_locale_len = 0; - /* Get width, ignore height */ ! pg_wcssize((unsigned char *) *ptr, strlen(*ptr), encoding, &tmp, &height, &space); ! tmp += numeric_locale_len; ! if (tmp > widths[i % col_count]) ! widths[i % col_count] = tmp; if (height > heights[i % col_count]) heights[i % col_count] = height; if (space > format_space[i % col_count]) format_space[i % col_count] = space; } if (opt_border == 0) ! total_w = col_count - 1; else if (opt_border == 1) ! total_w = col_count * 3 - 1; else ! total_w = col_count * 3 + 1; for (i = 0; i < col_count; i++) ! total_w += widths[i]; /* ! * At this point: widths contains the max width of each column heights ! * contains the max height of a cell of each column format_space contains ! * maximum space required to store formatted string so we prepare the ! * formatting structures */ if (col_count > 0) { ! int heights_total = 0; struct lineptr *lineptr; for (i = 0; i < col_count; i++) ! heights_total += heights[i]; ! lineptr = lineptr_list = pg_local_calloc(heights_total, sizeof(*lineptr_list)); for (i = 0; i < col_count; i++) { col_lineptrs[i] = lineptr; lineptr += heights[i]; ! format_buf[i] = pg_local_malloc(format_space[i]); col_lineptrs[i]->ptr = format_buf[i]; } --- 472,564 ---- complete = NULL; } ! /* scan all column headers, find maximum width */ for (i = 0; i < col_count; i++) { /* Get width & height */ ! int width, ! height, space; ! pg_wcssize((unsigned char *) headers[i], strlen(headers[i]), encoding, &width, &height, &space); ! if (width > width_max[i]) ! width_max[i] = width; if (height > heights[i]) heights[i] = height; if (space > format_space[i]) format_space[i] = space; + + width_header[i] = width; } ! /* scan all rows, find maximum width, compute cell_count */ ! for (i = 0, ptr = cells; *ptr; ptr++, i++, cell_count++) { ! int width, ! height, space; /* Get width, ignore height */ ! pg_wcssize((unsigned char *) *ptr, strlen(*ptr), encoding, &width, &height, &space); ! if (opt_numeric_locale && opt_align[i % col_count] == 'r') ! { ! width += additional_numeric_locale_len(*ptr); ! space += additional_numeric_locale_len(*ptr); ! } ! ! if (width > width_max[i % col_count]) ! width_max[i % col_count] = width; if (height > heights[i % col_count]) heights[i % col_count] = height; if (space > format_space[i % col_count]) format_space[i % col_count] = space; + + width_average[i % col_count] += width; } + /* If we have rows, compute average */ + if (col_count != 0 && cell_count != 0) + { + int rows = cell_count / col_count; + + for (i = 0; i < col_count; i++) + width_average[i % col_count] /= rows; + } + + /* adjust the total display width based on border style */ if (opt_border == 0) ! width_total = col_count - 1; else if (opt_border == 1) ! width_total = col_count * 3 - 1; else ! width_total = col_count * 3 + 1; for (i = 0; i < col_count; i++) ! width_total += width_max[i]; /* ! * At this point: width_max[] contains the max width of each column, ! * heights[] contains the max number of lines in each column, ! * format_space[] contains the maximum storage space for formatting ! * strings, width_total contains the giant width sum. Now we allocate ! * some memory... */ if (col_count > 0) { ! int height_total = 0; struct lineptr *lineptr; for (i = 0; i < col_count; i++) ! height_total += heights[i]; ! lineptr = lineptr_list = pg_local_calloc(height_total, sizeof(*lineptr_list)); for (i = 0; i < col_count; i++) { col_lineptrs[i] = lineptr; lineptr += heights[i]; ! format_buf[i] = pg_local_malloc(format_space[i] + 1); col_lineptrs[i]->ptr = format_buf[i]; } *************** *** 535,553 **** else lineptr_list = NULL; if (opt->start_table) { /* print title */ if (title && !opt_tuples_only) { ! /* Get width & height */ ! int height; ! pg_wcssize((unsigned char *) title, strlen(title), encoding, &tmp, &height, NULL); ! if (tmp >= total_w) ! fprintf(fout, "%s\n", title); else ! fprintf(fout, "%-*s%s\n", (total_w - tmp) / 2, "", title); } /* print headers */ --- 566,652 ---- else lineptr_list = NULL; + + /* Default word wrap to the full width, i.e. no word wrap */ + for (i = 0; i < col_count; i++) + width_wrap[i] = width_max[i]; + + /* + * Optional optimized word wrap. Shrink columns with a high max/avg ratio. + * Slighly bias against wider columns (increases chance a narrow column + * will fit in its cell) + */ + if (opt->format == PRINT_WRAP) + { + /* If we can get the terminal width */ + char *env = getenv("COLUMNS"); + + if (env != NULL) + tcolumns = atoi(env); + #ifdef TIOCGWINSZ + else + { + struct winsize screen_size; + + if (ioctl(fileno(stdout), TIOCGWINSZ, &screen_size) != -1) + tcolumns = screen_size.ws_col; + } + #endif + + if (tcolumns > 0) + { + /* Shink high ratio columns */ + while (width_total > tcolumns) + { + double max_ratio = 0, prev_max_ratio = 0; + double curr_ratio = 0; + int worst_col = -1; + + /* + * Find column that has the highest ratio of its maximum + * width compared to its average width. This tells us which + * column will produce the fewest wrapped values if shortened. + * width_wrap starts as equal to width_max. + */ + for (i = 0; i < col_count; i++) + if (width_average[i] && width_wrap[i] > width_header[i]) + { + if (COL_RATIO(i) > max_ratio) + { + prev_max_ratio = max_ratio; + max_ratio = curr_ratio; + worst_col = i; + } + } + + /* Exit loop if we can't squeeze any more. */ + if (worst_col < 0) + break; + + /* Squeeze the worst column. Lather, rinse, repeat */ + do + { + width_wrap[worst_col]--; + width_total--; + } while (width_total > tcolumns && COL_RATIO(i) >= prev_max_ratio); + } + } + } + + /* time to output */ if (opt->start_table) { /* print title */ if (title && !opt_tuples_only) { ! int width, ! height; ! pg_wcssize((unsigned char *) title, strlen(title), encoding, &width, &height, NULL); ! if (width >= width_total) ! fprintf(fout, "%s\n", title); /* Aligned */ else ! fprintf(fout, "%-*s%s\n", (width_total - width) / 2, "", title); /* Centered */ } /* print headers */ *************** *** 557,563 **** int line_count; if (opt_border == 2) ! _print_horizontal_line(col_count, widths, opt_border, fout); for (i = 0; i < col_count; i++) pg_wcsformat((unsigned char *) headers[i], strlen(headers[i]), encoding, col_lineptrs[i], heights[i]); --- 656,662 ---- int line_count; if (opt_border == 2) ! _print_horizontal_line(col_count, width_wrap, opt_border, fout); for (i = 0; i < col_count; i++) pg_wcsformat((unsigned char *) headers[i], strlen(headers[i]), encoding, col_lineptrs[i], heights[i]); *************** *** 580,586 **** if (!complete[i]) { ! nbspace = widths[i] - this_line->width; /* centered */ fprintf(fout, "%-*s%s%-*s", --- 679,685 ---- if (!complete[i]) { ! nbspace = width_wrap[i] - this_line->width; /* centered */ fprintf(fout, "%-*s%s%-*s", *************** *** 593,599 **** } } else ! fprintf(fout, "%*s", widths[i], ""); if (i < col_count - 1) { if (opt_border == 0) --- 692,698 ---- } } else ! fprintf(fout, "%*s", width_wrap[i], ""); if (i < col_count - 1) { if (opt_border == 0) *************** *** 611,711 **** fputc('\n', fout); } ! _print_horizontal_line(col_count, widths, opt_border, fout); } } /* print cells */ for (i = 0, ptr = cells; *ptr; i += col_count, ptr += col_count) { ! int j; ! int cols_todo = col_count; ! int line_count; /* Number of lines output so far in row */ if (cancel_pressed) break; for (j = 0; j < col_count; j++) pg_wcsformat((unsigned char *) ptr[j], strlen(ptr[j]), encoding, col_lineptrs[j], heights[j]); ! line_count = 0; memset(complete, 0, col_count * sizeof(int)); ! while (cols_todo) { ! /* beginning of line */ ! if (opt_border == 2) ! fputs("| ", fout); ! else if (opt_border == 1) ! fputc(' ', fout); ! ! for (j = 0; j < col_count; j++) { ! struct lineptr *this_line = col_lineptrs[j] + line_count; ! bool finalspaces = (opt_border == 2 || j != col_count - 1); ! if (complete[j]) /* Just print spaces... */ ! { ! if (finalspaces) ! fprintf(fout, "%*s", widths[j], ""); ! } ! else { ! /* content */ ! if (opt_align[j] == 'r') { ! if (opt_numeric_locale) { ! /* ! * Assumption: This code used only on strings ! * without multibyte characters, otherwise ! * this_line->width < strlen(this_ptr) and we get ! * an overflow ! */ ! char *my_cell = format_numeric_locale((char *) this_line->ptr); ! ! fprintf(fout, "%*s%s", ! (int) (widths[i % col_count] - strlen(my_cell)), "", ! my_cell); ! free(my_cell); } else ! fprintf(fout, "%*s%s", ! widths[j] - this_line->width, "", ! this_line->ptr); } ! else ! fprintf(fout, "%-s%*s", this_line->ptr, ! finalspaces ? (widths[j] - this_line->width) : 0, ""); ! /* If at the right height, done this col */ ! if (line_count == heights[j] - 1 || !this_line[1].ptr) { ! complete[j] = 1; ! cols_todo--; } } ! /* divider */ ! if ((j + 1) % col_count) { ! if (opt_border == 0) ! fputc(' ', fout); ! else if (line_count == 0) ! fputs(" | ", fout); ! else ! fprintf(fout, " %c ", complete[j + 1] ? ' ' : ':'); } } - if (opt_border == 2) - fputs(" |", fout); - fputc('\n', fout); - line_count++; } } if (opt->stop_table) { if (opt_border == 2 && !cancel_pressed) ! _print_horizontal_line(col_count, widths, opt_border, fout); /* print footers */ if (footers && !opt_tuples_only && !cancel_pressed) --- 710,857 ---- fputc('\n', fout); } ! _print_horizontal_line(col_count, width_wrap, opt_border, fout); } } /* print cells */ for (i = 0, ptr = cells; *ptr; i += col_count, ptr += col_count) { ! bool line_todo, ! cols_todo; ! int line_count; if (cancel_pressed) break; + /* + * Format each cell. Format again, it is a numeric formatting locale + * (e.g. 123,456 vs. 123456) + */ for (j = 0; j < col_count; j++) + { pg_wcsformat((unsigned char *) ptr[j], strlen(ptr[j]), encoding, col_lineptrs[j], heights[j]); + if (opt_numeric_locale && opt_align[j % col_count] == 'r') + { + char *my_cell; ! my_cell = format_numeric_locale((char *) col_lineptrs[j]->ptr); ! strcpy((char *) col_lineptrs[j]->ptr, my_cell); /* Buffer IS large ! * enough... now */ ! free(my_cell); ! } ! } ! ! /* Print rows to console */ memset(complete, 0, col_count * sizeof(int)); ! line_count = 0; ! line_todo = true; ! while (line_todo) { ! cols_todo = true; ! while (cols_todo) { ! char border_cell = '*'; ! cols_todo = false; ! ! /* left border */ ! if (opt_border == 2) ! fputs("| ", fout); ! else if (opt_border == 1) ! fputc(' ', fout); ! ! /* for each column */ ! for (j = 0; j < col_count; j++) { ! struct lineptr *this_line = &col_lineptrs[j][line_count]; ! ! if (heights[j] <= line_count) /* Blank column content */ ! { ! fprintf(fout, "%*s", width_wrap[j], ""); ! border_cell = '|'; ! } ! else if (opt_align[j] == 'r') /* Right aligned cell */ { ! int strlen_remaining = strlen((char *) this_line->ptr + complete[j]); ! ! if (strlen_remaining > width_wrap[j]) { ! fprintf(fout, "%.*s", (int) width_wrap[j], this_line->ptr + complete[j]); ! complete[j] += width_wrap[j]; /* We've done THIS much */ ! cols_todo = true; /* And there is more to do... */ ! border_cell = ':'; } else ! { ! fprintf(fout, "%*s", width_wrap[j] - strlen_remaining, ""); ! fprintf(fout, "%-s", this_line->ptr + complete[j]); ! complete[j] += strlen_remaining; ! border_cell = '|'; ! } } ! else /* Left aligned cell */ ! { ! int strlen_remaining = strlen((char *) this_line->ptr + complete[j]); ! ! if (strlen_remaining > width_wrap[j]) ! { ! fprintf(fout, "%.*s", (int) width_wrap[j], this_line->ptr + complete[j]); ! complete[j] += width_wrap[j]; /* We've done THIS much */ ! cols_todo = true; /* And there is more to do... */ ! border_cell = ':'; ! } ! else ! { ! fprintf(fout, "%-s", this_line->ptr + complete[j]); ! fprintf(fout, "%*s", width_wrap[j] - strlen_remaining, ""); ! complete[j] += strlen_remaining; ! border_cell = '|'; ! } ! } ! ! /* print a divider, middle of columns only */ ! if ((j + 1) % col_count) { ! if (opt_border == 0) ! fputc(' ', fout); ! else if (line_count == 0) ! fprintf(fout, " %c ", border_cell); ! else ! fprintf(fout, " %c ", complete[j + 1] ? ' ' : ':'); } } ! /* end of row border */ ! if (opt_border == 2) ! fprintf(fout, " %c", border_cell); ! fputc('\n', fout); ! } ! ! /* ! * Check if any columns have line continuations due to \n in the ! * cell. ! */ ! line_count++; ! line_todo = false; ! for (j = 0; j < col_count; j++) ! { ! if (line_count < heights[j]) { ! if (col_lineptrs[j][line_count].ptr) ! { ! line_todo = true; ! complete[j] = 0; ! } } } } } if (opt->stop_table) { if (opt_border == 2 && !cancel_pressed) ! _print_horizontal_line(col_count, width_wrap, opt_border, fout); /* print footers */ if (footers && !opt_tuples_only && !cancel_pressed) *************** *** 722,728 **** } /* clean up */ ! free(widths); free(heights); free(col_lineptrs); free(format_space); --- 868,877 ---- } /* clean up */ ! free(width_header); ! free(width_average); ! free(width_max); ! free(width_wrap); free(heights); free(col_lineptrs); free(format_space); *************** *** 754,760 **** dheight = 1, hformatsize = 0, dformatsize = 0; - int tmp = 0; char *divider; unsigned int cell_count = 0; struct lineptr *hlineptr, --- 903,908 ---- *************** *** 779,790 **** /* Find the maximum dimensions for the headers */ for (i = 0; i < col_count; i++) { ! int height, fs; ! pg_wcssize((unsigned char *) headers[i], strlen(headers[i]), encoding, &tmp, &height, &fs); ! if (tmp > hwidth) ! hwidth = tmp; if (height > hheight) hheight = height; if (fs > hformatsize) --- 927,939 ---- /* Find the maximum dimensions for the headers */ for (i = 0; i < col_count; i++) { ! int width, ! height, fs; ! pg_wcssize((unsigned char *) headers[i], strlen(headers[i]), encoding, &width, &height, &fs); ! if (width > hwidth) ! hwidth = width; if (height > hheight) hheight = height; if (fs > hformatsize) *************** *** 799,805 **** for (i = 0, ptr = cells; *ptr; ptr++, i++) { int numeric_locale_len; ! int height, fs; if (opt_align[i % col_count] == 'r' && opt_numeric_locale) --- 948,955 ---- for (i = 0, ptr = cells; *ptr; ptr++, i++) { int numeric_locale_len; ! int width, ! height, fs; if (opt_align[i % col_count] == 'r' && opt_numeric_locale) *************** *** 807,816 **** else numeric_locale_len = 0; ! pg_wcssize((unsigned char *) *ptr, strlen(*ptr), encoding, &tmp, &height, &fs); ! tmp += numeric_locale_len; ! if (tmp > dwidth) ! dwidth = tmp; if (height > dheight) dheight = height; if (fs > dformatsize) --- 957,966 ---- else numeric_locale_len = 0; ! pg_wcssize((unsigned char *) *ptr, strlen(*ptr), encoding, &width, &height, &fs); ! width += numeric_locale_len; ! if (width > dwidth) ! dwidth = width; if (height > dheight) dheight = height; if (fs > dformatsize) *************** *** 1879,1884 **** --- 2029,2035 ---- opt, output); break; case PRINT_ALIGNED: + case PRINT_WRAP: if (opt->expanded) print_aligned_vertical(title, headers, cells, footers, align, opt, output); Index: src/bin/psql/print.h =================================================================== RCS file: /cvsroot/pgsql/src/bin/psql/print.h,v retrieving revision 1.35 diff -c -c -r1.35 print.h *** src/bin/psql/print.h 1 Jan 2008 19:45:56 -0000 1.35 --- src/bin/psql/print.h 17 Apr 2008 14:05:40 -0000 *************** *** 21,26 **** --- 21,27 ---- PRINT_NOTHING = 0, /* to make sure someone initializes this */ PRINT_UNALIGNED, PRINT_ALIGNED, + PRINT_WRAP, PRINT_HTML, PRINT_LATEX, PRINT_TROFF_MS
Bryce Nesbitt wrote: > I've attached a patch, against current 8.4 cvs, which optionally sets a > maximum width for psql output: Some random comments: * Don't use C++ style comments (//). Some compilers don't like these. * Beware of brace position: we use braces on their own, indented at the start of a new line, so ! while(--count) { ! lines++; ! lines->ptr = NULL; ! lines->width = 0; ! } becomes ! while(--count) ! { ! lines++; ! lines->ptr = NULL; ! lines->width = 0; ! } (with correct indentation anyway) * Always use tabs, not spaces, to indent. Tabs are 4 spaces wide. * Don't use double stars in comments. * We're not in the habit of giving credit in code comments. It gets messy fast. * Don't lose warning comments like this one (unless you've removed the assumption of course) /* * Assumption: This code used only on strings * without multibyte characters, otherwise * this_line->width < strlen(this_ptr) and we get * an overflow */ In fact I wonder if you've introduced this assumption in the other case on that code (i.e. when alignment is not 'r'). I'm not seeing any checks for multibytes in there, but perhaps I'm missing it. * "} else" is forbidden too. Use two separate lines. -- Alvaro Herrera http://www.CommandPrompt.com/ The PostgreSQL Company - Command Prompt, Inc.
Alvaro is correct. I made most or all of these adjustments in the updated version I posted yesterday. --------------------------------------------------------------------------- Alvaro Herrera wrote: > Bryce Nesbitt wrote: > > I've attached a patch, against current 8.4 cvs, which optionally sets a > > maximum width for psql output: > > Some random comments: > > * Don't use C++ style comments (//). Some compilers don't like these. > > * Beware of brace position: we use braces on their own, indented at the > start of a new line, so > > ! while(--count) { > ! lines++; > ! lines->ptr = NULL; > ! lines->width = 0; > ! } > > becomes > > > ! while(--count) > ! { > ! lines++; > ! lines->ptr = NULL; > ! lines->width = 0; > ! } > > (with correct indentation anyway) > > > * Always use tabs, not spaces, to indent. Tabs are 4 spaces wide. > > * Don't use double stars in comments. > > * We're not in the habit of giving credit in code comments. It gets > messy fast. > > * Don't lose warning comments like this one (unless you've removed the > assumption of course) > > /* > * Assumption: This code used only on strings > * without multibyte characters, otherwise > * this_line->width < strlen(this_ptr) and we get > * an overflow > */ > > In fact I wonder if you've introduced this assumption in the other case > on that code (i.e. when alignment is not 'r'). I'm not seeing any > checks for multibytes in there, but perhaps I'm missing it. > > > * "} else" is forbidden too. Use two separate lines. > > -- > Alvaro Herrera http://www.CommandPrompt.com/ > The PostgreSQL Company - Command Prompt, Inc. -- Bruce Momjian <bruce@momjian.us> http://momjian.us EnterpriseDB http://enterprisedb.com + If your life is a hard drive, Christ can be your backup. +
Bruce Momjian wrote: > > Alvaro is correct. I made most or all of these adjustments in the > updated version I posted yesterday. Doh. I didn't realize you had posted a new version :-( People is complaining here that we don't teach people here anyway, so hopefully my comments were still useful :-) -- Alvaro Herrera http://www.CommandPrompt.com/ The PostgreSQL Company - Command Prompt, Inc.
Alvaro Herrera wrote: > Bruce Momjian wrote: > > > > Alvaro is correct. I made most or all of these adjustments in the > > updated version I posted yesterday. > > Doh. I didn't realize you had posted a new version :-( > > People is complaining here that we don't teach people here anyway, so > hopefully my comments were still useful :-) Oh, yea, certainly. I didn't mention it to the author at first because it was his first patch, and he did a _very_ nice job considering the complexity of what he was doing. -- Bruce Momjian <bruce@momjian.us> http://momjian.us EnterpriseDB http://enterprisedb.com + If your life is a hard drive, Christ can be your backup. +
"Peter Eisentraut" <peter_e@gmx.net> writes: > Bruce Momjian wrote: >> I checked the use of COLUMNS and it seems bash updates the environment >> variable when a window is resized. I added ioctl(TIOCGWINSZ) if COLUMNS >> isn't set. We already had a call in print.c for detecting the >> number of rows on the screen to determine if the pager should >> be used. Seems COLUMNS should take precedence over ioctl(), right? > > Considering that the code to determine the row count is undisputed so far, the > column count detection should work the same. That is, we might not need to > look at COLUMNS at all. Unless there is a use case for overriding the column > count (instead of just turning off the wrapping). I do that all the time. I normally am running under an emacs terminal so I don't know what width the ioctl's going to get back but it's unlikely to be right. In any case I may want to format the output to a width narrower than the window because I'm going to narrow it. Also, how would you suggest figuring the width to use for output going to a file? ioctl is irrelevant in that case, imho it should just default to 80 columns if COLUMNS is unset. -- Gregory Stark EnterpriseDB http://www.enterprisedb.com Ask me about EnterpriseDB's Slony Replication support!
Gregory Stark <stark@enterprisedb.com> writes: > Also, how would you suggest figuring the width to use for output going to a > file? ioctl is irrelevant in that case, imho it should just default to 80 > columns if COLUMNS is unset. It would be a spectacularly awful idea for this patch to affect the output to a file at all. regards, tom lane
"Tom Lane" <tgl@sss.pgh.pa.us> writes: > Gregory Stark <stark@enterprisedb.com> writes: >> Also, how would you suggest figuring the width to use for output going to a >> file? ioctl is irrelevant in that case, imho it should just default to 80 >> columns if COLUMNS is unset. > > It would be a spectacularly awful idea for this patch to affect the > output to a file at all. It's a compromise of convenience over principle to allow the default output format to vary but I would still want to have the same set of output formats _available_ to me regardless of whether I'm redirecting to a file or not. Much like ls -C is available even if you're redirecting to a file and -1 is available if you're on a terminal. It sucks to run a program, decide you want to capture that output and find you get something else. It *really* sucks to find there's just no way to get the same output short of heroic efforts. I also have the converse problem occasionally. I run everything under emacs and occasionally run into programs which default to awkward output formats. Usually it's not too bad because it's still on a pty but the window width is a particular one which confuses programs. -- Gregory Stark EnterpriseDB http://www.enterprisedb.com Ask me about EnterpriseDB's RemoteDBA services!
<br /><blockquote cite="mid:871w53bzsi.fsf@oxford.xeocode.com" type="cite"><blockquote type="cite"><pre wrap="">It wouldbe a spectacularly awful idea for this patch to affect the output to a file at all. </pre></blockquote><pre wrap="">..... It sucks to run a program, decide you want to capture that output and find you get something else. It *really* sucks to find there's just no way to get the same output short of heroic efforts.</pre></blockquote> I agree with Gregory here: I may want to capture either the wrappedor unwrapped output to a file or a pipe.<br /> Perhaps the enabling flag for this feature should take a parameter,which is the number of columns to wrap to.<br /><br /> I was not bold enough to propose that wrapping be the defaultbehavior for the terminal.<br /> And there's no way I'd want wrapping as the default for pipe output.<br /><br /> -Bryce<br /><br />
<br /><br /> Peter Eisentraut wrote: <blockquote cite="mid:200804171509.25472.peter_e@gmx.net" type="cite"><pre wrap="">BruceMomjian wrote: </pre><blockquote type="cite"><pre wrap="">I checked the use of COLUMNS and it seems bash updatesthe environment variable when a window is resized. I added ioctl(TIOCGWINSZ) if COLUMNS isn't set. We already had a call in print.c for detecting the number of rows on the screen to determine if the pager should be used. Seems COLUMNS should take precedence over ioctl(), right? </pre></blockquote><pre wrap=""> Considering that the code to determine the row count is undisputed so far, the column count detection should work the same. That is, we might not need to look at COLUMNS at all. Unless there is a use case for overriding the column count (instead of just turning off the wrapping). </pre></blockquote> I asked the folks over at "Experts Exchange" to testthe behavior of the ioctl and $COLUMNS on various platforms. I'd been told that I would face huge problems if a consolewas resized. But the results were pretty consistent, and nothing had problems with resize: <a class="moz-txt-link-freetext" href="http://www.experts-exchange.com/Programming/Open_Source/Q_23243646.html">http://www.experts-exchange.com/Programming/Open_Source/Q_23243646.html</a><br /><br/> It appears impossible to override $COLUMNS, on some platforms as the readline call sets it.<br /> On many platforms$COLUMNS is null until the call to readline.<br /> OSX does not set $COLUMNS at all.<br /><br /> -Bryce<br /><br />
<br /> Bruce Momjian wrote:<br /><blockquote cite="mid:200804171407.m3HE7EW24318@momjian.us" type="cite"><blockquote type="cite"><prewrap="">I also found that for hugely wide output it was better to give up (do nothing), rather than mangle the output in a futile attempt to squash it to the window width. So there is one more clause in the wrapping if. </pre></blockquote><pre wrap=""> Was it because of performance? I have a way to fix that by decrementing by more than one to shrink a column? I am attaching a new patch with my improved loop. It remembers the previous maximum ratio.</pre></blockquote> No! Performance was not the issue.<br /> Theout just looked like a jumble onscreen when the line was word wrapped BUT did not fit on the screen anyway.<br /><br />To increase the number of layouts that fit, a co-worker suggested I squeeze out the 2 spaces in each column header.<br/><br /> -Bryce<br /><br />
Bryce Nesbitt <bryce2@obviously.com> writes: > <pre wrap="">I checked the use of COLUMNS and it seems bash updates the environment > variable when a window is resized. [ Please get rid of the HTML formatting ... ] Bash can update the environment all it wants, but that will not affect what is seen by a program that's already running. Personally I often resize the window while psql is running, and I expect that to work. I'm with Peter on this one: we have used ioctl, and nothing else, to determine the vertical window dimension for many years now, to the tune of approximately zero complaints. It's going to take one hell of a strong argument to persuade me that determination of the horizontal dimension should not work exactly the same way. regards, tom lane
Alvaro Herrera wrote: > * Don't lose warning comments like this one (unless you've removed the > assumption of course) > > /* > * Assumption: This code used only on strings > * without multibyte characters, otherwise > * this_line->width < strlen(this_ptr) and we get > * an overflow > */ In fact, that particular assumption was causing a problem, causing a segfault. I can't be certain, because the multibyte stuff is pretty intense, but I think I nailed it. Thanks for all your comments!
Alvaro Herrera wrote: > People [are] complaining here that we don't teach people here anyway, so > hopefully my comments were still useful :-) > Yes they are useful. As a new patcher, where should I look for coding standards? How about a little FAQ at the top of the CVS source tree? Though, darn it, I sure like // And my vi is set to: set sw=4 set ts=4 set expandtab Because my corporate projects require spaces not tabs. > Some random comments: > > * Don't use C++ style comments (//). Some compilers don't like these. > > * Beware of brace position: we use braces on their own, indented at the > start of a new line, so > > ! while(--count) { > ! lines++; > ! lines->ptr = NULL; > ! lines->width = 0; > ! } > > becomes > > > ! while(--count) > ! { > ! lines++; > ! lines->ptr = NULL; > ! lines->width = 0; > ! } > > (with correct indentation anyway) > > > * Always use tabs, not spaces, to indent. Tabs are 4 spaces wide. > > * Don't use double stars in comments. > > * "} else" is forbidden too. Use two separate lines.
Bruce Momjian wrote: > I don't think you need to do that to get it applied --- there is nothing > windows-specific in your code. > > Is this ready to be applied? Do you want to send a final update or are > you still testing? > Looks good, but I suggest adding "give up if the header is too wide": if (tcolumns > 0 && tcolumns >= total_header_width) /* * psql - the PostgreSQL interactive terminal * * Copyright (c) 2000-2008, PostgreSQL Global Development Group * * $PostgreSQL: pgsql/src/bin/psql/print.c,v 1.97 2008/03/27 03:57:34 tgl Exp $ */ #include "postgres_fe.h" #include "print.h" #include "catalog/pg_type.h" #include <math.h> #include <signal.h> #include <unistd.h> #ifndef WIN32 #include <sys/ioctl.h> /* for ioctl() */ #endif #ifdef HAVE_TERMIOS_H #include <termios.h> #endif #include <locale.h> #include "pqsignal.h" #include "mbprint.h" /* * We define the cancel_pressed flag in this file, rather than common.c where * it naturally belongs, because this file is also used by non-psql programs * (see the bin/scripts/ directory). In those programs cancel_pressed will * never become set and will have no effect. * * Note: print.c's general strategy for when to check cancel_pressed is to do * so at completion of each row of output. */ volatile bool cancel_pressed = false; static char *decimal_point; static char *grouping; static char *thousands_sep; #define COL_RATIO(col_num) \ ((double) width_wrap[col_num] / width_average[col_num] + \ width_max[col_num] * 0.01) /* Penalize wide columns */ static void * pg_local_malloc(size_t size) { void *tmp; tmp = malloc(size); if (!tmp) { fprintf(stderr, _("out of memory\n")); exit(EXIT_FAILURE); } return tmp; } static void * pg_local_calloc(int count, size_t size) { void *tmp; tmp = calloc(count, size); if (!tmp) { fprintf(stderr, _("out of memory\n")); exit(EXIT_FAILURE); } return tmp; } static int integer_digits(const char *my_str) { int frac_len; if (my_str[0] == '-') my_str++; frac_len = strchr(my_str, '.') ? strlen(strchr(my_str, '.')) : 0; return strlen(my_str) - frac_len; } /* Return additional length required for locale-aware numeric output */ static int additional_numeric_locale_len(const char *my_str) { int int_len = integer_digits(my_str), len = 0; int groupdigits = atoi(grouping); if (int_len > 0) /* Don't count a leading separator */ len = (int_len / groupdigits - (int_len % groupdigits == 0)) * strlen(thousands_sep); if (strchr(my_str, '.') != NULL) len += strlen(decimal_point) - strlen("."); return len; } static int strlen_with_numeric_locale(const char *my_str) { return strlen(my_str) + additional_numeric_locale_len(my_str); } /* Returns the appropriately formatted string in a new allocated block, caller must free */ static char * format_numeric_locale(const char *my_str) { int i, j, int_len = integer_digits(my_str), leading_digits; int groupdigits = atoi(grouping); int new_str_start = 0; char *new_str = new_str = pg_local_malloc( strlen_with_numeric_locale(my_str) + 1); leading_digits = (int_len % groupdigits != 0) ? int_len % groupdigits : groupdigits; if (my_str[0] == '-') /* skip over sign, affects grouping * calculations */ { new_str[0] = my_str[0]; my_str++; new_str_start = 1; } for (i = 0, j = new_str_start;; i++, j++) { /* Hit decimal point? */ if (my_str[i] == '.') { strcpy(&new_str[j], decimal_point); j += strlen(decimal_point); /* add fractional part */ strcpy(&new_str[j], &my_str[i] + 1); break; } /* End of string? */ if (my_str[i] == '\0') { new_str[j] = '\0'; break; } /* Add separator? */ if (i != 0 && (i - leading_digits) % groupdigits == 0) { strcpy(&new_str[j], thousands_sep); j += strlen(thousands_sep); } new_str[j] = my_str[i]; } return new_str; } /*************************/ /* Unaligned text */ /*************************/ static void print_unaligned_text(const char *title, const char *const * headers, const char *const * cells, const char *const * footers, const char *opt_align, const printTableOpt *opt, FILE *fout) { const char *opt_fieldsep = opt->fieldSep; const char *opt_recordsep = opt->recordSep; bool opt_tuples_only = opt->tuples_only; bool opt_numeric_locale = opt->numericLocale; unsigned int col_count = 0; unsigned int i; const char *const * ptr; bool need_recordsep = false; if (cancel_pressed) return; if (!opt_fieldsep) opt_fieldsep = ""; if (!opt_recordsep) opt_recordsep = ""; /* count columns */ for (ptr = headers; *ptr; ptr++) col_count++; if (opt->start_table) { /* print title */ if (!opt_tuples_only && title) fprintf(fout, "%s%s", title, opt_recordsep); /* print headers */ if (!opt_tuples_only) { for (ptr = headers; *ptr; ptr++) { if (ptr != headers) fputs(opt_fieldsep, fout); fputs(*ptr, fout); } need_recordsep = true; } } else /* assume continuing printout */ need_recordsep = true; /* print cells */ for (i = 0, ptr = cells; *ptr; i++, ptr++) { if (need_recordsep) { fputs(opt_recordsep, fout); need_recordsep = false; if (cancel_pressed) break; } if (opt_align[i % col_count] == 'r' && opt_numeric_locale) { char *my_cell = format_numeric_locale(*ptr); fputs(my_cell, fout); free(my_cell); } else fputs(*ptr, fout); if ((i + 1) % col_count) fputs(opt_fieldsep, fout); else need_recordsep = true; } /* print footers */ if (opt->stop_table) { if (!opt_tuples_only && footers && !cancel_pressed) for (ptr = footers; *ptr; ptr++) { if (need_recordsep) { fputs(opt_recordsep, fout); need_recordsep = false; } fputs(*ptr, fout); need_recordsep = true; } /* the last record needs to be concluded with a newline */ if (need_recordsep) fputc('\n', fout); } } static void print_unaligned_vertical(const char *title, const char *const * headers, const char *const * cells, const char *const * footers, const char *opt_align, const printTableOpt *opt, FILE *fout) { const char *opt_fieldsep = opt->fieldSep; const char *opt_recordsep = opt->recordSep; bool opt_tuples_only = opt->tuples_only; bool opt_numeric_locale = opt->numericLocale; unsigned int col_count = 0; unsigned int i; const char *const * ptr; bool need_recordsep = false; if (cancel_pressed) return; if (!opt_fieldsep) opt_fieldsep = ""; if (!opt_recordsep) opt_recordsep = ""; /* count columns */ for (ptr = headers; *ptr; ptr++) col_count++; if (opt->start_table) { /* print title */ if (!opt_tuples_only && title) { fputs(title, fout); need_recordsep = true; } } else /* assume continuing printout */ need_recordsep = true; /* print records */ for (i = 0, ptr = cells; *ptr; i++, ptr++) { if (need_recordsep) { /* record separator is 2 occurrences of recordsep in this mode */ fputs(opt_recordsep, fout); fputs(opt_recordsep, fout); need_recordsep = false; if (cancel_pressed) break; } fputs(headers[i % col_count], fout); fputs(opt_fieldsep, fout); if (opt_align[i % col_count] == 'r' && opt_numeric_locale) { char *my_cell = format_numeric_locale(*ptr); fputs(my_cell, fout); free(my_cell); } else fputs(*ptr, fout); if ((i + 1) % col_count) fputs(opt_recordsep, fout); else need_recordsep = true; } if (opt->stop_table) { /* print footers */ if (!opt_tuples_only && footers && *footers && !cancel_pressed) { fputs(opt_recordsep, fout); for (ptr = footers; *ptr; ptr++) { fputs(opt_recordsep, fout); fputs(*ptr, fout); } } fputc('\n', fout); } } /********************/ /* Aligned text */ /********************/ /* draw "line" */ static void _print_horizontal_line(const unsigned int col_count, const unsigned int *widths, unsigned short border, FILE *fout) { unsigned int i, j; if (border == 1) fputc('-', fout); else if (border == 2) fputs("+-", fout); for (i = 0; i < col_count; i++) { for (j = 0; j < widths[i]; j++) fputc('-', fout); if (i < col_count - 1) { if (border == 0) fputc(' ', fout); else fputs("-+-", fout); } } if (border == 2) fputs("-+", fout); else if (border == 1) fputc('-', fout); fputc('\n', fout); } /* * Prety pretty boxes around cells. */ static void print_aligned_text(const char *title, const char *const * headers, const char *const * cells, const char *const * footers, const char *opt_align, const printTableOpt *opt, FILE *fout) { bool opt_tuples_only = opt->tuples_only; bool opt_numeric_locale = opt->numericLocale; int encoding = opt->encoding; unsigned short int opt_border = opt->border; unsigned int col_count = 0, cell_count = 0; unsigned int i, j; unsigned int *width_header, *width_max, *width_wrap, *width_average; unsigned int *heights, *format_space; unsigned char **format_buf; unsigned int width_total; unsigned int total_header_width; const char *const * ptr; struct lineptr **col_lineptrs; /* pointers to line pointer per column */ struct lineptr *lineptr_list; /* complete list of linepointers */ int *complete; /* Array remembering which columns have * completed output */ int tcolumns = 0; /* Width of interactive console */ if (cancel_pressed) return; if (opt_border > 2) opt_border = 2; /* count columns */ for (ptr = headers; *ptr; ptr++) col_count++; if (col_count > 0) { width_header = pg_local_calloc(col_count, sizeof(*width_header)); width_average = pg_local_calloc(col_count, sizeof(*width_average)); width_max = pg_local_calloc(col_count, sizeof(*width_max)); width_wrap = pg_local_calloc(col_count, sizeof(*width_wrap)); heights = pg_local_calloc(col_count, sizeof(*heights)); col_lineptrs = pg_local_calloc(col_count, sizeof(*col_lineptrs)); format_space = pg_local_calloc(col_count, sizeof(*format_space)); format_buf = pg_local_calloc(col_count, sizeof(*format_buf)); complete = pg_local_calloc(col_count, sizeof(*complete)); } else { width_header = NULL; width_average = NULL; width_max = NULL; width_wrap = NULL; heights = NULL; col_lineptrs = NULL; format_space = NULL; format_buf = NULL; complete = NULL; } /* scan all column headers, find maximum width */ for (i = 0; i < col_count; i++) { /* Get width & height */ int width, height, space; pg_wcssize((unsigned char *) headers[i], strlen(headers[i]), encoding, &width, &height, &space); if (width > width_max[i]) width_max[i] = width; if (height > heights[i]) heights[i] = height; if (space > format_space[i]) format_space[i] = space; width_header[i] = width; } /* scan all rows, find maximum width, compute cell_count */ for (i = 0, ptr = cells; *ptr; ptr++, i++, cell_count++) { int width, height, space; /* Get width, ignore height */ pg_wcssize((unsigned char *) *ptr, strlen(*ptr), encoding, &width, &height, &space); if (opt_numeric_locale && opt_align[i % col_count] == 'r') { width += additional_numeric_locale_len(*ptr); space += additional_numeric_locale_len(*ptr); } if (width > width_max[i % col_count]) width_max[i % col_count] = width; if (height > heights[i % col_count]) heights[i % col_count] = height; if (space > format_space[i % col_count]) format_space[i % col_count] = space; width_average[i % col_count] += width; } /* If we have rows, compute average */ if (col_count != 0 && cell_count != 0) { int rows = cell_count / col_count; for (i = 0; i < col_count; i++) width_average[i % col_count] /= rows; } /* adjust the total display width based on border style */ if (opt_border == 0) width_total = col_count - 1; else if (opt_border == 1) width_total = col_count * 3 - 1; else width_total = col_count * 3 + 1; total_header_width = width_total; for (i = 0; i < col_count; i++) { width_total += width_max[i]; total_header_width += width_header[i]; } /* * At this point: width_max[] contains the max width of each column, * heights[] contains the max number of lines in each column, * format_space[] contains the maximum storage space for formatting * strings, width_total contains the giant width sum. Now we allocate * some memory... */ if (col_count > 0) { int height_total = 0; struct lineptr *lineptr; for (i = 0; i < col_count; i++) height_total += heights[i]; lineptr = lineptr_list = pg_local_calloc(height_total, sizeof(*lineptr_list)); for (i = 0; i < col_count; i++) { col_lineptrs[i] = lineptr; lineptr += heights[i]; format_buf[i] = pg_local_malloc(format_space[i] + 1); col_lineptrs[i]->ptr = format_buf[i]; } } else lineptr_list = NULL; /* Default word wrap to the full width, i.e. no word wrap */ for (i = 0; i < col_count; i++) width_wrap[i] = width_max[i]; /* * Optional optimized word wrap. Shrink columns with a high max/avg ratio. * Slighly bias against wider columns (increases chance a narrow column * will fit in its cell) */ if (opt->format == PRINT_WRAP) { /* If we can get the terminal width */ char *env = getenv("COLUMNS"); if (env != NULL) tcolumns = atoi(env); #ifdef TIOCGWINSZ else { struct winsize screen_size; if (ioctl(fileno(stdout), TIOCGWINSZ, &screen_size) != -1) tcolumns = screen_size.ws_col; } #endif /* * If available columns is positive... * and greater than the width of the unshrinkable column headers */ if (tcolumns > 0 && tcolumns >= total_header_width) { /* While there is still excess width... */ while (width_total > tcolumns) { double max_ratio = 0, prev_max_ratio = 0; double curr_ratio = 0; int worst_col = -1; /* * Find column that has the highest ratio of its maximum * width compared to its average width. This tells us which * column will produce the fewest wrapped values if shortened. * width_wrap starts as equal to width_max. */ for (i = 0; i < col_count; i++) if (width_average[i] && width_wrap[i] > width_header[i]) { if (COL_RATIO(i) > max_ratio) { prev_max_ratio = max_ratio; max_ratio = curr_ratio; worst_col = i; } } /* Exit loop if we can't squeeze any more. */ if (worst_col < 0) break; /* Squeeze the worst column. Lather, rinse, repeat */ do { width_wrap[worst_col]--; width_total--; } while (width_total > tcolumns && COL_RATIO(i) >= prev_max_ratio); } } } /* time to output */ if (opt->start_table) { /* print title */ if (title && !opt_tuples_only) { int width, height; pg_wcssize((unsigned char *) title, strlen(title), encoding, &width, &height, NULL); if (width >= width_total) fprintf(fout, "%s\n", title); /* Aligned */ else fprintf(fout, "%-*s%s\n", (width_total - width) / 2, "", title); /* Centered */ } /* print headers */ if (!opt_tuples_only) { int cols_todo; int line_count; if (opt_border == 2) _print_horizontal_line(col_count, width_wrap, opt_border, fout); for (i = 0; i < col_count; i++) pg_wcsformat((unsigned char *) headers[i], strlen(headers[i]), encoding, col_lineptrs[i], heights[i]); cols_todo = col_count; line_count = 0; memset(complete, 0, col_count * sizeof(int)); while (cols_todo) { if (opt_border == 2) fprintf(fout, "|%c", line_count ? '+' : ' '); else if (opt_border == 1) fputc(line_count ? '+' : ' ', fout); for (i = 0; i < col_count; i++) { unsigned int nbspace; struct lineptr *this_line = col_lineptrs[i] + line_count; if (!complete[i]) { nbspace = width_wrap[i] - this_line->width; /* centered */ fprintf(fout, "%-*s%s%-*s", nbspace / 2, "", this_line->ptr, (nbspace + 1) / 2, ""); if (line_count == (heights[i] - 1) || !(this_line + 1)->ptr) { cols_todo--; complete[i] = 1; } } else fprintf(fout, "%*s", width_wrap[i], ""); if (i < col_count - 1) { if (opt_border == 0) fputc(line_count ? '+' : ' ', fout); else fprintf(fout, " |%c", line_count ? '+' : ' '); } } line_count++; if (opt_border == 2) fputs(" |", fout); else if (opt_border == 1) fputc(' ', fout);; fputc('\n', fout); } _print_horizontal_line(col_count, width_wrap, opt_border, fout); } } /* print cells */ for (i = 0, ptr = cells; *ptr; i += col_count, ptr += col_count) { bool line_todo, cols_todo; int line_count; if (cancel_pressed) break; /* * Format each cell. Format again, it is a numeric formatting locale * (e.g. 123,456 vs. 123456) */ for (j = 0; j < col_count; j++) { pg_wcsformat((unsigned char *) ptr[j], strlen(ptr[j]), encoding, col_lineptrs[j], heights[j]); if (opt_numeric_locale && opt_align[j % col_count] == 'r') { char *my_cell; my_cell = format_numeric_locale((char *) col_lineptrs[j]->ptr); strcpy((char *) col_lineptrs[j]->ptr, my_cell); /* Buffer IS large * enough... now */ free(my_cell); } } /* Print rows to console */ memset(complete, 0, col_count * sizeof(int)); line_count = 0; line_todo = true; while (line_todo) { cols_todo = true; while (cols_todo) { char border_cell = '*'; cols_todo = false; /* left border */ if (opt_border == 2) fputs("| ", fout); else if (opt_border == 1) fputc(' ', fout); /* for each column */ for (j = 0; j < col_count; j++) { struct lineptr *this_line = &col_lineptrs[j][line_count]; if (heights[j] <= line_count) /* Blank column content */ { fprintf(fout, "%*s", width_wrap[j], ""); border_cell = '|'; } else if (opt_align[j] == 'r') /* Right aligned cell */ { int strlen_remaining = strlen((char *) this_line->ptr + complete[j]); if (strlen_remaining > width_wrap[j]) { fprintf(fout, "%.*s", (int) width_wrap[j], this_line->ptr + complete[j]); complete[j] += width_wrap[j]; /* We've done THIS much */ cols_todo = true; /* And there is more to do... */ border_cell = ':'; } else { fprintf(fout, "%*s", width_wrap[j] - strlen_remaining, ""); fprintf(fout, "%-s", this_line->ptr + complete[j]); complete[j] += strlen_remaining; border_cell = '|'; } } else /* Left aligned cell */ { int strlen_remaining = strlen((char *) this_line->ptr + complete[j]); if (strlen_remaining > width_wrap[j]) { fprintf(fout, "%.*s", (int) width_wrap[j], this_line->ptr + complete[j]); complete[j] += width_wrap[j]; /* We've done THIS much */ cols_todo = true; /* And there is more to do... */ border_cell = ':'; } else { fprintf(fout, "%-s", this_line->ptr + complete[j]); fprintf(fout, "%*s", width_wrap[j] - strlen_remaining, ""); complete[j] += strlen_remaining; border_cell = '|'; } } /* print a divider, middle of columns only */ if ((j + 1) % col_count) { if (opt_border == 0) fputc(' ', fout); else if (line_count == 0) fprintf(fout, " %c ", border_cell); else fprintf(fout, " %c ", complete[j + 1] ? ' ' : ':'); } } /* end of row border */ if (opt_border == 2) fprintf(fout, " %c", border_cell); fputc('\n', fout); } /* * Check if any columns have line continuations due to \n in the * cell. */ line_count++; line_todo = false; for (j = 0; j < col_count; j++) { if (line_count < heights[j]) { if (col_lineptrs[j][line_count].ptr) { line_todo = true; complete[j] = 0; } } } } } if (opt->stop_table) { if (opt_border == 2 && !cancel_pressed) _print_horizontal_line(col_count, width_wrap, opt_border, fout); /* print footers */ if (footers && !opt_tuples_only && !cancel_pressed) for (ptr = footers; *ptr; ptr++) fprintf(fout, "%s\n", *ptr); /* * for some reason MinGW (and MSVC) outputs an extra newline, so this * suppresses it */ #ifndef WIN32 fputc('\n', fout); #endif } /* clean up */ free(width_header); free(width_average); free(width_max); free(width_wrap); free(heights); free(col_lineptrs); free(format_space); free(complete); free(lineptr_list); for (i = 0; i < col_count; i++) free(format_buf[i]); free(format_buf); } static void print_aligned_vertical(const char *title, const char *const * headers, const char *const * cells, const char *const * footers, const char *opt_align, const printTableOpt *opt, FILE *fout) { bool opt_tuples_only = opt->tuples_only; bool opt_numeric_locale = opt->numericLocale; unsigned short int opt_border = opt->border; int encoding = opt->encoding; unsigned int col_count = 0; unsigned long record = opt->prior_records + 1; const char *const * ptr; unsigned int i, hwidth = 0, dwidth = 0, hheight = 1, dheight = 1, hformatsize = 0, dformatsize = 0; char *divider; unsigned int cell_count = 0; struct lineptr *hlineptr, *dlineptr; if (cancel_pressed) return; if (opt_border > 2) opt_border = 2; if (cells[0] == NULL && opt->start_table && opt->stop_table) { fprintf(fout, _("(No rows)\n")); return; } /* count columns */ for (ptr = headers; *ptr; ptr++) col_count++; /* Find the maximum dimensions for the headers */ for (i = 0; i < col_count; i++) { int width, height, fs; pg_wcssize((unsigned char *) headers[i], strlen(headers[i]), encoding, &width, &height, &fs); if (width > hwidth) hwidth = width; if (height > hheight) hheight = height; if (fs > hformatsize) hformatsize = fs; } /* Count cells, find their lengths */ for (ptr = cells; *ptr; ptr++) cell_count++; /* find longest data cell */ for (i = 0, ptr = cells; *ptr; ptr++, i++) { int numeric_locale_len; int width, height, fs; if (opt_align[i % col_count] == 'r' && opt_numeric_locale) numeric_locale_len = additional_numeric_locale_len(*ptr); else numeric_locale_len = 0; pg_wcssize((unsigned char *) *ptr, strlen(*ptr), encoding, &width, &height, &fs); width += numeric_locale_len; if (width > dwidth) dwidth = width; if (height > dheight) dheight = height; if (fs > dformatsize) dformatsize = fs; } /* * We now have all the information we need to setup the formatting * structures */ dlineptr = pg_local_malloc(sizeof(*dlineptr) * dheight); hlineptr = pg_local_malloc(sizeof(*hlineptr) * hheight); dlineptr->ptr = pg_local_malloc(dformatsize); hlineptr->ptr = pg_local_malloc(hformatsize); /* make horizontal border */ divider = pg_local_malloc(hwidth + dwidth + 10); divider[0] = '\0'; if (opt_border == 2) strcat(divider, "+-"); for (i = 0; i < hwidth; i++) strcat(divider, opt_border > 0 ? "-" : " "); if (opt_border > 0) strcat(divider, "-+-"); else strcat(divider, " "); for (i = 0; i < dwidth; i++) strcat(divider, opt_border > 0 ? "-" : " "); if (opt_border == 2) strcat(divider, "-+"); if (opt->start_table) { /* print title */ if (!opt_tuples_only && title) fprintf(fout, "%s\n", title); } /* print records */ for (i = 0, ptr = cells; *ptr; i++, ptr++) { int line_count, dcomplete, hcomplete; if (i % col_count == 0) { if (cancel_pressed) break; if (!opt_tuples_only) { char record_str[64]; size_t record_str_len; if (opt_border == 0) snprintf(record_str, 64, "* Record %lu", record++); else snprintf(record_str, 64, "[ RECORD %lu ]", record++); record_str_len = strlen(record_str); if (record_str_len + opt_border > strlen(divider)) fprintf(fout, "%.*s%s\n", opt_border, divider, record_str); else { char *div_copy = strdup(divider); if (!div_copy) { fprintf(stderr, _("out of memory\n")); exit(EXIT_FAILURE); } strncpy(div_copy + opt_border, record_str, record_str_len); fprintf(fout, "%s\n", div_copy); free(div_copy); } } else if (i != 0 || !opt->start_table || opt_border == 2) fprintf(fout, "%s\n", divider); } /* Format the header */ pg_wcsformat((unsigned char *) headers[i % col_count], strlen(headers[i % col_count]), encoding, hlineptr, hheight); /* Format the data */ pg_wcsformat((unsigned char *) *ptr, strlen(*ptr), encoding, dlineptr, dheight); line_count = 0; dcomplete = hcomplete = 0; while (!dcomplete || !hcomplete) { if (opt_border == 2) fputs("| ", fout); if (!hcomplete) { fprintf(fout, "%-s%*s", hlineptr[line_count].ptr, hwidth - hlineptr[line_count].width, ""); if (line_count == (hheight - 1) || !hlineptr[line_count + 1].ptr) hcomplete = 1; } else fprintf(fout, "%*s", hwidth, ""); if (opt_border > 0) fprintf(fout, " %c ", (line_count == 0) ? '|' : ':'); else fputs(" ", fout); if (!dcomplete) { if (opt_align[i % col_count] == 'r' && opt_numeric_locale) { char *my_cell = format_numeric_locale((char *) dlineptr[line_count].ptr); if (opt_border < 2) fprintf(fout, "%s\n", my_cell); else fprintf(fout, "%-s%*s |\n", my_cell, (int) (dwidth - strlen(my_cell)), ""); free(my_cell); } else { if (opt_border < 2) fprintf(fout, "%s\n", dlineptr[line_count].ptr); else fprintf(fout, "%-s%*s |\n", dlineptr[line_count].ptr, dwidth - dlineptr[line_count].width, ""); } if (line_count == dheight - 1 || !dlineptr[line_count + 1].ptr) dcomplete = 1; } else { if (opt_border < 2) fputc('\n', fout); else fprintf(fout, "%*s |\n", dwidth, ""); } line_count++; } } if (opt->stop_table) { if (opt_border == 2 && !cancel_pressed) fprintf(fout, "%s\n", divider); /* print footers */ if (!opt_tuples_only && footers && *footers && !cancel_pressed) { if (opt_border < 2) fputc('\n', fout); for (ptr = footers; *ptr; ptr++) fprintf(fout, "%s\n", *ptr); } fputc('\n', fout); } free(divider); free(hlineptr->ptr); free(dlineptr->ptr); free(hlineptr); free(dlineptr); } /**********************/ /* HTML printing ******/ /**********************/ void html_escaped_print(const char *in, FILE *fout) { const char *p; bool leading_space = true; for (p = in; *p; p++) { switch (*p) { case '&': fputs("&", fout); break; case '<': fputs("<", fout); break; case '>': fputs(">", fout); break; case '\n': fputs("<br />\n", fout); break; case '"': fputs(""", fout); break; case ' ': /* protect leading space, for EXPLAIN output */ if (leading_space) fputs(" ", fout); else fputs(" ", fout); break; default: fputc(*p, fout); } if (*p != ' ') leading_space = false; } } static void print_html_text(const char *title, const char *const * headers, const char *const * cells, const char *const * footers, const char *opt_align, const printTableOpt *opt, FILE *fout) { bool opt_tuples_only = opt->tuples_only; bool opt_numeric_locale = opt->numericLocale; unsigned short int opt_border = opt->border; const char *opt_table_attr = opt->tableAttr; unsigned int col_count = 0; unsigned int i; const char *const * ptr; if (cancel_pressed) return; /* count columns */ for (ptr = headers; *ptr; ptr++) col_count++; if (opt->start_table) { fprintf(fout, "<table border=\"%d\"", opt_border); if (opt_table_attr) fprintf(fout, " %s", opt_table_attr); fputs(">\n", fout); /* print title */ if (!opt_tuples_only && title) { fputs(" <caption>", fout); html_escaped_print(title, fout); fputs("</caption>\n", fout); } /* print headers */ if (!opt_tuples_only) { fputs(" <tr>\n", fout); for (ptr = headers; *ptr; ptr++) { fputs(" <th align=\"center\">", fout); html_escaped_print(*ptr, fout); fputs("</th>\n", fout); } fputs(" </tr>\n", fout); } } /* print cells */ for (i = 0, ptr = cells; *ptr; i++, ptr++) { if (i % col_count == 0) { if (cancel_pressed) break; fputs(" <tr valign=\"top\">\n", fout); } fprintf(fout, " <td align=\"%s\">", opt_align[(i) % col_count] == 'r' ? "right" : "left"); /* is string only whitespace? */ if ((*ptr)[strspn(*ptr, " \t")] == '\0') fputs(" ", fout); else if (opt_align[i % col_count] == 'r' && opt_numeric_locale) { char *my_cell = format_numeric_locale(*ptr); html_escaped_print(my_cell, fout); free(my_cell); } else html_escaped_print(*ptr, fout); fputs("</td>\n", fout); if ((i + 1) % col_count == 0) fputs(" </tr>\n", fout); } if (opt->stop_table) { fputs("</table>\n", fout); /* print footers */ if (!opt_tuples_only && footers && *footers && !cancel_pressed) { fputs("<p>", fout); for (ptr = footers; *ptr; ptr++) { html_escaped_print(*ptr, fout); fputs("<br />\n", fout); } fputs("</p>", fout); } fputc('\n', fout); } } static void print_html_vertical(const char *title, const char *const * headers, const char *const * cells, const char *const * footers, const char *opt_align, const printTableOpt *opt, FILE *fout) { bool opt_tuples_only = opt->tuples_only; bool opt_numeric_locale = opt->numericLocale; unsigned short int opt_border = opt->border; const char *opt_table_attr = opt->tableAttr; unsigned int col_count = 0; unsigned long record = opt->prior_records + 1; unsigned int i; const char *const * ptr; if (cancel_pressed) return; /* count columns */ for (ptr = headers; *ptr; ptr++) col_count++; if (opt->start_table) { fprintf(fout, "<table border=\"%d\"", opt_border); if (opt_table_attr) fprintf(fout, " %s", opt_table_attr); fputs(">\n", fout); /* print title */ if (!opt_tuples_only && title) { fputs(" <caption>", fout); html_escaped_print(title, fout); fputs("</caption>\n", fout); } } /* print records */ for (i = 0, ptr = cells; *ptr; i++, ptr++) { if (i % col_count == 0) { if (cancel_pressed) break; if (!opt_tuples_only) fprintf(fout, "\n <tr><td colspan=\"2\" align=\"center\">Record %lu</td></tr>\n", record++); else fputs("\n <tr><td colspan=\"2\"> </td></tr>\n", fout); } fputs(" <tr valign=\"top\">\n" " <th>", fout); html_escaped_print(headers[i % col_count], fout); fputs("</th>\n", fout); fprintf(fout, " <td align=\"%s\">", opt_align[i % col_count] == 'r' ? "right" : "left"); /* is string only whitespace? */ if ((*ptr)[strspn(*ptr, " \t")] == '\0') fputs(" ", fout); else if (opt_align[i % col_count] == 'r' && opt_numeric_locale) { char *my_cell = format_numeric_locale(*ptr); html_escaped_print(my_cell, fout); free(my_cell); } else html_escaped_print(*ptr, fout); fputs("</td>\n </tr>\n", fout); } if (opt->stop_table) { fputs("</table>\n", fout); /* print footers */ if (!opt_tuples_only && footers && *footers && !cancel_pressed) { fputs("<p>", fout); for (ptr = footers; *ptr; ptr++) { html_escaped_print(*ptr, fout); fputs("<br />\n", fout); } fputs("</p>", fout); } fputc('\n', fout); } } /*************************/ /* LaTeX */ /*************************/ static void latex_escaped_print(const char *in, FILE *fout) { const char *p; for (p = in; *p; p++) switch (*p) { case '&': fputs("\\&", fout); break; case '%': fputs("\\%", fout); break; case '$': fputs("\\$", fout); break; case '_': fputs("\\_", fout); break; case '{': fputs("\\{", fout); break; case '}': fputs("\\}", fout); break; case '\\': fputs("\\backslash", fout); break; case '\n': fputs("\\\\", fout); break; default: fputc(*p, fout); } } static void print_latex_text(const char *title, const char *const * headers, const char *const * cells, const char *const * footers, const char *opt_align, const printTableOpt *opt, FILE *fout) { bool opt_tuples_only = opt->tuples_only; bool opt_numeric_locale = opt->numericLocale; unsigned short int opt_border = opt->border; unsigned int col_count = 0; unsigned int i; const char *const * ptr; if (cancel_pressed) return; if (opt_border > 2) opt_border = 2; /* count columns */ for (ptr = headers; *ptr; ptr++) col_count++; if (opt->start_table) { /* print title */ if (!opt_tuples_only && title) { fputs("\\begin{center}\n", fout); latex_escaped_print(title, fout); fputs("\n\\end{center}\n\n", fout); } /* begin environment and set alignments and borders */ fputs("\\begin{tabular}{", fout); if (opt_border == 2) fputs("| ", fout); for (i = 0; i < col_count; i++) { fputc(*(opt_align + i), fout); if (opt_border != 0 && i < col_count - 1) fputs(" | ", fout); } if (opt_border == 2) fputs(" |", fout); fputs("}\n", fout); if (!opt_tuples_only && opt_border == 2) fputs("\\hline\n", fout); /* print headers */ if (!opt_tuples_only) { for (i = 0, ptr = headers; i < col_count; i++, ptr++) { if (i != 0) fputs(" & ", fout); fputs("\\textit{", fout); latex_escaped_print(*ptr, fout); fputc('}', fout); } fputs(" \\\\\n", fout); fputs("\\hline\n", fout); } } /* print cells */ for (i = 0, ptr = cells; *ptr; i++, ptr++) { if (opt_numeric_locale) { char *my_cell = format_numeric_locale(*ptr); latex_escaped_print(my_cell, fout); free(my_cell); } else latex_escaped_print(*ptr, fout); if ((i + 1) % col_count == 0) { fputs(" \\\\\n", fout); if (cancel_pressed) break; } else fputs(" & ", fout); } if (opt->stop_table) { if (opt_border == 2) fputs("\\hline\n", fout); fputs("\\end{tabular}\n\n\\noindent ", fout); /* print footers */ if (footers && !opt_tuples_only && !cancel_pressed) { for (ptr = footers; *ptr; ptr++) { latex_escaped_print(*ptr, fout); fputs(" \\\\\n", fout); } } fputc('\n', fout); } } static void print_latex_vertical(const char *title, const char *const * headers, const char *const * cells, const char *const * footers, const char *opt_align, const printTableOpt *opt, FILE *fout) { bool opt_tuples_only = opt->tuples_only; bool opt_numeric_locale = opt->numericLocale; unsigned short int opt_border = opt->border; unsigned int col_count = 0; unsigned long record = opt->prior_records + 1; unsigned int i; const char *const * ptr; (void) opt_align; /* currently unused parameter */ if (cancel_pressed) return; if (opt_border > 2) opt_border = 2; /* count columns */ for (ptr = headers; *ptr; ptr++) col_count++; if (opt->start_table) { /* print title */ if (!opt_tuples_only && title) { fputs("\\begin{center}\n", fout); latex_escaped_print(title, fout); fputs("\n\\end{center}\n\n", fout); } /* begin environment and set alignments and borders */ fputs("\\begin{tabular}{", fout); if (opt_border == 0) fputs("cl", fout); else if (opt_border == 1) fputs("c|l", fout); else if (opt_border == 2) fputs("|c|l|", fout); fputs("}\n", fout); } /* print records */ for (i = 0, ptr = cells; *ptr; i++, ptr++) { /* new record */ if (i % col_count == 0) { if (cancel_pressed) break; if (!opt_tuples_only) { if (opt_border == 2) { fputs("\\hline\n", fout); fprintf(fout, "\\multicolumn{2}{|c|}{\\textit{Record %lu}} \\\\\n", record++); } else fprintf(fout, "\\multicolumn{2}{c}{\\textit{Record %lu}} \\\\\n", record++); } if (opt_border >= 1) fputs("\\hline\n", fout); } latex_escaped_print(headers[i % col_count], fout); fputs(" & ", fout); latex_escaped_print(*ptr, fout); fputs(" \\\\\n", fout); } if (opt->stop_table) { if (opt_border == 2) fputs("\\hline\n", fout); fputs("\\end{tabular}\n\n\\noindent ", fout); /* print footers */ if (footers && !opt_tuples_only && !cancel_pressed) { for (ptr = footers; *ptr; ptr++) { if (opt_numeric_locale) { char *my_cell = format_numeric_locale(*ptr); latex_escaped_print(my_cell, fout); free(my_cell); } else latex_escaped_print(*ptr, fout); fputs(" \\\\\n", fout); } } fputc('\n', fout); } } /*************************/ /* Troff -ms */ /*************************/ static void troff_ms_escaped_print(const char *in, FILE *fout) { const char *p; for (p = in; *p; p++) switch (*p) { case '\\': fputs("\\(rs", fout); break; default: fputc(*p, fout); } } static void print_troff_ms_text(const char *title, const char *const * headers, const char *const * cells, const char *const * footers, const char *opt_align, const printTableOpt *opt, FILE *fout) { bool opt_tuples_only = opt->tuples_only; bool opt_numeric_locale = opt->numericLocale; unsigned short int opt_border = opt->border; unsigned int col_count = 0; unsigned int i; const char *const * ptr; if (cancel_pressed) return; if (opt_border > 2) opt_border = 2; /* count columns */ for (ptr = headers; *ptr; ptr++) col_count++; if (opt->start_table) { /* print title */ if (!opt_tuples_only && title) { fputs(".LP\n.DS C\n", fout); troff_ms_escaped_print(title, fout); fputs("\n.DE\n", fout); } /* begin environment and set alignments and borders */ fputs(".LP\n.TS\n", fout); if (opt_border == 2) fputs("center box;\n", fout); else fputs("center;\n", fout); for (i = 0; i < col_count; i++) { fputc(*(opt_align + i), fout); if (opt_border > 0 && i < col_count - 1) fputs(" | ", fout); } fputs(".\n", fout); /* print headers */ if (!opt_tuples_only) { for (i = 0, ptr = headers; i < col_count; i++, ptr++) { if (i != 0) fputc('\t', fout); fputs("\\fI", fout); troff_ms_escaped_print(*ptr, fout); fputs("\\fP", fout); } fputs("\n_\n", fout); } } /* print cells */ for (i = 0, ptr = cells; *ptr; i++, ptr++) { if (opt_numeric_locale) { char *my_cell = format_numeric_locale(*ptr); troff_ms_escaped_print(my_cell, fout); free(my_cell); } else troff_ms_escaped_print(*ptr, fout); if ((i + 1) % col_count == 0) { fputc('\n', fout); if (cancel_pressed) break; } else fputc('\t', fout); } if (opt->stop_table) { fputs(".TE\n.DS L\n", fout); /* print footers */ if (footers && !opt_tuples_only && !cancel_pressed) for (ptr = footers; *ptr; ptr++) { troff_ms_escaped_print(*ptr, fout); fputc('\n', fout); } fputs(".DE\n", fout); } } static void print_troff_ms_vertical(const char *title, const char *const * headers, const char *const * cells, const char *const * footers, const char *opt_align, const printTableOpt *opt, FILE *fout) { bool opt_tuples_only = opt->tuples_only; bool opt_numeric_locale = opt->numericLocale; unsigned short int opt_border = opt->border; unsigned int col_count = 0; unsigned long record = opt->prior_records + 1; unsigned int i; const char *const * ptr; unsigned short current_format = 0; /* 0=none, 1=header, 2=body */ (void) opt_align; /* currently unused parameter */ if (cancel_pressed) return; if (opt_border > 2) opt_border = 2; /* count columns */ for (ptr = headers; *ptr; ptr++) col_count++; if (opt->start_table) { /* print title */ if (!opt_tuples_only && title) { fputs(".LP\n.DS C\n", fout); troff_ms_escaped_print(title, fout); fputs("\n.DE\n", fout); } /* begin environment and set alignments and borders */ fputs(".LP\n.TS\n", fout); if (opt_border == 2) fputs("center box;\n", fout); else fputs("center;\n", fout); /* basic format */ if (opt_tuples_only) fputs("c l;\n", fout); } else current_format = 2; /* assume tuples printed already */ /* print records */ for (i = 0, ptr = cells; *ptr; i++, ptr++) { /* new record */ if (i % col_count == 0) { if (cancel_pressed) break; if (!opt_tuples_only) { if (current_format != 1) { if (opt_border == 2 && record > 1) fputs("_\n", fout); if (current_format != 0) fputs(".T&\n", fout); fputs("c s.\n", fout); current_format = 1; } fprintf(fout, "\\fIRecord %lu\\fP\n", record++); } if (opt_border >= 1) fputs("_\n", fout); } if (!opt_tuples_only) { if (current_format != 2) { if (current_format != 0) fputs(".T&\n", fout); if (opt_border != 1) fputs("c l.\n", fout); else fputs("c | l.\n", fout); current_format = 2; } } troff_ms_escaped_print(headers[i % col_count], fout); fputc('\t', fout); if (opt_numeric_locale) { char *my_cell = format_numeric_locale(*ptr); troff_ms_escaped_print(my_cell, fout); free(my_cell); } else troff_ms_escaped_print(*ptr, fout); fputc('\n', fout); } if (opt->stop_table) { fputs(".TE\n.DS L\n", fout); /* print footers */ if (footers && !opt_tuples_only && !cancel_pressed) for (ptr = footers; *ptr; ptr++) { troff_ms_escaped_print(*ptr, fout); fputc('\n', fout); } fputs(".DE\n", fout); } } /********************************/ /* Public functions */ /********************************/ /* * PageOutput * * Tests if pager is needed and returns appropriate FILE pointer. */ FILE * PageOutput(int lines, unsigned short int pager) { /* check whether we need / can / are supposed to use pager */ if (pager #ifndef WIN32 && isatty(fileno(stdin)) && isatty(fileno(stdout)) #endif ) { const char *pagerprog; FILE *pagerpipe; #ifdef TIOCGWINSZ int result; struct winsize screen_size; result = ioctl(fileno(stdout), TIOCGWINSZ, &screen_size); /* >= accounts for a one-line prompt */ if (result == -1 || lines >= screen_size.ws_row || pager > 1) { #endif pagerprog = getenv("PAGER"); if (!pagerprog) pagerprog = DEFAULT_PAGER; #ifndef WIN32 pqsignal(SIGPIPE, SIG_IGN); #endif pagerpipe = popen(pagerprog, "w"); if (pagerpipe) return pagerpipe; #ifdef TIOCGWINSZ } #endif } return stdout; } /* * ClosePager * * Close previously opened pager pipe, if any */ void ClosePager(FILE *pagerpipe) { if (pagerpipe && pagerpipe != stdout) { /* * If printing was canceled midstream, warn about it. * * Some pagers like less use Ctrl-C as part of their command set. Even * so, we abort our processing and warn the user what we did. If the * pager quit as a result of the SIGINT, this message won't go * anywhere ... */ if (cancel_pressed) fprintf(pagerpipe, _("Interrupted\n")); pclose(pagerpipe); #ifndef WIN32 pqsignal(SIGPIPE, SIG_DFL); #endif } } void printTable(const char *title, const char *const * headers, const char *const * cells, const char *const * footers, const char *align, const printTableOpt *opt, FILE *fout, FILE *flog) { static const char *default_footer[] = {NULL}; FILE *output; if (cancel_pressed) return; if (opt->format == PRINT_NOTHING) return; if (!footers) footers = default_footer; if (fout == stdout) { int col_count = 0, row_count = 0, lines; const char *const * ptr; /* rough estimate of columns and rows */ if (headers) for (ptr = headers; *ptr; ptr++) col_count++; if (cells) for (ptr = cells; *ptr; ptr++) row_count++; if (col_count > 0) row_count /= col_count; if (opt->expanded) lines = (col_count + 1) * row_count; else lines = row_count + 1; if (footers && !opt->tuples_only) for (ptr = footers; *ptr; ptr++) lines++; output = PageOutput(lines, opt->pager); } else output = fout; /* print the stuff */ if (flog) print_aligned_text(title, headers, cells, footers, align, opt, flog); switch (opt->format) { case PRINT_UNALIGNED: if (opt->expanded) print_unaligned_vertical(title, headers, cells, footers, align, opt, output); else print_unaligned_text(title, headers, cells, footers, align, opt, output); break; case PRINT_ALIGNED: case PRINT_WRAP: if (opt->expanded) print_aligned_vertical(title, headers, cells, footers, align, opt, output); else print_aligned_text(title, headers, cells, footers, align, opt, output); break; case PRINT_HTML: if (opt->expanded) print_html_vertical(title, headers, cells, footers, align, opt, output); else print_html_text(title, headers, cells, footers, align, opt, output); break; case PRINT_LATEX: if (opt->expanded) print_latex_vertical(title, headers, cells, footers, align, opt, output); else print_latex_text(title, headers, cells, footers, align, opt, output); break; case PRINT_TROFF_MS: if (opt->expanded) print_troff_ms_vertical(title, headers, cells, footers, align, opt, output); else print_troff_ms_text(title, headers, cells, footers, align, opt, output); break; default: fprintf(stderr, _("invalid output format (internal error): %d"), opt->format); exit(EXIT_FAILURE); } /* Only close if we used the pager */ if (output != fout) ClosePager(output); } void printQuery(const PGresult *result, const printQueryOpt *opt, FILE *fout, FILE *flog) { int ntuples; int nfields; int ncells; const char **headers; const char **cells; char **footers; char *align; int i, r, c; if (cancel_pressed) return; /* extract headers */ ntuples = PQntuples(result); nfields = PQnfields(result); headers = pg_local_calloc(nfields + 1, sizeof(*headers)); for (i = 0; i < nfields; i++) { headers[i] = (char *) mbvalidate((unsigned char *) PQfname(result, i), opt->topt.encoding); #ifdef ENABLE_NLS if (opt->trans_headers) headers[i] = _(headers[i]); #endif } /* set cells */ ncells = ntuples * nfields; cells = pg_local_calloc(ncells + 1, sizeof(*cells)); i = 0; for (r = 0; r < ntuples; r++) { for (c = 0; c < nfields; c++) { if (PQgetisnull(result, r, c)) cells[i] = opt->nullPrint ? opt->nullPrint : ""; else { cells[i] = (char *) mbvalidate((unsigned char *) PQgetvalue(result, r, c), opt->topt.encoding); #ifdef ENABLE_NLS if (opt->trans_columns && opt->trans_columns[c]) cells[i] = _(cells[i]); #endif } i++; } } /* set footers */ if (opt->footers) footers = opt->footers; else if (!opt->topt.expanded && opt->default_footer) { unsigned long total_records; footers = pg_local_calloc(2, sizeof(*footers)); footers[0] = pg_local_malloc(100); total_records = opt->topt.prior_records + ntuples; if (total_records == 1) snprintf(footers[0], 100, _("(1 row)")); else snprintf(footers[0], 100, _("(%lu rows)"), total_records); } else footers = NULL; /* set alignment */ align = pg_local_calloc(nfields + 1, sizeof(*align)); for (i = 0; i < nfields; i++) { Oid ftype = PQftype(result, i); switch (ftype) { case INT2OID: case INT4OID: case INT8OID: case FLOAT4OID: case FLOAT8OID: case NUMERICOID: case OIDOID: case XIDOID: case CIDOID: case CASHOID: align[i] = 'r'; break; default: align[i] = 'l'; break; } } /* call table printer */ printTable(opt->title, headers, cells, (const char *const *) footers, align, &opt->topt, fout, flog); free(headers); free(cells); if (footers && !opt->footers) { free(footers[0]); free(footers); } free(align); } void setDecimalLocale(void) { struct lconv *extlconv; extlconv = localeconv(); if (*extlconv->decimal_point) decimal_point = strdup(extlconv->decimal_point); else decimal_point = "."; /* SQL output standard */ if (*extlconv->grouping && atoi(extlconv->grouping) > 0) grouping = strdup(extlconv->grouping); else grouping = "3"; /* most common */ /* similar code exists in formatting.c */ if (*extlconv->thousands_sep) thousands_sep = strdup(extlconv->thousands_sep); /* Make sure thousands separator doesn't match decimal point symbol. */ else if (strcmp(decimal_point, ",") != 0) thousands_sep = ","; else thousands_sep = "."; }
Peter Eisentraut wrote: > Bruce Momjian wrote: > >> I checked the use of COLUMNS and it seems bash updates the environment >> variable when a window is resized. I added ioctl(TIOCGWINSZ) if COLUMNS >> isn't set. We already had a call in print.c for detecting the >> number of rows on the screen to determine if the pager should >> be used. Seems COLUMNS should take precedence over ioctl(), right? > > Considering that the code to determine the row count is undisputed so far, the > column count detection should work the same. That is, we might not need to > look at COLUMNS at all. Unless there is a use case for overriding the column > count (instead of just turning off the wrapping). > I asked the folks over at "Experts Exchange" to test the behavior of the ioctl and $COLUMNS on various platforms. I'd been told that I would face huge problems if a console was resized. But the results were pretty consistent, and nothing had problems with resize: http://www.experts-exchange.com/Programming/Open_Source/Q_23243646.html It appears impossible to override $COLUMNS, on some platforms as the readline call sets it. On many platforms $COLUMNS is null until the call to readline. OSX does not set $COLUMNS at all. In short, I recommend the ioctl instead. In order to provide a way to wrap output to a pipe, I think a different mechanism will have to be found. -Bryce
Bryce Nesbitt wrote: > Alvaro Herrera wrote: > > People [are] complaining here that we don't teach people here > > anyway, so hopefully my comments were still useful :-) > > > Yes they are useful. As a new patcher, where should I look for > coding standards? How about a little FAQ at the > top of the CVS source tree? > > Though, darn it, I sure like // > > And my vi is set to: > set sw=4 > set ts=4 > set expandtab > Because my corporate projects require spaces not tabs. See the developer FAQ on the website. IIRC, it even contains what you should put in your vi config file to make it do the right thing... //Magnus
"Tom Lane" <tgl@sss.pgh.pa.us> writes: > Bryce Nesbitt <bryce2@obviously.com> writes: >> <pre wrap="">I checked the use of COLUMNS and it seems bash updates the environment >> variable when a window is resized. > > [ Please get rid of the HTML formatting ... ] > > Bash can update the environment all it wants, but that will not affect > what is seen by a program that's already running. Personally I often > resize the window while psql is running, and I expect that to work. Hm, then having COLUMNS override the ioctl isn't such a good idea. Checking GNU ls source the ioctl overrides COLUMNS if it works, but there's a --width option which trumps both of the other two. I guess just a psql variable would be the equivalent. > I'm with Peter on this one: we have used ioctl, and nothing else, to > determine the vertical window dimension for many years now, to the tune > of approximately zero complaints. It's going to take one hell of a > strong argument to persuade me that determination of the horizontal > dimension should not work exactly the same way. Well the cases are not analogous. Firstly, the window height doesn't actually alter the output. Secondly there's really no downside in a false positive since most pagers just exit if they decide the output fit on the screen -- which probably explains why no ssh users have complained... And in any case you can always override it by piping the output to a pager yourself -- which is effectively all I'm suggesting doing here. So here are your two hella-strong arguments: a) not all terminals support the ioctl. Emacs shell users may be eccentric but surely using psql over ssh isn't especially uncommon. Falling back to COLUMNS is standard, GNU ls is not alone, Solaris and FreeBSD both document supporting COLUMNS. b) you don't necessarily *want* the output formatted to fill the screen. You may be generating a report to email and want to set the width to the RFC recommended 72 characters. You may just have a full screen terminal but not enjoy reading 200-character long lines -- try it, it's really hard: MANWIDTH If $MANWIDTH is set, its value is used as the line length for which manual pages should be formatted. If it is not set, manual pages will be formatted with a line length appropriate to the current terminal (using an ioctl(2) if available, the value of $COLUMNS, or falling back to 80 characters ifneither is available). Cat pages will only be saved when the default formatting can be used, that is when the terminal line length is between 66 and 80 characters. -- Gregory Stark EnterpriseDB http://www.enterprisedb.com Ask me about EnterpriseDB's On-Demand Production Tuning
Peter Eisentraut wrote: > Bruce Momjian wrote: > >> I checked the use of COLUMNS and it seems bash updates the environment >> variable when a window is resized. I added ioctl(TIOCGWINSZ) if COLUMNS >> isn't set. We already had a call in print.c for detecting the >> number of rows on the screen to determine if the pager should >> be used. Seems COLUMNS should take precedence over ioctl(), right? > > Considering that the code to determine the row count is undisputed so far, the > column count detection should work the same. That is, we might not need to > look at COLUMNS at all. Unless there is a use case for overriding the column > count (instead of just turning off the wrapping). > I asked the folks over at "Experts Exchange" to test the behavior of the ioctl and $COLUMNS on various platforms. I'd been told that I would face huge problems if a console was resized. But the results were pretty consistent, and $COLUMNS had no problems with resize: http://www.experts-exchange.com/Programming/Open_Source/Q_23243646.html But appears impossible to override $COLUMNS, on some platforms as the readline call sets it. On many platforms $COLUMNS is null until the call to readline. OSX does not set $COLUMNS at all. So I think the ioctl should be used to determine the wrap width for terminals, and some other mechanism used for pipes. There's no way I'd want wrapping as the default for pipe output. I was not bold enough to propose that wrapping be the default behavior for the terminal. -Bryce
"Bryce Nesbitt" <bryce2@obviously.com> writes: > I asked the folks over at "Experts Exchange" to test the behavior of the ioctl I always thought that was a joke domain name, like Pen Island.com. -- Gregory Stark EnterpriseDB http://www.enterprisedb.com Ask me about EnterpriseDB's PostGIS support!
Bryce Nesbitt wrote: > Alvaro Herrera wrote: >> People [are] complaining here that we don't teach people here anyway, so >> hopefully my comments were still useful :-) >> > Yes they are useful. As a new patcher, where should I look for coding > standards? How about a little FAQ at the > top of the CVS source tree? The developer's FAQ is supposed to contain this kind of thing, but I think it's rather thin on actual details. (Some time ago it was proposed to create a "style guide", but people here thought it was a waste of time and "it will not cover what's really important", so we're stuck with repeating the advice over and over.) > Though, darn it, I sure like // > > And my vi is set to: > set sw=4 > set ts=4 > set expandtab > Because my corporate projects require spaces not tabs. I use this: :if match(getcwd(), "/home/alvherre/Code/CVS/pgsql") == 0 : set cinoptions=(0 : set tabstop=4 : set shiftwidth=4 : let $CSCOPE_DB=substitute(getcwd(), "^\\(.*/pgsql/source/[^/]*\\)/.*", "\\1", "") : let &tags=substitute(getcwd(), "^\\(.*/pgsql/source/[^/]*\\)/.*", "\\1", "") . "/tags" : let &path=substitute(getcwd(), "^\\(.*/pgsql/source/[^/]*\\)/.*", "\\1", "") . "/src/include" :endif Of course, you need to adjust the paths. The cscope, tags and path things let me quickly navigate through the files, but they don't affect the editor behavior. I never set expandtab so it's not a problem for me, but I guess you can do ":set noexpandtab" in that block to reset it for Postgres use. -- Alvaro Herrera http://www.CommandPrompt.com/ PostgreSQL Replication, Consulting, Custom Development, 24x7 support
On Fri, 18 Apr 2008 17:21:26 +0100 Gregory Stark <stark@enterprisedb.com> wrote: > "Bryce Nesbitt" <bryce2@obviously.com> writes: > > > I asked the folks over at "Experts Exchange" to test the behavior > > of the ioctl > > I always thought that was a joke domain name, like Pen Island.com. > Its not and a lot of postgresql users interact there. Joshua D. Drake -- The PostgreSQL Company since 1997: http://www.commandprompt.com/ PostgreSQL Community Conference: http://www.postgresqlconference.org/ United States PostgreSQL Association: http://www.postgresql.us/ Donate to the PostgreSQL Project: http://www.postgresql.org/about/donate
bryce2@obviously.com (Bryce Nesbitt) writes: > Alvaro Herrera wrote: >> People [are] complaining here that we don't teach people here anyway, so >> hopefully my comments were still useful :-) >> > Yes they are useful. As a new patcher, where should I look for coding > standards? How about a little FAQ at the > top of the CVS source tree? > > Though, darn it, I sure like // > > And my vi is set to: > set sw=4 > set ts=4 > set expandtab > Because my corporate projects require spaces not tabs. Note that you can find config for vim and emacs to get them to support the coding standards in: /opt/src/pgsql-HEAD/src/tools/editors/emacs.samples /opt/src/pgsql-HEAD/src/tools/editors/vim.samples For vim, the essentials are thus: :if match(getcwd(), "/pgsql") >=0 || match(getcwd(), "/postgresql") >= 0 : set cinoptions=(0 : set tabstop=4 : set shiftwidth=4 :endif The hooks are slightly different (though not by spectacularly much, somewhat surprisingly) for Emacs... -- let name="cbbrowne" and tld="cbbrowne.com" in name ^ "@" ^ tld;; http://cbbrowne.com/info/advocacy.html "A language that doesn't affect the way you think about programming, is not worth knowing." -- Alan J. Perlis
Bryce Nesbitt wrote: > Bruce Momjian wrote: > > I don't think you need to do that to get it applied --- there is nothing > > windows-specific in your code. > > > > Is this ready to be applied? Do you want to send a final update or are > > you still testing? > > > Looks good, but I suggest adding "give up if the header is too wide": > > if (tcolumns > 0 && tcolumns >= total_header_width) OK, I have created a new version of the patch, attached. Bryce, I included your code line above and the other changes you had in the new version of print.c. I also set it to use ioctl() width if the output is to the terminal, and \pset column for other cases, and adjusted the documentation. I played a little with how wrapping would look with values that contained newlines: test=> \pset format wrapped Output format is wrapped. test=> \pset columns 70 Target column width for "wrap" format is 70. test=> select 1, 2, repeat('a', 80), repeat('b', 80), E'a\nb\nc', 1 from generate_series(1,2)\g /rtmp/d ?column? | ?column? | repeat | repeat | ?column? | ?column? ----------+----------+------------+-------------+----------+---------- 1 | 2 | aaaaaaaaaa | bbbbbbbbbbb | a | 1 ; aaaaaaaaaa ; bbbbbbbbbbb ; aaaaaaaaaa ; bbbbbbbbbbb ; aaaaaaaaaa ; bbbbbbbbbbb ; aaaaaaaaaa ; bbbbbbbbbbb ; aaaaaaaaaa ; bbbbbbbbbbb ; aaaaaaaaaa ; bbbbbbbbbbb ; aaaaaaaaaa ; bbb : b : c 1 | 2 | aaaaaaaaaa | bbbbbbbbbbb | a | 1 ; aaaaaaaaaa ; bbbbbbbbbbb ; aaaaaaaaaa ; bbbbbbbbbbb ; aaaaaaaaaa ; bbbbbbbbbbb ; aaaaaaaaaa ; bbbbbbbbbbb ; aaaaaaaaaa ; bbbbbbbbbbb ; aaaaaaaaaa ; bbbbbbbbbbb ; aaaaaaaaaa ; bbb : b : c (2 rows) Notice that the marker for wrapping is now ";". The marker is always on the left edge, assuming you are not in the first column, in which case there is no marker. This is how newline behaves and I copied that. Also note that there is no separator for columns that don't wrap down far enough. This is also how the newline case is handled. Is this the way we want it to display? (Bryce, I am not sure your original code handled this right.) So, a query with only one column is going to be unclear whether it has embedded newlines or wrapping has happening. One idea is for wrapping to place a dash at the end of the value, so you have: aaaaaaaaaa- aaaaaaaaaa- aaaa or something like that. Another issue is whether newlines should filter up into the rows already used for wrapping in adjacent columns. Right now it doesn't do that but we could. I found a problem with the original patch related to multibyte. The code was fine up until the wrapping happened, at which point 'width' was assumed to equal characters and the value was chopped into width "byte" chunks. It has to be done in width "character" chunks so each chunk has the same number of characters and you don't split a multi-byte character across a line. I fixed it by creating a new function mb_strlen_max_width(). I also restructured some of the code and added comments. Community, I am looking for feedback on how to handle some of my questions above. Bryce, I am sorry this patch is taking so many iterations but the feature has to work perfectly in lots of complex configurations so it takes longer to complete and apply. -- Bruce Momjian <bruce@momjian.us> http://momjian.us EnterpriseDB http://enterprisedb.com + If your life is a hard drive, Christ can be your backup. + Index: doc/src/sgml/ref/psql-ref.sgml =================================================================== RCS file: /cvsroot/pgsql/doc/src/sgml/ref/psql-ref.sgml,v retrieving revision 1.199 diff -c -c -r1.199 psql-ref.sgml *** doc/src/sgml/ref/psql-ref.sgml 30 Mar 2008 18:10:20 -0000 1.199 --- doc/src/sgml/ref/psql-ref.sgml 20 Apr 2008 03:53:39 -0000 *************** *** 1513,1519 **** <listitem> <para> Sets the output format to one of <literal>unaligned</literal>, ! <literal>aligned</literal>, <literal>html</literal>, <literal>latex</literal>, or <literal>troff-ms</literal>. Unique abbreviations are allowed. (That would mean one letter is enough.) --- 1513,1520 ---- <listitem> <para> Sets the output format to one of <literal>unaligned</literal>, ! <literal>aligned</literal>, <literal>wrapped</literal>, ! <literal>html</literal>, <literal>latex</literal>, or <literal>troff-ms</literal>. Unique abbreviations are allowed. (That would mean one letter is enough.) *************** *** 1525,1532 **** is intended to create output that might be intended to be read in by other programs (tab-separated, comma-separated). <quote>Aligned</quote> mode is the standard, human-readable, ! nicely formatted text output that is default. The ! <quote><acronym>HTML</acronym></quote> and <quote>LaTeX</quote> modes put out tables that are intended to be included in documents using the respective mark-up language. They are not complete documents! (This might not be --- 1526,1535 ---- is intended to create output that might be intended to be read in by other programs (tab-separated, comma-separated). <quote>Aligned</quote> mode is the standard, human-readable, ! nicely formatted text output that is default. ! <quote>Wrapped</quote> is like <literal>aligned</> but wraps ! the output to fit the detected screen width or <literal>\pset ! columns</>. The <quote><acronym>HTML</acronym></quote> and <quote>LaTeX</quote> modes put out tables that are intended to be included in documents using the respective mark-up language. They are not complete documents! (This might not be *************** *** 1537,1542 **** --- 1540,1556 ---- </varlistentry> <varlistentry> + <term><literal>columns</literal></term> + <listitem> + <para> + Controls the target width for the <literal>wrap</> format + when output is not to the screen, or the operating system does + not support screen width detection. + </para> + </listitem> + </varlistentry> + + <varlistentry> <term><literal>border</literal></term> <listitem> <para> Index: src/bin/psql/command.c =================================================================== RCS file: /cvsroot/pgsql/src/bin/psql/command.c,v retrieving revision 1.186 diff -c -c -r1.186 command.c *** src/bin/psql/command.c 1 Jan 2008 19:45:55 -0000 1.186 --- src/bin/psql/command.c 20 Apr 2008 03:53:39 -0000 *************** *** 1526,1531 **** --- 1526,1534 ---- case PRINT_ALIGNED: return "aligned"; break; + case PRINT_WRAP: + return "wrapped"; + break; case PRINT_HTML: return "html"; break; *************** *** 1559,1564 **** --- 1562,1569 ---- popt->topt.format = PRINT_UNALIGNED; else if (pg_strncasecmp("aligned", value, vallen) == 0) popt->topt.format = PRINT_ALIGNED; + else if (pg_strncasecmp("wrapped", value, vallen) == 0) + popt->topt.format = PRINT_WRAP; else if (pg_strncasecmp("html", value, vallen) == 0) popt->topt.format = PRINT_HTML; else if (pg_strncasecmp("latex", value, vallen) == 0) *************** *** 1567,1573 **** popt->topt.format = PRINT_TROFF_MS; else { ! psql_error("\\pset: allowed formats are unaligned, aligned, html, latex, troff-ms\n"); return false; } --- 1572,1578 ---- popt->topt.format = PRINT_TROFF_MS; else { ! psql_error("\\pset: allowed formats are unaligned, aligned, wrapped, html, latex, troff-ms\n"); return false; } *************** *** 1748,1753 **** --- 1753,1768 ---- } } + /* set border style/width */ + else if (strcmp(param, "columns") == 0) + { + if (value) + popt->topt.columns = atoi(value); + + if (!quiet) + printf(_("Target column width for \"wrap\" format is %d.\n"), popt->topt.columns); + } + else { psql_error("\\pset: unknown option: %s\n", param); Index: src/bin/psql/mbprint.c =================================================================== RCS file: /cvsroot/pgsql/src/bin/psql/mbprint.c,v retrieving revision 1.30 diff -c -c -r1.30 mbprint.c *** src/bin/psql/mbprint.c 16 Apr 2008 18:18:00 -0000 1.30 --- src/bin/psql/mbprint.c 20 Apr 2008 03:53:41 -0000 *************** *** 204,210 **** /* * pg_wcssize takes the given string in the given encoding and returns three * values: ! * result_width: Width in display character of longest line in string * result_height: Number of lines in display output * result_format_size: Number of bytes required to store formatted representation of string */ --- 204,210 ---- /* * pg_wcssize takes the given string in the given encoding and returns three * values: ! * result_width: Width in display characters of the longest line in string * result_height: Number of lines in display output * result_format_size: Number of bytes required to store formatted representation of string */ *************** *** 279,284 **** --- 279,288 ---- return width; } + /* + * Filter out unprintable characters, companion to wcs_size. + * Break input into lines (based on \n or \r). + */ void pg_wcsformat(unsigned char *pwcs, size_t len, int encoding, struct lineptr * lines, int count) *************** *** 353,364 **** } len -= chlen; } ! *ptr++ = '\0'; lines->width = linewidth; ! lines++; ! count--; ! if (count > 0) lines->ptr = NULL; } unsigned char * --- 357,373 ---- } len -= chlen; } ! *ptr++ = '\0'; /* Terminate formatted string */ ! lines->width = linewidth; ! ! /* Fill remaining array slots with nulls */ ! while (--count) ! { ! lines++; lines->ptr = NULL; + lines->width = 0; + } } unsigned char * Index: src/bin/psql/print.c =================================================================== RCS file: /cvsroot/pgsql/src/bin/psql/print.c,v retrieving revision 1.97 diff -c -c -r1.97 print.c *** src/bin/psql/print.c 27 Mar 2008 03:57:34 -0000 1.97 --- src/bin/psql/print.c 20 Apr 2008 03:53:42 -0000 *************** *** 28,33 **** --- 28,35 ---- #include "mbprint.h" + static int mb_strlen_max_width(char *str, int *width, int encoding); + /* * We define the cancel_pressed flag in this file, rather than common.c where * it naturally belongs, because this file is also used by non-psql programs *************** *** 43,48 **** --- 45,51 ---- static char *grouping; static char *thousands_sep; + static void * pg_local_malloc(size_t size) { *************** *** 396,401 **** --- 399,407 ---- } + /* + * Prety pretty boxes around cells. + */ static void print_aligned_text(const char *title, const char *const * headers, const char *const * cells, const char *const * footers, *************** *** 404,429 **** { bool opt_tuples_only = opt->tuples_only; bool opt_numeric_locale = opt->numericLocale; - unsigned short int opt_border = opt->border; int encoding = opt->encoding; ! unsigned int col_count = 0; ! unsigned int cell_count = 0; ! unsigned int i; ! int tmp; ! unsigned int *widths, ! total_w; ! unsigned int *heights; ! unsigned int *format_space; unsigned char **format_buf; const char *const * ptr; ! struct lineptr **col_lineptrs; /* pointers to line pointer for each ! * column */ struct lineptr *lineptr_list; /* complete list of linepointers */ ! int *complete; /* Array remembering which columns have ! * completed output */ if (cancel_pressed) return; --- 410,441 ---- { bool opt_tuples_only = opt->tuples_only; bool opt_numeric_locale = opt->numericLocale; int encoding = opt->encoding; ! unsigned short int opt_border = opt->border; ! ! unsigned int col_count = 0, cell_count = 0; ! ! unsigned int i, ! j; ! ! unsigned int *width_header, ! *width_max, ! *width_wrap, ! *width_average; ! unsigned int *heights, ! *format_space; unsigned char **format_buf; + unsigned int width_total; + unsigned int total_header_width; const char *const * ptr; ! struct lineptr **col_lineptrs; /* pointers to line pointer per column */ struct lineptr *lineptr_list; /* complete list of linepointers */ ! bool *header_done; /* Have all header lines been output? */ ! int *bytes_output; /* Bytes output for column value */ ! int tcolumns = 0; /* Width of interactive console */ if (cancel_pressed) return; *************** *** 437,533 **** if (col_count > 0) { ! widths = pg_local_calloc(col_count, sizeof(*widths)); heights = pg_local_calloc(col_count, sizeof(*heights)); col_lineptrs = pg_local_calloc(col_count, sizeof(*col_lineptrs)); format_space = pg_local_calloc(col_count, sizeof(*format_space)); format_buf = pg_local_calloc(col_count, sizeof(*format_buf)); ! complete = pg_local_calloc(col_count, sizeof(*complete)); } else { ! widths = NULL; heights = NULL; col_lineptrs = NULL; format_space = NULL; format_buf = NULL; ! complete = NULL; } ! /* count cells (rows * cols) */ ! for (ptr = cells; *ptr; ptr++) ! cell_count++; ! ! /* calc column widths */ for (i = 0; i < col_count; i++) { /* Get width & height */ ! int height, space; ! pg_wcssize((unsigned char *) headers[i], strlen(headers[i]), encoding, &tmp, &height, &space); ! if (tmp > widths[i]) ! widths[i] = tmp; if (height > heights[i]) heights[i] = height; if (space > format_space[i]) format_space[i] = space; } ! for (i = 0, ptr = cells; *ptr; ptr++, i++) { ! int numeric_locale_len; ! int height, space; - if (opt_align[i % col_count] == 'r' && opt_numeric_locale) - numeric_locale_len = additional_numeric_locale_len(*ptr); - else - numeric_locale_len = 0; - /* Get width, ignore height */ ! pg_wcssize((unsigned char *) *ptr, strlen(*ptr), encoding, &tmp, &height, &space); ! tmp += numeric_locale_len; ! if (tmp > widths[i % col_count]) ! widths[i % col_count] = tmp; if (height > heights[i % col_count]) heights[i % col_count] = height; if (space > format_space[i % col_count]) format_space[i % col_count] = space; } if (opt_border == 0) ! total_w = col_count - 1; else if (opt_border == 1) ! total_w = col_count * 3 - 1; else ! total_w = col_count * 3 + 1; for (i = 0; i < col_count; i++) ! total_w += widths[i]; /* ! * At this point: widths contains the max width of each column heights ! * contains the max height of a cell of each column format_space contains ! * maximum space required to store formatted string so we prepare the ! * formatting structures */ if (col_count > 0) { ! int heights_total = 0; struct lineptr *lineptr; for (i = 0; i < col_count; i++) ! heights_total += heights[i]; ! lineptr = lineptr_list = pg_local_calloc(heights_total, sizeof(*lineptr_list)); for (i = 0; i < col_count; i++) { col_lineptrs[i] = lineptr; lineptr += heights[i]; ! format_buf[i] = pg_local_malloc(format_space[i]); col_lineptrs[i]->ptr = format_buf[i]; } --- 449,570 ---- if (col_count > 0) { ! width_header = pg_local_calloc(col_count, sizeof(*width_header)); ! width_average = pg_local_calloc(col_count, sizeof(*width_average)); ! width_max = pg_local_calloc(col_count, sizeof(*width_max)); ! width_wrap = pg_local_calloc(col_count, sizeof(*width_wrap)); heights = pg_local_calloc(col_count, sizeof(*heights)); col_lineptrs = pg_local_calloc(col_count, sizeof(*col_lineptrs)); format_space = pg_local_calloc(col_count, sizeof(*format_space)); format_buf = pg_local_calloc(col_count, sizeof(*format_buf)); ! header_done = pg_local_calloc(col_count, sizeof(*header_done)); ! bytes_output = pg_local_calloc(col_count, sizeof(*bytes_output)); } else { ! width_header = NULL; ! width_average = NULL; ! width_max = NULL; ! width_wrap = NULL; heights = NULL; col_lineptrs = NULL; format_space = NULL; format_buf = NULL; ! header_done = NULL; ! bytes_output = NULL; } ! /* scan all column headers, find maximum width */ for (i = 0; i < col_count; i++) { /* Get width & height */ ! int width, ! height, space; ! pg_wcssize((unsigned char *) headers[i], strlen(headers[i]), encoding, &width, &height, &space); ! if (width > width_max[i]) ! width_max[i] = width; if (height > heights[i]) heights[i] = height; if (space > format_space[i]) format_space[i] = space; + + width_header[i] = width; } ! /* scan all rows, find maximum width, compute cell_count */ ! for (i = 0, ptr = cells; *ptr; ptr++, i++, cell_count++) { ! int width, ! height, space; /* Get width, ignore height */ ! pg_wcssize((unsigned char *) *ptr, strlen(*ptr), encoding, &width, &height, &space); ! if (opt_numeric_locale && opt_align[i % col_count] == 'r') ! { ! width += additional_numeric_locale_len(*ptr); ! space += additional_numeric_locale_len(*ptr); ! } ! ! if (width > width_max[i % col_count]) ! width_max[i % col_count] = width; if (height > heights[i % col_count]) heights[i % col_count] = height; if (space > format_space[i % col_count]) format_space[i % col_count] = space; + + width_average[i % col_count] += width; + } + + /* If we have rows, compute average */ + if (col_count != 0 && cell_count != 0) + { + int rows = cell_count / col_count; + + for (i = 0; i < col_count; i++) + width_average[i % col_count] /= rows; } + /* adjust the total display width based on border style */ if (opt_border == 0) ! width_total = col_count - 1; else if (opt_border == 1) ! width_total = col_count * 3 - 1; else ! width_total = col_count * 3 + 1; ! total_header_width = width_total; for (i = 0; i < col_count; i++) ! { ! width_total += width_max[i]; ! total_header_width += width_header[i]; ! } /* ! * At this point: width_max[] contains the max width of each column, ! * heights[] contains the max number of lines in each column, ! * format_space[] contains the maximum storage space for formatting ! * strings, width_total contains the giant width sum. Now we allocate ! * some memory... */ if (col_count > 0) { ! int height_total = 0; struct lineptr *lineptr; for (i = 0; i < col_count; i++) ! height_total += heights[i]; ! lineptr = lineptr_list = pg_local_calloc(height_total, sizeof(*lineptr_list)); for (i = 0; i < col_count; i++) { col_lineptrs[i] = lineptr; lineptr += heights[i]; ! format_buf[i] = pg_local_malloc(format_space[i] + 1); col_lineptrs[i]->ptr = format_buf[i]; } *************** *** 535,571 **** else lineptr_list = NULL; if (opt->start_table) { /* print title */ if (title && !opt_tuples_only) { ! /* Get width & height */ ! int height; ! pg_wcssize((unsigned char *) title, strlen(title), encoding, &tmp, &height, NULL); ! if (tmp >= total_w) ! fprintf(fout, "%s\n", title); else ! fprintf(fout, "%-*s%s\n", (total_w - tmp) / 2, "", title); } /* print headers */ if (!opt_tuples_only) { ! int cols_todo; int line_count; if (opt_border == 2) ! _print_horizontal_line(col_count, widths, opt_border, fout); for (i = 0; i < col_count; i++) pg_wcsformat((unsigned char *) headers[i], strlen(headers[i]), encoding, col_lineptrs[i], heights[i]); ! cols_todo = col_count; line_count = 0; ! memset(complete, 0, col_count * sizeof(int)); ! while (cols_todo) { if (opt_border == 2) fprintf(fout, "|%c", line_count ? '+' : ' '); --- 572,678 ---- else lineptr_list = NULL; + + /* Default word wrap to the full width, i.e. no word wrap */ + for (i = 0; i < col_count; i++) + width_wrap[i] = width_max[i]; + + /* + * Optional optimized word wrap. Shrink columns with a high max/avg ratio. + * Slighly bias against wider columns (increases chance a narrow column + * will fit in its cell) + */ + if (opt->format == PRINT_WRAP) + { + /* Get terminal width */ + #ifdef TIOCGWINSZ + if (fout == stdout && isatty(fileno(stdout))) + { + struct winsize screen_size; + + if (ioctl(fileno(stdout), TIOCGWINSZ, &screen_size) != -1) + tcolumns = screen_size.ws_col; + } + else + #endif + tcolumns = opt->columns; + + /* + * If available columns is positive... + * and greater than the width of the unshrinkable column headers + */ + if (tcolumns > 0 && tcolumns >= total_header_width) + { + /* While there is still excess width... */ + while (width_total > tcolumns) + { + double max_ratio = 0; + int worst_col = -1; + + /* + * Find column that has the highest ratio of its maximum + * width compared to its average width. This tells us which + * column will produce the fewest wrapped values if shortened. + * width_wrap starts as equal to width_max. + */ + for (i = 0; i < col_count; i++) + { + if (width_average[i] && width_wrap[i] > width_header[i]) + { + /* Penalize wide columns by +1% of their width (0.01) */ + double ratio = (double)width_wrap[i] / width_average[i] + + width_max[i] * 0.01; + + if (ratio > max_ratio) + { + max_ratio = ratio; + worst_col = i; + } + } + } + + /* Exit loop if we can't squeeze any more. */ + if (worst_col == -1) + break; + + width_wrap[worst_col]--; + width_total--; + } + } + } + + /* time to output */ if (opt->start_table) { /* print title */ if (title && !opt_tuples_only) { ! int width, ! height; ! pg_wcssize((unsigned char *) title, strlen(title), encoding, &width, &height, NULL); ! if (width >= width_total) ! fprintf(fout, "%s\n", title); /* Aligned */ else ! fprintf(fout, "%-*s%s\n", (width_total - width) / 2, "", title); /* Centered */ } /* print headers */ if (!opt_tuples_only) { ! int more_col_wrapping; int line_count; if (opt_border == 2) ! _print_horizontal_line(col_count, width_wrap, opt_border, fout); for (i = 0; i < col_count; i++) pg_wcsformat((unsigned char *) headers[i], strlen(headers[i]), encoding, col_lineptrs[i], heights[i]); ! more_col_wrapping = col_count; line_count = 0; ! memset(header_done, false, col_count * sizeof(bool)); ! while (more_col_wrapping) { if (opt_border == 2) fprintf(fout, "|%c", line_count ? '+' : ' '); *************** *** 578,586 **** struct lineptr *this_line = col_lineptrs[i] + line_count; ! if (!complete[i]) { ! nbspace = widths[i] - this_line->width; /* centered */ fprintf(fout, "%-*s%s%-*s", --- 685,693 ---- struct lineptr *this_line = col_lineptrs[i] + line_count; ! if (!header_done[i]) { ! nbspace = width_wrap[i] - this_line->width; /* centered */ fprintf(fout, "%-*s%s%-*s", *************** *** 588,599 **** if (line_count == (heights[i] - 1) || !(this_line + 1)->ptr) { ! cols_todo--; ! complete[i] = 1; } } else ! fprintf(fout, "%*s", widths[i], ""); if (i < col_count - 1) { if (opt_border == 0) --- 695,706 ---- if (line_count == (heights[i] - 1) || !(this_line + 1)->ptr) { ! more_col_wrapping--; ! header_done[i] = 1; } } else ! fprintf(fout, "%*s", width_wrap[i], ""); if (i < col_count - 1) { if (opt_border == 0) *************** *** 611,711 **** fputc('\n', fout); } ! _print_horizontal_line(col_count, widths, opt_border, fout); } } /* print cells */ for (i = 0, ptr = cells; *ptr; i += col_count, ptr += col_count) { ! int j; ! int cols_todo = col_count; ! int line_count; /* Number of lines output so far in row */ if (cancel_pressed) break; for (j = 0; j < col_count; j++) pg_wcsformat((unsigned char *) ptr[j], strlen(ptr[j]), encoding, col_lineptrs[j], heights[j]); ! line_count = 0; ! memset(complete, 0, col_count * sizeof(int)); ! while (cols_todo) ! { ! /* beginning of line */ ! if (opt_border == 2) ! fputs("| ", fout); ! else if (opt_border == 1) ! fputc(' ', fout); ! for (j = 0; j < col_count; j++) { ! struct lineptr *this_line = col_lineptrs[j] + line_count; ! bool finalspaces = (opt_border == 2 || j != col_count - 1); ! if (complete[j]) /* Just print spaces... */ ! { ! if (finalspaces) ! fprintf(fout, "%*s", widths[j], ""); ! } ! else { ! /* content */ ! if (opt_align[j] == 'r') { ! if (opt_numeric_locale) { ! /* ! * Assumption: This code used only on strings ! * without multibyte characters, otherwise ! * this_line->width < strlen(this_ptr) and we get ! * an overflow ! */ ! char *my_cell = format_numeric_locale((char *) this_line->ptr); ! ! fprintf(fout, "%*s%s", ! (int) (widths[i % col_count] - strlen(my_cell)), "", ! my_cell); ! free(my_cell); } - else - fprintf(fout, "%*s%s", - widths[j] - this_line->width, "", - this_line->ptr); } ! else ! fprintf(fout, "%-s%*s", this_line->ptr, ! finalspaces ? (widths[j] - this_line->width) : 0, ""); ! /* If at the right height, done this col */ ! if (line_count == heights[j] - 1 || !this_line[1].ptr) { ! complete[j] = 1; ! cols_todo--; } } ! /* divider */ ! if ((j + 1) % col_count) { ! if (opt_border == 0) ! fputc(' ', fout); ! else if (line_count == 0) ! fputs(" | ", fout); ! else ! fprintf(fout, " %c ", complete[j + 1] ? ' ' : ':'); } } ! if (opt_border == 2) ! fputs(" |", fout); ! fputc('\n', fout); ! line_count++; ! } } if (opt->stop_table) { if (opt_border == 2 && !cancel_pressed) ! _print_horizontal_line(col_count, widths, opt_border, fout); /* print footers */ if (footers && !opt_tuples_only && !cancel_pressed) --- 718,877 ---- fputc('\n', fout); } ! _print_horizontal_line(col_count, width_wrap, opt_border, fout); } } /* print cells */ for (i = 0, ptr = cells; *ptr; i += col_count, ptr += col_count) { ! bool more_newline_values, ! more_col_wrapping; ! int line_count = 0, col_line_count; if (cancel_pressed) break; + /* + * Format each cell. Format again, it is a numeric formatting locale + * (e.g. 123,456 vs. 123456) + */ for (j = 0; j < col_count; j++) + { pg_wcsformat((unsigned char *) ptr[j], strlen(ptr[j]), encoding, col_lineptrs[j], heights[j]); + if (opt_numeric_locale && opt_align[j % col_count] == 'r') + { + char *my_cell; ! my_cell = format_numeric_locale((char *) col_lineptrs[j]->ptr); ! strcpy((char *) col_lineptrs[j]->ptr, my_cell); /* Buffer IS large ! * enough... now */ ! free(my_cell); ! } ! } ! /* Print rows to console */ ! memset(bytes_output, 0, col_count * sizeof(int)); ! ! do ! { ! col_line_count = 0; ! do { ! /* left border */ ! if (opt_border == 2) ! fputs("| ", fout); ! else if (opt_border == 1) ! fputc(' ', fout); ! more_col_wrapping = false; ! ! /* for each column */ ! for (j = 0; j < col_count; j++) { ! /* Past column height so pad for other columns */ ! if (line_count >= heights[j]) ! { ! fprintf(fout, "%*s", width_wrap[j], ""); ! } ! else { ! struct lineptr *this_line = &col_lineptrs[j][line_count]; ! int bytes_to_output, chars_to_output = width_wrap[j]; ! ! /* Get strlen() of the width_wrap character */ ! bytes_to_output = mb_strlen_max_width(this_line->ptr + ! bytes_output[j], &chars_to_output, encoding); ! ! /* value fills column? */ ! if (chars_to_output == width_wrap[j]) ! { ! /* If we are filling the column, alignment doesn't matter */ ! ! fprintf(fout, "%.*s", (int) bytes_to_output, ! this_line->ptr + bytes_output[j]); ! bytes_output[j] += bytes_to_output; ! /* Is the string done? */ ! more_col_wrapping = (*(this_line->ptr + bytes_output[j]) != 0); ! } ! else /* last line of column value */ { ! if (opt_align[j] == 'r') /* Right aligned cell */ ! { ! /* spaces first */ ! fprintf(fout, "%*s", width_wrap[j] - chars_to_output, ""); ! fprintf(fout, "%-s", this_line->ptr + bytes_output[j]); ! } ! else /* Left aligned cell */ ! { ! /* spaces second */ ! fprintf(fout, "%-s", this_line->ptr + bytes_output[j]); ! fprintf(fout, "%*s", width_wrap[j] - chars_to_output, ""); ! } ! bytes_output[j] += bytes_to_output; } } ! ! /* print a divider, middle columns only */ ! if ((j + 1) % col_count) { ! if (opt_border == 0) ! fputc(' ', fout); ! /* first line for values? */ ! else if (line_count == 0 && col_line_count == 0) ! fputs(" | ", fout); ! /* next value is beyond height? */ ! else if (line_count >= heights[j + 1]) ! fputs(" ", fout); ! /* start of another newline string? */ ! else if (col_line_count == 0) ! fputs(" : ", fout); ! else ! { ! /* does the next column wrap to this line? */ ! struct lineptr *this_line = &col_lineptrs[j+1][line_count]; ! bool string_done = *(this_line->ptr + bytes_output[j+1]) == 0; ! ! fputs(string_done ? " " : " ; ", fout); ! } } } ! /* end of row border */ ! if (opt_border == 2) ! fputs(" | ", fout); ! fputc('\n', fout); ! col_line_count++; ! ! } while (more_col_wrapping); ! ! /* ! * Check if any columns have line continuations due to \n in the ! * cell. ! */ ! line_count++; ! ! /* Do we have more lines for any column value, i.e embedded \n? */ ! more_newline_values = false; ! for (j = 0; j < col_count; j++) ! { ! if (line_count < heights[j]) { ! if (col_lineptrs[j][line_count].ptr) ! { ! more_newline_values = true; ! bytes_output[j] = 0; ! } } } ! ! } while (more_newline_values); } if (opt->stop_table) { if (opt_border == 2 && !cancel_pressed) ! _print_horizontal_line(col_count, width_wrap, opt_border, fout); /* print footers */ if (footers && !opt_tuples_only && !cancel_pressed) *************** *** 722,732 **** } /* clean up */ ! free(widths); free(heights); free(col_lineptrs); free(format_space); ! free(complete); free(lineptr_list); for (i = 0; i < col_count; i++) free(format_buf[i]); --- 888,902 ---- } /* clean up */ ! free(width_header); ! free(width_average); ! free(width_max); ! free(width_wrap); free(heights); free(col_lineptrs); free(format_space); ! free(header_done); ! free(bytes_output); free(lineptr_list); for (i = 0; i < col_count; i++) free(format_buf[i]); *************** *** 754,760 **** dheight = 1, hformatsize = 0, dformatsize = 0; - int tmp = 0; char *divider; unsigned int cell_count = 0; struct lineptr *hlineptr, --- 924,929 ---- *************** *** 779,790 **** /* Find the maximum dimensions for the headers */ for (i = 0; i < col_count; i++) { ! int height, fs; ! pg_wcssize((unsigned char *) headers[i], strlen(headers[i]), encoding, &tmp, &height, &fs); ! if (tmp > hwidth) ! hwidth = tmp; if (height > hheight) hheight = height; if (fs > hformatsize) --- 948,960 ---- /* Find the maximum dimensions for the headers */ for (i = 0; i < col_count; i++) { ! int width, ! height, fs; ! pg_wcssize((unsigned char *) headers[i], strlen(headers[i]), encoding, &width, &height, &fs); ! if (width > hwidth) ! hwidth = width; if (height > hheight) hheight = height; if (fs > hformatsize) *************** *** 799,805 **** for (i = 0, ptr = cells; *ptr; ptr++, i++) { int numeric_locale_len; ! int height, fs; if (opt_align[i % col_count] == 'r' && opt_numeric_locale) --- 969,976 ---- for (i = 0, ptr = cells; *ptr; ptr++, i++) { int numeric_locale_len; ! int width, ! height, fs; if (opt_align[i % col_count] == 'r' && opt_numeric_locale) *************** *** 807,816 **** else numeric_locale_len = 0; ! pg_wcssize((unsigned char *) *ptr, strlen(*ptr), encoding, &tmp, &height, &fs); ! tmp += numeric_locale_len; ! if (tmp > dwidth) ! dwidth = tmp; if (height > dheight) dheight = height; if (fs > dformatsize) --- 978,987 ---- else numeric_locale_len = 0; ! pg_wcssize((unsigned char *) *ptr, strlen(*ptr), encoding, &width, &height, &fs); ! width += numeric_locale_len; ! if (width > dwidth) ! dwidth = width; if (height > dheight) dheight = height; if (fs > dformatsize) *************** *** 1879,1884 **** --- 2050,2056 ---- opt, output); break; case PRINT_ALIGNED: + case PRINT_WRAP: if (opt->expanded) print_aligned_vertical(title, headers, cells, footers, align, opt, output); *************** *** 2066,2068 **** --- 2238,2262 ---- else thousands_sep = "."; } + + /* + * Returns the byte length to the end of the specified character + * and number of display characters processed (useful if the string + * is shorter then dpylen). + */ + static int + mb_strlen_max_width(char *str, int *width, int encoding) + { + int width_left = *width; + char *start = str; + + *width = 0; + while (*str && width_left--) + { + str += PQmblen(str, encoding); + (*width)++; + } + + /* last byte */ + return str - start; + } Index: src/bin/psql/print.h =================================================================== RCS file: /cvsroot/pgsql/src/bin/psql/print.h,v retrieving revision 1.35 diff -c -c -r1.35 print.h *** src/bin/psql/print.h 1 Jan 2008 19:45:56 -0000 1.35 --- src/bin/psql/print.h 20 Apr 2008 03:53:42 -0000 *************** *** 21,26 **** --- 21,27 ---- PRINT_NOTHING = 0, /* to make sure someone initializes this */ PRINT_UNALIGNED, PRINT_ALIGNED, + PRINT_WRAP, PRINT_HTML, PRINT_LATEX, PRINT_TROFF_MS *************** *** 47,52 **** --- 48,54 ---- * decimal marker */ char *tableAttr; /* attributes for HTML <table ...> */ int encoding; /* character encoding */ + int columns; /* target width for wrap format */ } printTableOpt;
The newline handling code was, by far, the most complex part of this patch. While I think it would be nicer to "filter up" past the newline, I just don't see this as a common need. Large text fields with newlines are difficult to really view in psql anyway, and they are likely to be the longest columns in any given query. Bottom line: not worth messing with. I'm split on the new formatting style. It looks a lot less "grid-like", which I might like once I get used to it (which will be a while, because I can't run my own patch in daily use, as my servers are too old). I use a version of the wrapping patch that I backported to postgres 8.1, which was prior to multibyte support, and much much simpler. I got this warning compiling: print.c:784: warning: pointer targets in passing argument 1 of ‘mb_strlen_max_width’ differ in signedness And I did have trouble applying the patch -- I had to manually give it the filename, and tell it to reverse the patch.
1) "\pset columns XX" should make it clear that's for file output only. 2) There's an extra space, which breaks \pset border 2 717c717 < fputc(' ', fout);; --- > fputc(' ', fout); 842c842 < fputs(" | ", fout); --- > fputs(" |", f 2) With \pset border 2, the far left border, for symmetry, should work like the middle borders. 3) I'm getting bolder: how about having \pset format wrapped as the default? Any downsides?
Alvaro Herrera wrote: > The developer's FAQ is supposed to contain this kind of thing, but I > think it's rather thin on actual details. (Some time ago it was > proposed to create a "style guide", but people here thought it was a > waste of time and "it will not cover what's really important", so we're > stuck with repeating the advice over and over.) Excellent wiki material ...
Bryce Nesbitt wrote: > The newline handling code was, by far, the most complex part of this > patch. While I think it would be nicer to "filter up" past the newline, > I just don't see this as a common need. Large text fields with newlines > are difficult to really view in psql anyway, and they are likely to be > the longest columns in any given query. Bottom line: not worth messing > with. OK, we can always return to it. > I'm split on the new formatting style. It looks a lot less "grid-like", > which I might like once I get used to it (which will be a while, because > I can't run my own patch in daily use, as my servers are too old). I > use a version of the wrapping patch that I backported to postgres 8.1, > which was prior to multibyte support, and much much simpler. What "new formatting style" are you referring to above? Did I modify what you sent as the original patch? > I got this warning compiling: > print.c:784: warning: pointer targets in passing argument 1 of > ?mb_strlen_max_width? differ in signedness > And I did have trouble applying the patch -- I had to manually give it > the filename, and tell it to reverse the patch. OK, fixed. -- Bruce Momjian <bruce@momjian.us> http://momjian.us EnterpriseDB http://enterprisedb.com + If your life is a hard drive, Christ can be your backup. +
Bryce Nesbitt wrote: > 1) "\pset columns XX" should make it clear that's for file output only. OK, docs updated. > 2) There's an extra space, which breaks \pset border 2 > > 717c717 > < fputc(' ', fout);; > --- > > fputc(' ', fout); > 842c842 > < fputs(" | ", fout); > --- > > fputs(" |", f OK, got them fixed. > 2) With \pset border 2, the far left border, for symmetry, should work > like the middle borders. OK, how does it look now with this patch? > 3) I'm getting bolder: how about having \pset format wrapped as the > default? Any downsides? I think we are going to want it as the default for many psql informational commands, like \df. Not sure about a more general default. We were just discussing using \x as a default for wide output but it seems this wrap style is probably a better solution than switching for \x for wide columns (less distracting for the user and cleaner). That will have to be a separate discussion once we are done. Oh, I found a problem in my coding of the new wrap function I added. While I handled the case where a character might span multiple bytes, I assumed all characters had a display width of one. You can see from pg_wcsformat()'s use of PQdsplen() that this isn't always the case. I have modified the patch to properly use PQdsplen() but we are going to need multi-byte users to test this once we are done. -- Bruce Momjian <bruce@momjian.us> http://momjian.us EnterpriseDB http://enterprisedb.com + If your life is a hard drive, Christ can be your backup. + Index: doc/src/sgml/ref/psql-ref.sgml =================================================================== RCS file: /cvsroot/pgsql/doc/src/sgml/ref/psql-ref.sgml,v retrieving revision 1.199 diff -c -c -r1.199 psql-ref.sgml *** doc/src/sgml/ref/psql-ref.sgml 30 Mar 2008 18:10:20 -0000 1.199 --- doc/src/sgml/ref/psql-ref.sgml 21 Apr 2008 15:27:55 -0000 *************** *** 1513,1519 **** <listitem> <para> Sets the output format to one of <literal>unaligned</literal>, ! <literal>aligned</literal>, <literal>html</literal>, <literal>latex</literal>, or <literal>troff-ms</literal>. Unique abbreviations are allowed. (That would mean one letter is enough.) --- 1513,1520 ---- <listitem> <para> Sets the output format to one of <literal>unaligned</literal>, ! <literal>aligned</literal>, <literal>wrapped</literal>, ! <literal>html</literal>, <literal>latex</literal>, or <literal>troff-ms</literal>. Unique abbreviations are allowed. (That would mean one letter is enough.) *************** *** 1525,1532 **** is intended to create output that might be intended to be read in by other programs (tab-separated, comma-separated). <quote>Aligned</quote> mode is the standard, human-readable, ! nicely formatted text output that is default. The ! <quote><acronym>HTML</acronym></quote> and <quote>LaTeX</quote> modes put out tables that are intended to be included in documents using the respective mark-up language. They are not complete documents! (This might not be --- 1526,1535 ---- is intended to create output that might be intended to be read in by other programs (tab-separated, comma-separated). <quote>Aligned</quote> mode is the standard, human-readable, ! nicely formatted text output that is default. ! <quote>Wrapped</quote> is like <literal>aligned</> but wraps ! the output to fit the detected screen width or <literal>\pset ! columns</>. The <quote><acronym>HTML</acronym></quote> and <quote>LaTeX</quote> modes put out tables that are intended to be included in documents using the respective mark-up language. They are not complete documents! (This might not be *************** *** 1537,1542 **** --- 1540,1556 ---- </varlistentry> <varlistentry> + <term><literal>columns</literal></term> + <listitem> + <para> + Controls the target width for the <literal>wrap</> format when + output is to a file, or the operating system does not support + screen width detection. + </para> + </listitem> + </varlistentry> + + <varlistentry> <term><literal>border</literal></term> <listitem> <para> Index: src/bin/psql/command.c =================================================================== RCS file: /cvsroot/pgsql/src/bin/psql/command.c,v retrieving revision 1.186 diff -c -c -r1.186 command.c *** src/bin/psql/command.c 1 Jan 2008 19:45:55 -0000 1.186 --- src/bin/psql/command.c 21 Apr 2008 15:27:56 -0000 *************** *** 1526,1531 **** --- 1526,1534 ---- case PRINT_ALIGNED: return "aligned"; break; + case PRINT_WRAP: + return "wrapped"; + break; case PRINT_HTML: return "html"; break; *************** *** 1559,1564 **** --- 1562,1569 ---- popt->topt.format = PRINT_UNALIGNED; else if (pg_strncasecmp("aligned", value, vallen) == 0) popt->topt.format = PRINT_ALIGNED; + else if (pg_strncasecmp("wrapped", value, vallen) == 0) + popt->topt.format = PRINT_WRAP; else if (pg_strncasecmp("html", value, vallen) == 0) popt->topt.format = PRINT_HTML; else if (pg_strncasecmp("latex", value, vallen) == 0) *************** *** 1567,1573 **** popt->topt.format = PRINT_TROFF_MS; else { ! psql_error("\\pset: allowed formats are unaligned, aligned, html, latex, troff-ms\n"); return false; } --- 1572,1578 ---- popt->topt.format = PRINT_TROFF_MS; else { ! psql_error("\\pset: allowed formats are unaligned, aligned, wrapped, html, latex, troff-ms\n"); return false; } *************** *** 1748,1753 **** --- 1753,1768 ---- } } + /* set border style/width */ + else if (strcmp(param, "columns") == 0) + { + if (value) + popt->topt.columns = atoi(value); + + if (!quiet) + printf(_("Target column width for \"wrap\" format is %d.\n"), popt->topt.columns); + } + else { psql_error("\\pset: unknown option: %s\n", param); Index: src/bin/psql/mbprint.c =================================================================== RCS file: /cvsroot/pgsql/src/bin/psql/mbprint.c,v retrieving revision 1.30 diff -c -c -r1.30 mbprint.c *** src/bin/psql/mbprint.c 16 Apr 2008 18:18:00 -0000 1.30 --- src/bin/psql/mbprint.c 21 Apr 2008 15:28:01 -0000 *************** *** 204,210 **** /* * pg_wcssize takes the given string in the given encoding and returns three * values: ! * result_width: Width in display character of longest line in string * result_height: Number of lines in display output * result_format_size: Number of bytes required to store formatted representation of string */ --- 204,210 ---- /* * pg_wcssize takes the given string in the given encoding and returns three * values: ! * result_width: Width in display characters of the longest line in string * result_height: Number of lines in display output * result_format_size: Number of bytes required to store formatted representation of string */ *************** *** 279,284 **** --- 279,288 ---- return width; } + /* + * Filter out unprintable characters, companion to wcs_size. + * Break input into lines (based on \n or \r). + */ void pg_wcsformat(unsigned char *pwcs, size_t len, int encoding, struct lineptr * lines, int count) *************** *** 353,364 **** } len -= chlen; } ! *ptr++ = '\0'; lines->width = linewidth; ! lines++; ! count--; ! if (count > 0) lines->ptr = NULL; } unsigned char * --- 357,373 ---- } len -= chlen; } ! *ptr++ = '\0'; /* Terminate formatted string */ ! lines->width = linewidth; ! ! /* Fill remaining array slots with nulls */ ! while (--count) ! { ! lines++; lines->ptr = NULL; + lines->width = 0; + } } unsigned char * Index: src/bin/psql/print.c =================================================================== RCS file: /cvsroot/pgsql/src/bin/psql/print.c,v retrieving revision 1.97 diff -c -c -r1.97 print.c *** src/bin/psql/print.c 27 Mar 2008 03:57:34 -0000 1.97 --- src/bin/psql/print.c 21 Apr 2008 15:28:02 -0000 *************** *** 28,33 **** --- 28,35 ---- #include "mbprint.h" + static int strlen_max_width(unsigned char *str, int *target_width, int encoding); + /* * We define the cancel_pressed flag in this file, rather than common.c where * it naturally belongs, because this file is also used by non-psql programs *************** *** 43,48 **** --- 45,51 ---- static char *grouping; static char *thousands_sep; + static void * pg_local_malloc(size_t size) { *************** *** 396,401 **** --- 399,407 ---- } + /* + * Prety pretty boxes around cells. + */ static void print_aligned_text(const char *title, const char *const * headers, const char *const * cells, const char *const * footers, *************** *** 404,429 **** { bool opt_tuples_only = opt->tuples_only; bool opt_numeric_locale = opt->numericLocale; - unsigned short int opt_border = opt->border; int encoding = opt->encoding; ! unsigned int col_count = 0; ! unsigned int cell_count = 0; ! unsigned int i; ! int tmp; ! unsigned int *widths, ! total_w; ! unsigned int *heights; ! unsigned int *format_space; unsigned char **format_buf; const char *const * ptr; ! struct lineptr **col_lineptrs; /* pointers to line pointer for each ! * column */ struct lineptr *lineptr_list; /* complete list of linepointers */ ! int *complete; /* Array remembering which columns have ! * completed output */ if (cancel_pressed) return; --- 410,441 ---- { bool opt_tuples_only = opt->tuples_only; bool opt_numeric_locale = opt->numericLocale; int encoding = opt->encoding; ! unsigned short int opt_border = opt->border; ! ! unsigned int col_count = 0, cell_count = 0; ! ! unsigned int i, ! j; ! ! unsigned int *width_header, ! *width_max, ! *width_wrap, ! *width_average; ! unsigned int *heights, ! *format_space; unsigned char **format_buf; + unsigned int width_total; + unsigned int total_header_width; const char *const * ptr; ! struct lineptr **col_lineptrs; /* pointers to line pointer per column */ struct lineptr *lineptr_list; /* complete list of linepointers */ ! bool *header_done; /* Have all header lines been output? */ ! int *bytes_output; /* Bytes output for column value */ ! int tcolumns = 0; /* Width of interactive console */ if (cancel_pressed) return; *************** *** 437,533 **** if (col_count > 0) { ! widths = pg_local_calloc(col_count, sizeof(*widths)); heights = pg_local_calloc(col_count, sizeof(*heights)); col_lineptrs = pg_local_calloc(col_count, sizeof(*col_lineptrs)); format_space = pg_local_calloc(col_count, sizeof(*format_space)); format_buf = pg_local_calloc(col_count, sizeof(*format_buf)); ! complete = pg_local_calloc(col_count, sizeof(*complete)); } else { ! widths = NULL; heights = NULL; col_lineptrs = NULL; format_space = NULL; format_buf = NULL; ! complete = NULL; } ! /* count cells (rows * cols) */ ! for (ptr = cells; *ptr; ptr++) ! cell_count++; ! ! /* calc column widths */ for (i = 0; i < col_count; i++) { /* Get width & height */ ! int height, space; ! pg_wcssize((unsigned char *) headers[i], strlen(headers[i]), encoding, &tmp, &height, &space); ! if (tmp > widths[i]) ! widths[i] = tmp; if (height > heights[i]) heights[i] = height; if (space > format_space[i]) format_space[i] = space; } ! for (i = 0, ptr = cells; *ptr; ptr++, i++) { ! int numeric_locale_len; ! int height, space; - if (opt_align[i % col_count] == 'r' && opt_numeric_locale) - numeric_locale_len = additional_numeric_locale_len(*ptr); - else - numeric_locale_len = 0; - /* Get width, ignore height */ ! pg_wcssize((unsigned char *) *ptr, strlen(*ptr), encoding, &tmp, &height, &space); ! tmp += numeric_locale_len; ! if (tmp > widths[i % col_count]) ! widths[i % col_count] = tmp; if (height > heights[i % col_count]) heights[i % col_count] = height; if (space > format_space[i % col_count]) format_space[i % col_count] = space; } if (opt_border == 0) ! total_w = col_count - 1; else if (opt_border == 1) ! total_w = col_count * 3 - 1; else ! total_w = col_count * 3 + 1; for (i = 0; i < col_count; i++) ! total_w += widths[i]; /* ! * At this point: widths contains the max width of each column heights ! * contains the max height of a cell of each column format_space contains ! * maximum space required to store formatted string so we prepare the ! * formatting structures */ if (col_count > 0) { ! int heights_total = 0; struct lineptr *lineptr; for (i = 0; i < col_count; i++) ! heights_total += heights[i]; ! lineptr = lineptr_list = pg_local_calloc(heights_total, sizeof(*lineptr_list)); for (i = 0; i < col_count; i++) { col_lineptrs[i] = lineptr; lineptr += heights[i]; ! format_buf[i] = pg_local_malloc(format_space[i]); col_lineptrs[i]->ptr = format_buf[i]; } --- 449,570 ---- if (col_count > 0) { ! width_header = pg_local_calloc(col_count, sizeof(*width_header)); ! width_average = pg_local_calloc(col_count, sizeof(*width_average)); ! width_max = pg_local_calloc(col_count, sizeof(*width_max)); ! width_wrap = pg_local_calloc(col_count, sizeof(*width_wrap)); heights = pg_local_calloc(col_count, sizeof(*heights)); col_lineptrs = pg_local_calloc(col_count, sizeof(*col_lineptrs)); format_space = pg_local_calloc(col_count, sizeof(*format_space)); format_buf = pg_local_calloc(col_count, sizeof(*format_buf)); ! header_done = pg_local_calloc(col_count, sizeof(*header_done)); ! bytes_output = pg_local_calloc(col_count, sizeof(*bytes_output)); } else { ! width_header = NULL; ! width_average = NULL; ! width_max = NULL; ! width_wrap = NULL; heights = NULL; col_lineptrs = NULL; format_space = NULL; format_buf = NULL; ! header_done = NULL; ! bytes_output = NULL; } ! /* scan all column headers, find maximum width */ for (i = 0; i < col_count; i++) { /* Get width & height */ ! int width, ! height, space; ! pg_wcssize((unsigned char *) headers[i], strlen(headers[i]), encoding, &width, &height, &space); ! if (width > width_max[i]) ! width_max[i] = width; if (height > heights[i]) heights[i] = height; if (space > format_space[i]) format_space[i] = space; + + width_header[i] = width; } ! /* scan all rows, find maximum width, compute cell_count */ ! for (i = 0, ptr = cells; *ptr; ptr++, i++, cell_count++) { ! int width, ! height, space; /* Get width, ignore height */ ! pg_wcssize((unsigned char *) *ptr, strlen(*ptr), encoding, &width, &height, &space); ! if (opt_numeric_locale && opt_align[i % col_count] == 'r') ! { ! width += additional_numeric_locale_len(*ptr); ! space += additional_numeric_locale_len(*ptr); ! } ! ! if (width > width_max[i % col_count]) ! width_max[i % col_count] = width; if (height > heights[i % col_count]) heights[i % col_count] = height; if (space > format_space[i % col_count]) format_space[i % col_count] = space; + + width_average[i % col_count] += width; + } + + /* If we have rows, compute average */ + if (col_count != 0 && cell_count != 0) + { + int rows = cell_count / col_count; + + for (i = 0; i < col_count; i++) + width_average[i % col_count] /= rows; } + /* adjust the total display width based on border style */ if (opt_border == 0) ! width_total = col_count - 1; else if (opt_border == 1) ! width_total = col_count * 3 - 1; else ! width_total = col_count * 3 + 1; ! total_header_width = width_total; for (i = 0; i < col_count; i++) ! { ! width_total += width_max[i]; ! total_header_width += width_header[i]; ! } /* ! * At this point: width_max[] contains the max width of each column, ! * heights[] contains the max number of lines in each column, ! * format_space[] contains the maximum storage space for formatting ! * strings, width_total contains the giant width sum. Now we allocate ! * some memory... */ if (col_count > 0) { ! int height_total = 0; struct lineptr *lineptr; for (i = 0; i < col_count; i++) ! height_total += heights[i]; ! lineptr = lineptr_list = pg_local_calloc(height_total, sizeof(*lineptr_list)); for (i = 0; i < col_count; i++) { col_lineptrs[i] = lineptr; lineptr += heights[i]; ! format_buf[i] = pg_local_malloc(format_space[i] + 1); col_lineptrs[i]->ptr = format_buf[i]; } *************** *** 535,571 **** else lineptr_list = NULL; if (opt->start_table) { /* print title */ if (title && !opt_tuples_only) { ! /* Get width & height */ ! int height; ! pg_wcssize((unsigned char *) title, strlen(title), encoding, &tmp, &height, NULL); ! if (tmp >= total_w) ! fprintf(fout, "%s\n", title); else ! fprintf(fout, "%-*s%s\n", (total_w - tmp) / 2, "", title); } /* print headers */ if (!opt_tuples_only) { ! int cols_todo; int line_count; if (opt_border == 2) ! _print_horizontal_line(col_count, widths, opt_border, fout); for (i = 0; i < col_count; i++) pg_wcsformat((unsigned char *) headers[i], strlen(headers[i]), encoding, col_lineptrs[i], heights[i]); ! cols_todo = col_count; line_count = 0; ! memset(complete, 0, col_count * sizeof(int)); ! while (cols_todo) { if (opt_border == 2) fprintf(fout, "|%c", line_count ? '+' : ' '); --- 572,678 ---- else lineptr_list = NULL; + + /* Default word wrap to the full width, i.e. no word wrap */ + for (i = 0; i < col_count; i++) + width_wrap[i] = width_max[i]; + + /* + * Optional optimized word wrap. Shrink columns with a high max/avg ratio. + * Slighly bias against wider columns (increases chance a narrow column + * will fit in its cell) + */ + if (opt->format == PRINT_WRAP) + { + /* Get terminal width */ + #ifdef TIOCGWINSZ + if (fout == stdout && isatty(fileno(stdout))) + { + struct winsize screen_size; + + if (ioctl(fileno(stdout), TIOCGWINSZ, &screen_size) != -1) + tcolumns = screen_size.ws_col; + } + else + #endif + tcolumns = opt->columns; + + /* + * If available columns is positive... + * and greater than the width of the unshrinkable column headers + */ + if (tcolumns > 0 && tcolumns >= total_header_width) + { + /* While there is still excess width... */ + while (width_total > tcolumns) + { + double max_ratio = 0; + int worst_col = -1; + + /* + * Find column that has the highest ratio of its maximum + * width compared to its average width. This tells us which + * column will produce the fewest wrapped values if shortened. + * width_wrap starts as equal to width_max. + */ + for (i = 0; i < col_count; i++) + { + if (width_average[i] && width_wrap[i] > width_header[i]) + { + /* Penalize wide columns by +1% of their width (0.01) */ + double ratio = (double)width_wrap[i] / width_average[i] + + width_max[i] * 0.01; + + if (ratio > max_ratio) + { + max_ratio = ratio; + worst_col = i; + } + } + } + + /* Exit loop if we can't squeeze any more. */ + if (worst_col == -1) + break; + + width_wrap[worst_col]--; + width_total--; + } + } + } + + /* time to output */ if (opt->start_table) { /* print title */ if (title && !opt_tuples_only) { ! int width, ! height; ! pg_wcssize((unsigned char *) title, strlen(title), encoding, &width, &height, NULL); ! if (width >= width_total) ! fprintf(fout, "%s\n", title); /* Aligned */ else ! fprintf(fout, "%-*s%s\n", (width_total - width) / 2, "", title); /* Centered */ } /* print headers */ if (!opt_tuples_only) { ! int more_col_wrapping; int line_count; if (opt_border == 2) ! _print_horizontal_line(col_count, width_wrap, opt_border, fout); for (i = 0; i < col_count; i++) pg_wcsformat((unsigned char *) headers[i], strlen(headers[i]), encoding, col_lineptrs[i], heights[i]); ! more_col_wrapping = col_count; line_count = 0; ! memset(header_done, false, col_count * sizeof(bool)); ! while (more_col_wrapping) { if (opt_border == 2) fprintf(fout, "|%c", line_count ? '+' : ' '); *************** *** 578,586 **** struct lineptr *this_line = col_lineptrs[i] + line_count; ! if (!complete[i]) { ! nbspace = widths[i] - this_line->width; /* centered */ fprintf(fout, "%-*s%s%-*s", --- 685,693 ---- struct lineptr *this_line = col_lineptrs[i] + line_count; ! if (!header_done[i]) { ! nbspace = width_wrap[i] - this_line->width; /* centered */ fprintf(fout, "%-*s%s%-*s", *************** *** 588,599 **** if (line_count == (heights[i] - 1) || !(this_line + 1)->ptr) { ! cols_todo--; ! complete[i] = 1; } } else ! fprintf(fout, "%*s", widths[i], ""); if (i < col_count - 1) { if (opt_border == 0) --- 695,706 ---- if (line_count == (heights[i] - 1) || !(this_line + 1)->ptr) { ! more_col_wrapping--; ! header_done[i] = 1; } } else ! fprintf(fout, "%*s", width_wrap[i], ""); if (i < col_count - 1) { if (opt_border == 0) *************** *** 607,711 **** if (opt_border == 2) fputs(" |", fout); else if (opt_border == 1) ! fputc(' ', fout);; fputc('\n', fout); } ! _print_horizontal_line(col_count, widths, opt_border, fout); } } /* print cells */ for (i = 0, ptr = cells; *ptr; i += col_count, ptr += col_count) { ! int j; ! int cols_todo = col_count; ! int line_count; /* Number of lines output so far in row */ if (cancel_pressed) break; for (j = 0; j < col_count; j++) pg_wcsformat((unsigned char *) ptr[j], strlen(ptr[j]), encoding, col_lineptrs[j], heights[j]); ! line_count = 0; ! memset(complete, 0, col_count * sizeof(int)); ! while (cols_todo) ! { ! /* beginning of line */ ! if (opt_border == 2) ! fputs("| ", fout); ! else if (opt_border == 1) ! fputc(' ', fout); ! for (j = 0; j < col_count; j++) { ! struct lineptr *this_line = col_lineptrs[j] + line_count; ! bool finalspaces = (opt_border == 2 || j != col_count - 1); ! if (complete[j]) /* Just print spaces... */ ! { ! if (finalspaces) ! fprintf(fout, "%*s", widths[j], ""); ! } ! else { ! /* content */ ! if (opt_align[j] == 'r') { ! if (opt_numeric_locale) { ! /* ! * Assumption: This code used only on strings ! * without multibyte characters, otherwise ! * this_line->width < strlen(this_ptr) and we get ! * an overflow ! */ ! char *my_cell = format_numeric_locale((char *) this_line->ptr); ! ! fprintf(fout, "%*s%s", ! (int) (widths[i % col_count] - strlen(my_cell)), "", ! my_cell); ! free(my_cell); } ! else ! fprintf(fout, "%*s%s", ! widths[j] - this_line->width, "", ! this_line->ptr); } ! else ! fprintf(fout, "%-s%*s", this_line->ptr, ! finalspaces ? (widths[j] - this_line->width) : 0, ""); ! /* If at the right height, done this col */ ! if (line_count == heights[j] - 1 || !this_line[1].ptr) { ! complete[j] = 1; ! cols_todo--; } } ! /* divider */ ! if ((j + 1) % col_count) { ! if (opt_border == 0) ! fputc(' ', fout); ! else if (line_count == 0) ! fputs(" | ", fout); ! else ! fprintf(fout, " %c ", complete[j + 1] ? ' ' : ':'); } } ! if (opt_border == 2) ! fputs(" |", fout); ! fputc('\n', fout); ! line_count++; ! } } if (opt->stop_table) { if (opt_border == 2 && !cancel_pressed) ! _print_horizontal_line(col_count, widths, opt_border, fout); /* print footers */ if (footers && !opt_tuples_only && !cancel_pressed) --- 714,877 ---- if (opt_border == 2) fputs(" |", fout); else if (opt_border == 1) ! fputc(' ', fout); fputc('\n', fout); } ! _print_horizontal_line(col_count, width_wrap, opt_border, fout); } } /* print cells */ for (i = 0, ptr = cells; *ptr; i += col_count, ptr += col_count) { ! bool more_newline_values, ! more_col_wrapping; ! int line_count = 0, col_line_count; if (cancel_pressed) break; + /* + * Format each cell. Format again, it is a numeric formatting locale + * (e.g. 123,456 vs. 123456) + */ for (j = 0; j < col_count; j++) + { pg_wcsformat((unsigned char *) ptr[j], strlen(ptr[j]), encoding, col_lineptrs[j], heights[j]); + if (opt_numeric_locale && opt_align[j % col_count] == 'r') + { + char *my_cell; ! my_cell = format_numeric_locale((char *) col_lineptrs[j]->ptr); ! strcpy((char *) col_lineptrs[j]->ptr, my_cell); /* Buffer IS large ! * enough... now */ ! free(my_cell); ! } ! } ! /* Print rows to console */ ! memset(bytes_output, 0, col_count * sizeof(int)); ! ! do ! { ! col_line_count = 0; ! do { ! /* left border */ ! if (opt_border == 2) ! fputs("| ", fout); ! else if (opt_border == 1) ! fputc(' ', fout); ! more_col_wrapping = false; ! ! /* for each column */ ! for (j = 0; j < col_count; j++) { ! /* Past column height so pad for other columns */ ! if (line_count >= heights[j]) ! fprintf(fout, "%*s", width_wrap[j], ""); ! else { ! struct lineptr *this_line = &col_lineptrs[j][line_count]; ! int bytes_to_output, chars_to_output = width_wrap[j]; ! ! /* Get strlen() of the width_wrap character */ ! bytes_to_output = strlen_max_width(this_line->ptr + ! bytes_output[j], &chars_to_output, encoding); ! ! /* ! * If we exceeded width_wrap, it means the display width ! * of a single character was wider than our target width. ! * In that case, we have to pretend we are only printing ! * the target display width and make the best of it. ! */ ! if (chars_to_output > width_wrap[j]) ! chars_to_output = width_wrap[j]; ! ! if (opt_align[j] == 'r') /* Right aligned cell */ { ! /* spaces first */ ! fprintf(fout, "%*s", width_wrap[j] - chars_to_output, ""); ! fprintf(fout, "%.*s", bytes_to_output, ! this_line->ptr + bytes_output[j]); } ! else /* Left aligned cell */ ! { ! /* spaces second */ ! fprintf(fout, "%.*s", bytes_to_output, ! this_line->ptr + bytes_output[j]); ! fprintf(fout, "%*s", width_wrap[j] - chars_to_output, ""); ! } ! ! bytes_output[j] += bytes_to_output; ! ! /* Are we at the end of the string? */ ! more_col_wrapping = more_col_wrapping || ! (*(this_line->ptr + bytes_output[j]) != 0); } ! ! /* print a divider, middle columns only */ ! if ((j + 1) % col_count) { ! if (opt_border == 0) ! fputc(' ', fout); ! /* first line for values? */ ! else if (line_count == 0 && col_line_count == 0) ! fputs(" | ", fout); ! /* next value is beyond height? */ ! else if (line_count >= heights[j + 1]) ! fputs(" ", fout); ! /* start of another newline string? */ ! else if (col_line_count == 0) ! fputs(" : ", fout); ! else ! { ! /* Does the next column wrap to this line? */ ! struct lineptr *this_line = &col_lineptrs[j+1][line_count]; ! bool string_done = *(this_line->ptr + bytes_output[j+1]) == 0; ! ! fputs(string_done ? " " : " ; ", fout); ! } } } ! /* end of row border */ ! if (opt_border == 2) ! fputs(" |", fout); ! fputc('\n', fout); ! col_line_count++; ! ! } while (more_col_wrapping); ! ! /* ! * Check if any columns have line continuations due to \n in the ! * cell. ! */ ! line_count++; ! ! /* Do we have more lines for any column value, i.e embedded \n? */ ! more_newline_values = false; ! for (j = 0; j < col_count; j++) ! { ! if (line_count < heights[j]) { ! if (col_lineptrs[j][line_count].ptr) ! { ! more_newline_values = true; ! bytes_output[j] = 0; ! } } } ! ! } while (more_newline_values); } if (opt->stop_table) { if (opt_border == 2 && !cancel_pressed) ! _print_horizontal_line(col_count, width_wrap, opt_border, fout); /* print footers */ if (footers && !opt_tuples_only && !cancel_pressed) *************** *** 722,732 **** } /* clean up */ ! free(widths); free(heights); free(col_lineptrs); free(format_space); ! free(complete); free(lineptr_list); for (i = 0; i < col_count; i++) free(format_buf[i]); --- 888,902 ---- } /* clean up */ ! free(width_header); ! free(width_average); ! free(width_max); ! free(width_wrap); free(heights); free(col_lineptrs); free(format_space); ! free(header_done); ! free(bytes_output); free(lineptr_list); for (i = 0; i < col_count; i++) free(format_buf[i]); *************** *** 754,760 **** dheight = 1, hformatsize = 0, dformatsize = 0; - int tmp = 0; char *divider; unsigned int cell_count = 0; struct lineptr *hlineptr, --- 924,929 ---- *************** *** 779,790 **** /* Find the maximum dimensions for the headers */ for (i = 0; i < col_count; i++) { ! int height, fs; ! pg_wcssize((unsigned char *) headers[i], strlen(headers[i]), encoding, &tmp, &height, &fs); ! if (tmp > hwidth) ! hwidth = tmp; if (height > hheight) hheight = height; if (fs > hformatsize) --- 948,960 ---- /* Find the maximum dimensions for the headers */ for (i = 0; i < col_count; i++) { ! int width, ! height, fs; ! pg_wcssize((unsigned char *) headers[i], strlen(headers[i]), encoding, &width, &height, &fs); ! if (width > hwidth) ! hwidth = width; if (height > hheight) hheight = height; if (fs > hformatsize) *************** *** 799,805 **** for (i = 0, ptr = cells; *ptr; ptr++, i++) { int numeric_locale_len; ! int height, fs; if (opt_align[i % col_count] == 'r' && opt_numeric_locale) --- 969,976 ---- for (i = 0, ptr = cells; *ptr; ptr++, i++) { int numeric_locale_len; ! int width, ! height, fs; if (opt_align[i % col_count] == 'r' && opt_numeric_locale) *************** *** 807,816 **** else numeric_locale_len = 0; ! pg_wcssize((unsigned char *) *ptr, strlen(*ptr), encoding, &tmp, &height, &fs); ! tmp += numeric_locale_len; ! if (tmp > dwidth) ! dwidth = tmp; if (height > dheight) dheight = height; if (fs > dformatsize) --- 978,987 ---- else numeric_locale_len = 0; ! pg_wcssize((unsigned char *) *ptr, strlen(*ptr), encoding, &width, &height, &fs); ! width += numeric_locale_len; ! if (width > dwidth) ! dwidth = width; if (height > dheight) dheight = height; if (fs > dformatsize) *************** *** 1879,1884 **** --- 2050,2056 ---- opt, output); break; case PRINT_ALIGNED: + case PRINT_WRAP: if (opt->expanded) print_aligned_vertical(title, headers, cells, footers, align, opt, output); *************** *** 2066,2068 **** --- 2238,2275 ---- else thousands_sep = "."; } + + /* + * Returns the byte length to the end of the specified character + * and number of display characters processed (useful if the string + * is shorter then dpylen). + */ + static int + strlen_max_width(unsigned char *str, int *target_width, int encoding) + { + unsigned char *start = str; + int curr_width = 0; + + while (*str && curr_width < *target_width) + { + int char_width = PQdsplen((char *) str, encoding); + + /* + * If the display width of the new character causes + * the string to exceed its target width, skip it + * and return. However, if this is the first character + * of the string (*width == 0), we have to accept it. + */ + if (*target_width - curr_width < char_width && curr_width != 0) + break; + + str += PQmblen((char *)str, encoding); + + curr_width += char_width; + } + + *target_width = curr_width; + + /* last byte */ + return str - start; + } Index: src/bin/psql/print.h =================================================================== RCS file: /cvsroot/pgsql/src/bin/psql/print.h,v retrieving revision 1.35 diff -c -c -r1.35 print.h *** src/bin/psql/print.h 1 Jan 2008 19:45:56 -0000 1.35 --- src/bin/psql/print.h 21 Apr 2008 15:28:02 -0000 *************** *** 21,26 **** --- 21,27 ---- PRINT_NOTHING = 0, /* to make sure someone initializes this */ PRINT_UNALIGNED, PRINT_ALIGNED, + PRINT_WRAP, PRINT_HTML, PRINT_LATEX, PRINT_TROFF_MS *************** *** 47,52 **** --- 48,54 ---- * decimal marker */ char *tableAttr; /* attributes for HTML <table ...> */ int encoding; /* character encoding */ + int columns; /* target width for wrap format */ } printTableOpt;
Bruce Momjian wrote: > ! /* print a divider, middle columns only */ > ! if ((j + 1) % col_count) > { > ! if (opt_border == 0) > ! fputc(' ', fout); > ! /* first line for values? */ > ! else if (line_count == 0 && col_line_count == 0) > ! fputs(" | ", fout); > ! /* next value is beyond height? */ > ! else if (line_count >= heights[j + 1]) > ! fputs(" ", fout); > ! /* start of another newline string? */ > ! else if (col_line_count == 0) > ! fputs(" : ", fout); > ! else > ! { > ! /* Does the next column wrap to this line? */ > ! struct lineptr *this_line = &col_lineptrs[j+1][line_count]; > ! bool string_done = *(this_line->ptr + bytes_output[j+1]) == 0; > ! > ! fputs(string_done ? " " : " ; ", fout); > ! } > } I think it's a bad idea to use the same " : " separator in the two last cases. They are different and they should be displayed differently. -- Alvaro Herrera http://www.CommandPrompt.com/ PostgreSQL Replication, Consulting, Custom Development, 24x7 support
Alvaro Herrera wrote: > Bruce Momjian wrote: > > > ! /* print a divider, middle columns only */ > > ! if ((j + 1) % col_count) > > { > > ! if (opt_border == 0) > > ! fputc(' ', fout); > > ! /* first line for values? */ > > ! else if (line_count == 0 && col_line_count == 0) > > ! fputs(" | ", fout); > > ! /* next value is beyond height? */ > > ! else if (line_count >= heights[j + 1]) > > ! fputs(" ", fout); > > ! /* start of another newline string? */ > > ! else if (col_line_count == 0) > > ! fputs(" : ", fout); > > ! else > > ! { > > ! /* Does the next column wrap to this line? */ > > ! struct lineptr *this_line = &col_lineptrs[j+1][line_count]; > > ! bool string_done = *(this_line->ptr + bytes_output[j+1]) == 0; > > ! > > ! fputs(string_done ? " " : " ; ", fout); > > ! } > > } > > I think it's a bad idea to use the same " : " separator in the two last > cases. They are different and they should be displayed differently. I confirmed with Alvaro that he didn't notice the first uses a colon and the second a semicolon, so he is OK. -- Bruce Momjian <bruce@momjian.us> http://momjian.us EnterpriseDB http://enterprisedb.com + If your life is a hard drive, Christ can be your backup. +
Alvaro Herrera wrote: > Bruce Momjian wrote: > > > ! fputs(string_done ? " " : " ; ", fout); > > ! } > > } > > I think it's a bad idea to use the same " : " separator in the two last > cases. They are different and they should be displayed differently. Bruce noted by IM that I misread the ? : expression :-) Sorry for the noise. -- Alvaro Herrera http://www.CommandPrompt.com/ The PostgreSQL Company - Command Prompt, Inc.
Bruce Momjian wrote: > > I think it's a bad idea to use the same " : " separator in the two last > > cases. They are different and they should be displayed differently. > > I confirmed with Alvaro that he didn't notice the first uses a colon and > the second a semicolon, so he is OK. FYI, I added a comment to the patch so others wouldn't confuse this. The ? : C syntax makes such confusion easy. -- Bruce Momjian <bruce@momjian.us> http://momjian.us EnterpriseDB http://enterprisedb.com + If your life is a hard drive, Christ can be your backup. +