To wrap up this chapter, you'll convert the interactive query processor from the previous chapter into a libpgeasy client.
Most of the code remains the same, so I'll point out only the differences. The most important change is that you no longer have to pass the PGconn * (connection handle) to every function-libpgeasy is managing the connection handle for you.
1 /* 2 ** File: client4.c 3 */ 4 5 #include <stdlib.h> 6 #include <string.h> 7 #include <libpq-fe.h> 8 #include <libpgeasy.h> 9 #include <readline/readline.h> 10 #include <readline/history.h> 11 12 typedef enum { FALSE, TRUE } bool; 13 14 #define MAX_PRINT_LEN 40 15 16 static char separator[MAX_PRINT_LEN+1]; 17 18 void print_result_set( PGresult * result ) 19 { 20 int col; 21 int row; 22 int * sizes; 23 24 /* 25 ** Compute the size for each column 26 */ 27 sizes = (int *)calloc( PQnfields( result ), sizeof( int )); 28 29 for( col = 0; col < PQnfields( result ); col++ ) 30 { 31 int len = 0; 32 33 for( row = 0; row < PQntuples( result ); row++ ) 34 { 35 if( PQgetisnull( result, row, col )) 36 len = 0; 37 else 38 len = PQgetlength( result, row, col ); 39 40 if( len > sizes[col] ) 41 sizes[col] = len; 42 } 43 44 if(( len = strlen( PQfname( result, col ))) > sizes[col] ) 45 sizes[col] = len; 46 47 if( sizes[col] > MAX_PRINT_LEN ) 48 sizes[col] = MAX_PRINT_LEN; 49 } 50 51 /* 52 ** Print the field names. 53 */ 54 for( col = 0; col < PQnfields( result ); col++ ) 55 { 56 printf( "%-*s ", sizes[col], PQfname( result, col )); 57 } 58 59 printf( " " ); 60 61 /* 62 ** Print the separator line 63 */ 64 memset( separator, '-', MAX_PRINT_LEN ); 65 66 for( col = 0; col < PQnfields( result ); col++ ) 67 { 68 printf( "%*.*s ", sizes[col], sizes[col], separator ); 69 } 70 71 printf( " " ); 72 73 /* 74 ** Now loop through each of the tuples returned by 75 ** our query and print the results. 76 */ 77 for( row = 0; row < PQntuples( result ); row++ ) 78 { 79 for( col = 0; col < PQnfields( result ); col++ ) 80 { 81 if( PQgetisnull( result, row, col )) 82 printf( "%*s", sizes[col], "" ); 83 else 84 printf( "%*s ", sizes[col], PQgetvalue(result, row, col)); 85 } 86 87 printf( " " ); 88 89 } 90 printf( "(%d rows) ", PQntuples( result )); 91 92 free( sizes ); 93 }
You can't use the fetch() or fetchwithnulls() in the print_result_set() function. There is no way to construct a call to these functions because you can't know (at the time the program is compiled) how many columns will be returned by a query.
The process_query() function is very simple. The call to doquery() sends the command to the server and returns a pointer to the result set.
95 void process_query( char * buf ) 96 { 97 PGresult * result; 98 99 result = doquery( buf ); 100 101 if( PQresultStatus( result ) == PGRES_TUPLES_OK ) 102 { 103 print_result_set( result ); 104 } 105 else if( PQresultStatus( result ) == PGRES_COMMAND_OK ) 106 { 107 printf( "%s", PQcmdStatus( result )); 108 109 if( strlen( PQcmdTuples( result ))) 110 printf( " - %s rows ", PQcmdTuples( result )); 111 else 112 printf( " " ); 113 } 114 else 115 { 116 printf( "%s ", PQresultErrorMessage( result )); 117 } 118 }
The main() function is largely unchanged. I don't bother to save the connection handle returned by connectdb() because libpgeasy remembers it for me. The only other change in main() is that you set the error-handling mode calling on_error_continue(). If you don't set the error-handling mode, libpgeasy assumes that it should terminate your application if an error is encountered.
120 int main( int argc, char * argv[] ) 121 { 122 char * buf; 123 124 connectdb( argc > 1 ? argv[1] : "" ); 125 126 on_error_continue(); 127 128 using_history(); 129 read_history( ".pg_history" ); 130 131 while(( buf = readline( "->" )) != NULL ) 132 { 133 if( strncmp( buf, "quit", sizeof( "quit" ) - 1 ) == 0 ) 134 { 135 break; 136 } 137 else 138 { 139 if( strlen( buf ) != 0 ) 140 { 141 add_history( buf ); 142 process_query( buf ); 143 } 144 free( buf ); 145 } 146 } 147 148 write_history( ".pg_history" ); 149 150 disconnectdb(); 151 152 exit( EXIT_SUCCESS ); 153 }