[open-ils-commits] r16862 - trunk/Open-ILS/src/c-apps (scottmk)
svn at svn.open-ils.org
svn at svn.open-ils.org
Tue Jul 6 17:08:14 EDT 2010
Author: scottmk
Date: 2010-07-06 17:08:08 -0400 (Tue, 06 Jul 2010)
New Revision: 16862
Added:
trunk/Open-ILS/src/c-apps/test_qstore.c
Modified:
trunk/Open-ILS/src/c-apps/Makefile.am
Log:
Add new program test_qstore, a command-line utility for testing
and exercising most of the code for the qstore server.
A Open-ILS/src/c-apps/test_qstore.c
M Open-ILS/src/c-apps/Makefile.am
Modified: trunk/Open-ILS/src/c-apps/Makefile.am
===================================================================
--- trunk/Open-ILS/src/c-apps/Makefile.am 2010-07-06 20:52:39 UTC (rev 16861)
+++ trunk/Open-ILS/src/c-apps/Makefile.am 2010-07-06 21:08:08 UTC (rev 16862)
@@ -7,7 +7,7 @@
AM_CFLAGS = $(DEF_CFLAGS) -DOSRF_LOG_PARAMS -I at top_srcdir@/include/
AM_LDFLAGS = $(DEF_LDFLAGS) -L$(DBI_LIBS) -lopensrf
-bin_PROGRAMS = oils_dataloader dump_idl test_json_query
+bin_PROGRAMS = oils_dataloader dump_idl test_json_query test_qstore
oils_dataloader_SOURCES = oils_dataloader.c
oils_dataloader_LDFLAGS = $(AM_LDFLAGS) -loils_idl
oils_dataloader_DEPENDENCIES = liboils_idl.la liboils_utils.la
@@ -21,6 +21,11 @@
test_json_query_LDFLAGS = $(AM_LDFLAGS) -loils_idl -loils_utils
test_json_query_DEPENDENCIES = liboils_idl.la liboils_utils.la
+test_qstore_SOURCES = test_qstore.c buildSQL.c oils_buildq.c oils_execsql.c oils_sql.c oils_storedq.c
+test_qstore_CFLAGS = $(AM_CFLAGS)
+test_qstore_LDFLAGS = $(AM_LDFLAGS) -loils_idl -loils_utils
+test_qstore_DEPENDENCIES = liboils_idl.la liboils_utils.la
+
lib_LTLIBRARIES = liboils_idl.la liboils_utils.la oils_cstore.la oils_qstore.la oils_rstore.la oils_pcrud.la oils_auth.la
liboils_idl_la_SOURCES = oils_idl-core.c
Added: trunk/Open-ILS/src/c-apps/test_qstore.c
===================================================================
--- trunk/Open-ILS/src/c-apps/test_qstore.c (rev 0)
+++ trunk/Open-ILS/src/c-apps/test_qstore.c 2010-07-06 21:08:08 UTC (rev 16862)
@@ -0,0 +1,651 @@
+/**
+ @file test_qstore.c
+ @brief Test driver for routines to build queries from tables in the query schema.
+
+ This command-line utility exercises most of the code used in the qstore server, but
+ without the complications of sending and receiving OSRF messages.
+
+ Synopsis:
+
+ test_qstore [options] query_id
+
+ Query_id is the id of a row in the query.stored_query table, defining a stored query.
+
+ The program reads the specified row in query.stored_query, along with associated rows
+ in other tables, and displays the corresponding query as an SQL command. Optionally it
+ may execute the query, display the column names of the query result, and/or display the
+ bind variables.
+
+ In order to connect to the database, test_qstore uses various connection parameters
+ that may be specified on the command line. Any connection parameter not specified
+ reverts to a plausible default.
+
+ The database password may be read from a specified file or entered from the keyboard.
+
+ Options:
+
+ -b Boolean; Display the name of any bind variables, and their default values.
+
+ -D Specifies the name of the database driver; defaults to "pgsql".
+
+ -c Boolean; display column names of the query results, as assigned by PostgreSQL.
+
+ -d Specifies the database name; defaults to "evergreen".
+
+ -h Specifies the hostname of the database; defaults to "localhost".
+
+ -i Specifies the name of the IDL file; defaults to "/openils/conf/fm_IDL.xml".
+
+ -p Specifies the port number of the database; defaults to 5432.
+
+ -u Specifies the database user name; defaults to "evergreen".
+
+ -v Boolean; Run in verbose mode, spewing various detailed messages. This option is not
+ likely to be useful unless you are troubleshooting the code that loads the stored
+ query.
+
+ -w Specifies the name of a file containing the database password (no default).
+
+ -x Boolean: Execute the query and display the results.
+
+ Copyright (C) 2010 Equinox Software Inc.
+ Scott McKellar <scott at esilibrary.com>
+*/
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+#include <ctype.h>
+#include <unistd.h>
+#include <termios.h>
+#include <dbi/dbi.h>
+#include "opensrf/utils.h"
+#include "opensrf/string_array.h"
+#include "opensrf/osrf_json.h"
+#include "openils/oils_idl.h"
+#include "openils/oils_buildq.h"
+
+typedef struct {
+ int new_argc;
+ char ** new_argv;
+
+ int bind;
+ char* driver;
+ int driver_found;
+ char* database;
+ int database_found;
+ char* host;
+ int host_found;
+ char* idl;
+ int idl_found;
+ unsigned long port;
+ int port_found;
+ char* user;
+ int user_found;
+ char* password_file;
+ int password_file_found;
+ int verbose;
+ int columns;
+ int execute;
+} Opts;
+
+static void show_bind_variables( osrfHash* vars );
+static void show_msgs( const osrfStringArray* sa );
+static dbi_conn connect_db( Opts* opts );
+static int load_pw( growing_buffer* buf, FILE* in );
+static int prompt_password( growing_buffer* buf );
+static void initialize_opts( Opts * pOpts );
+static int get_Opts( int argc, char * argv[], Opts * pOpts );
+
+int main( int argc, char* argv[] ) {
+
+ // Parse the command line
+ printf( "\n" );
+ Opts opts;
+ if( get_Opts( argc, argv, &opts )) {
+ fprintf( stderr, "Unable to parse command line\n" );
+ return EXIT_FAILURE;
+ }
+
+ // Connect to the database
+ dbi_conn dbhandle = connect_db( &opts );
+ if( NULL == dbhandle )
+ return EXIT_FAILURE;
+
+ if( opts.verbose )
+ oilsStoredQSetVerbose();
+
+ osrfLogSetLevel( OSRF_LOG_WARNING );
+
+ // Load the IDL
+ if ( !oilsIDLInit( opts.idl )) {
+ fprintf( stderr, "Unable to load IDL at %s\n", opts.idl );
+ return EXIT_FAILURE;
+ }
+
+ // Load the stored query
+ BuildSQLState* state = buildSQLStateNew( dbhandle );
+ state->defaults_usable = 1;
+ state->values_required = 0;
+ StoredQ* sq = getStoredQuery( state, atoi( opts.new_argv[ 1 ] ));
+
+ if( !sq ) {
+ show_msgs( state->error_msgs );
+ printf( "Unable to build query\n" );
+ } else {
+ // If so requested, show the bind variables
+ if( opts.bind )
+ show_bind_variables( state->bindvar_list );
+
+ // Build the SQL query
+ if( buildSQL( state, sq )) {
+ show_msgs( state->error_msgs );
+ fprintf( stderr, "Unable to build SQL statement\n" );
+ }
+ else {
+ printf( "%s\n", OSRF_BUFFER_C_STR( state->sql ));
+
+ // If so requested, get the column names and display them
+ if( opts.columns ) {
+ jsonObject* cols = oilsGetColNames( state, sq );
+ if( cols ) {
+ printf( "Column names:\n" );
+ char* cols_str = jsonObjectToJSON( cols );
+ char* cols_out = jsonFormatString( cols_str );
+ printf( "%s\n\n", cols_out );
+ free( cols_out );
+ free( cols_str );
+ jsonObjectFree( cols );
+ } else
+ fprintf( stderr, "Unable to get column names\n\n" );
+ }
+
+ // If so requested, execute the query and display the results
+ if( opts.execute ) {
+ jsonObject* row = oilsFirstRow( state );
+ if( state->error ) {
+ show_msgs( state->error_msgs );
+ fprintf( stderr, "Unable to execute query\n" );
+ } else {
+ printf( "[" );
+ int first = 1; // boolean
+ while( row ) {
+
+ if( first ) {
+ printf( "\n\t" );
+ first = 0;
+ } else
+ printf( ",\n\t" );
+
+ char* json = jsonObjectToJSON( row );
+ printf( "%s", json );
+ free( json );
+ row = oilsNextRow( state );
+ }
+ if( state->error ) {
+ show_msgs( state->error_msgs );
+ fprintf( stderr, "Unable to fetch row\n" );
+ }
+ printf( "\n]\n" );
+ }
+ }
+ }
+ }
+
+ storedQFree( sq );
+ buildSQLStateFree( state );
+
+ buildSQLCleanup();
+ if ( dbhandle )
+ dbi_conn_close( dbhandle );
+
+ return EXIT_SUCCESS;
+}
+
+/**
+ @brief Display the bind variables.
+ @param vars Pointer to a hash keyed on bind variable name.
+
+ The data for each hash entry is a BindVar, a C struct whose members define the
+ attributes of the bind variable.
+*/
+static void show_bind_variables( osrfHash* vars ) {
+ printf( "Bind variables:\n\n" );
+ BindVar* bind = NULL;
+ osrfHashIterator* iter = osrfNewHashIterator( vars );
+
+ // Traverse the hash of bind variables
+ while(( bind = osrfHashIteratorNext( iter ))) {
+ const char* type = NULL;
+ switch( bind->type ) {
+ case BIND_STR :
+ type = "string";
+ break;
+ case BIND_NUM :
+ type = "number";
+ break;
+ case BIND_STR_LIST :
+ type = "string list";
+ break;
+ case BIND_NUM_LIST :
+ type = "number list";
+ break;
+ default :
+ type = "(unrecognized)";
+ break;
+ }
+
+ // The default and actual values are in the form of jsonObjects.
+ // Transform them back into raw JSON.
+ char* default_value = NULL;
+ if( bind->default_value )
+ default_value = jsonObjectToJSONRaw( bind->default_value );
+
+ char* actual_value = NULL;
+ if( bind->actual_value )
+ actual_value = jsonObjectToJSONRaw( bind->actual_value );
+
+ // Display the attributes of the current bind variable.
+ printf( "Name: %s\n", bind->name );
+ printf( "Label: %s\n", bind->label );
+ printf( "Type: %s\n", type );
+ printf( "Desc: %s\n", bind->description ? bind->description : "(none)" );
+ printf( "Default: %s\n", default_value ? default_value : "(none)" );
+ printf( "Actual: %s\n", actual_value ? actual_value : "(none)" );
+ printf( "\n" );
+
+ if( default_value )
+ free( default_value );
+
+ if( actual_value )
+ free( actual_value );
+ } // end while
+
+ osrfHashIteratorFree( iter );
+}
+
+/**
+ @brief Write a series of strings to standard output.
+ @param sa Array of strings.
+
+ Display messages emitted by the query-building machinery.
+*/
+static void show_msgs( const osrfStringArray* sa ) {
+ if( sa ) {
+ int i;
+ for( i = 0; i < sa->size; ++i ) {
+ const char* s = osrfStringArrayGetString( sa, i );
+ if( s )
+ printf( "%s\n", s );
+ }
+ }
+}
+
+/**
+ @brief Connect to the database.
+ @return If successful, a database handle; otherwise NULL;
+*/
+static dbi_conn connect_db( Opts* opts ) {
+ // Get a database handle
+ dbi_initialize( NULL );
+ dbi_conn dbhandle = dbi_conn_new( opts->driver );
+ if( !dbhandle ) {
+ fprintf( stderr, "Error loading database driver [%s]", opts->driver );
+ return NULL;
+ }
+
+ char* pw = NULL;
+ growing_buffer* buf = buffer_init( 32 );
+
+ // Get the database password, either from a designated file
+ // or from the terminal.
+ if( opts->password_file_found ) {
+ FILE* pwfile = fopen( opts->password_file, "r" );
+ if( !pwfile ) {
+ fprintf( stderr, "Unable to open password file %s\n", opts->password_file );
+ buffer_free( buf );
+ return NULL;
+ } else {
+ if( load_pw( buf, pwfile )) {
+ fprintf( stderr, "Unable to load password file %s\n", opts->password_file );
+ buffer_free( buf );
+ return NULL;
+ } else
+ pw = buffer_release( buf );
+ }
+ } else {
+ if( prompt_password( buf )) {
+ fprintf( stderr, "Unable to get password\n" );
+ buffer_free( buf );
+ return NULL;
+ } else
+ pw = buffer_release( buf );
+ }
+
+ // Set database connection options
+ dbi_conn_set_option( dbhandle, "host", opts->host );
+ dbi_conn_set_option_numeric( dbhandle, "port", opts->port );
+ dbi_conn_set_option( dbhandle, "username", opts->user );
+ dbi_conn_set_option( dbhandle, "password", pw );
+ dbi_conn_set_option( dbhandle, "dbname", opts->database );
+
+ // Connect to the database
+ const char* err;
+ if( dbi_conn_connect( dbhandle) < 0 ) {
+ sleep( 1 );
+ if ( dbi_conn_connect( dbhandle ) < 0 ) {
+ dbi_conn_error( dbhandle, &err );
+ fprintf( stderr, "Error connecting to database: %s", err );
+ dbi_conn_close( dbhandle );
+ free( pw );
+ return NULL;
+ }
+ }
+
+ free( pw );
+ return dbhandle;
+}
+
+/**
+ @brief Load one line from an input stream into a growing_buffer.
+ @param buf Pointer to the receiving buffer.
+ @param in Pointer to the input stream.
+ @return 0 in all cases. If there's ever a way to fail, return 1 for failure.
+
+ Intended for use in loading a password.
+*/
+static int load_pw( growing_buffer* buf, FILE* in ) {
+ buffer_reset( buf );
+ while( 1 ) {
+ int c = getc( in );
+ if( '\n' == c || EOF == c )
+ break;
+ else if( '\b' == c )
+ buffer_chomp( buf );
+ else
+ OSRF_BUFFER_ADD_CHAR( buf, c );
+ }
+ return 0;
+}
+
+/**
+ @brief Read a password from the terminal, with echo turned off.
+ @param buf Pointer to the receiving buffer.
+ @return 0 if successful, or 1 if not.
+
+ Read from /dev/tty if possible, or from stdin if not.
+*/
+static int prompt_password( growing_buffer* buf ) {
+ struct termios oldterm;
+
+ printf( "Password: " );
+ fflush( stdout );
+
+ FILE* term = fopen( "//dev//tty", "rw" );
+ if( NULL == term )
+ term = stdin;
+
+ // Capture the current state of the terminal
+ if( tcgetattr( fileno( term ), &oldterm ))
+ return 1;
+
+ // Turn off echo
+ struct termios newterm = oldterm;
+ newterm.c_lflag &= ~ECHO;
+ if( tcsetattr( fileno( term ), TCSAFLUSH, &newterm ))
+ return 1;
+
+ // Read the password
+ int rc = load_pw( buf, term );
+
+ // Turn echo back on
+ (void) tcsetattr( fileno( term ), TCSAFLUSH, &oldterm ); // restore echo
+
+ if( term != stdin )
+ fclose( term );
+
+ return rc;
+}
+
+/**
+ @brief Initialize an Opts structure.
+ @param pOpts Pointer to the Opts to be initialized.
+*/
+static void initialize_opts( Opts * pOpts ) {
+ pOpts->new_argc = 0;
+ pOpts->new_argv = NULL;
+
+ pOpts->bind = 0;
+ pOpts->driver_found = 0;
+ pOpts->driver = NULL;
+ pOpts->database_found = 0;
+ pOpts->database = NULL;
+ pOpts->host_found = 0;
+ pOpts->host = NULL;
+ pOpts->idl_found = 0;
+ pOpts->idl = NULL;
+ pOpts->port_found = 0;
+ pOpts->port = 0;
+ pOpts->user_found = 0;
+ pOpts->user = NULL;
+ pOpts->password_file_found = 0;
+ pOpts->password_file = NULL;
+ pOpts->verbose = 0;
+ pOpts->columns = 0;
+ pOpts->execute = 0;
+}
+
+/**
+ @brief Parse the command line.
+ @param argc argc from the command line.
+ @param argv argv from the command line.
+ @param pOpts Pointer to the Opts to be populated.
+ @return Zero if successful, or 1 if not.
+*/
+static int get_Opts( int argc, char * argv[], Opts * pOpts ) {
+ int rc = 0; /* return code */
+ unsigned long port_value = 0;
+ char * tail = NULL;
+ int opt;
+
+ /* Define valid option characters */
+
+ const char optstring[] = ":bD:cd:h:i:p:u:vw:x";
+
+ /* Initialize members of struct */
+
+ initialize_opts( pOpts );
+
+ /* Suppress error messages from getopt() */
+
+ opterr = 0;
+
+ /* Examine command line options */
+
+ while( ( opt = getopt( argc, argv, optstring )) != -1 )
+ {
+ switch( opt )
+ {
+ case 'b' : /* Display bind variables */
+ pOpts->bind = 1;
+ break;
+ case 'c' : /* Display column names */
+ pOpts->columns = 1;
+ break;
+ case 'D' : /* Get database driver */
+ if( pOpts->driver_found )
+ {
+ fprintf( stderr, "Only one occurrence of -D option allowed\n" );
+ rc = 1;
+ break;
+ }
+ pOpts->driver_found = 1;
+
+ pOpts->driver = optarg;
+ break;
+ case 'd' : /* Get database name */
+ if( pOpts->database_found )
+ {
+ fprintf( stderr, "Only one occurrence of -d option allowed\n" );
+ rc = 1;
+ break;
+ }
+ pOpts->database_found = 1;
+
+ pOpts->database = optarg;
+ break;
+ case 'h' : /* Get hostname of database */
+ if( pOpts->host_found )
+ {
+ fprintf( stderr, "Only one occurrence of -h option allowed\n" );
+ rc = 1;
+ break;
+ }
+ pOpts->host_found = 1;
+
+ pOpts->host = optarg;
+ break;
+ case 'i' : /* Get name of IDL file */
+ if( pOpts->idl_found )
+ {
+ fprintf( stderr, "Only one occurrence of -i option allowed\n" );
+ rc = 1;
+ break;
+ }
+ pOpts->idl_found = 1;
+
+ pOpts->idl = optarg;
+ break;
+ case 'p' : /* Get port number of database */
+ if( pOpts->port_found )
+ {
+ fprintf( stderr, "Only one occurrence of -p option allowed\n" );
+ rc = 1;
+ break;
+ }
+ pOpts->port_found = 1;
+
+ /* Skip white space; check for negative */
+
+ while( isspace( (unsigned char) *optarg ))
+ ++optarg;
+
+ if( '-' == *optarg )
+ {
+ fprintf( stderr, "Negative argument not allowed for "
+ "-p option: \"%s\"\n", optarg );
+ rc = 1;
+ break;
+ }
+
+ /* Convert to numeric value */
+
+ errno = 0;
+ port_value = strtoul( optarg, &tail, 10 );
+ if( *tail != '\0' )
+ {
+ fprintf( stderr, "Invalid or non-numeric argument "
+ "to -p option: \"%s\"\n", optarg );
+ rc = 1;
+ break;
+ }
+ else if( errno != 0 )
+ {
+ fprintf( stderr, "Too large argument "
+ "to -p option: \"%s\"\n", optarg );
+ rc = 1;
+ break;
+ }
+
+ pOpts->port = port_value;
+ break;
+ case 'u' : /* Get username of database account */
+ if( pOpts->user_found )
+ {
+ fprintf( stderr, "Only one occurrence of -u option allowed\n" );
+ rc = 1;
+ break;
+ }
+ pOpts->user_found = 1;
+
+ pOpts->user = optarg;
+ break;
+ case 'v' : /* Set verbose mode */
+ pOpts->verbose = 1;
+ break;
+ case 'w' : /* Get name of password_file */
+ if( pOpts->password_file_found )
+ {
+ fprintf( stderr, "Only one occurrence of -w option allowed\n" );
+ rc = 1;
+ break;
+ }
+ pOpts->password_file_found = 1;
+
+ pOpts->password_file = optarg;
+ break;
+ case 'x' : /* Set execute */
+ pOpts->execute = 1;
+ break;
+ case ':' : /* Missing argument */
+ fprintf( stderr, "Required argument missing on -%c option\n",
+ (char) optopt );
+ rc = 1;
+ break;
+ case '?' : /* Invalid option */
+ fprintf( stderr, "Invalid option '-%c' on command line\n",
+ (char) optopt );
+ rc = 1;
+ break;
+ default : /* Programmer error */
+ fprintf( stderr, "Internal error: unexpected value '-%c'"
+ "for optopt", (char) optopt );
+ rc = 1;
+ break;
+ } /* end switch */
+ } /* end while */
+
+ /* See if required options were supplied; apply defaults */
+
+ if( ! pOpts->driver_found )
+ pOpts->driver = "pgsql";
+
+ if( ! pOpts->database_found )
+ pOpts->database = "evergreen";
+
+ if( ! pOpts->host_found )
+ pOpts->host = "localhost";
+
+ if( ! pOpts->idl_found )
+ pOpts->idl = "/openils/conf/fm_IDL.xml";
+
+ if( ! pOpts->port_found )
+ pOpts->port = 5432;
+
+ if( ! pOpts->user_found )
+ pOpts->user = "evergreen";
+
+ if( optind > argc )
+ {
+ /* This should never happen! */
+
+ fprintf( stderr, "Program error: found more arguments than expected\n" );
+ rc = 1;
+ }
+ else
+ {
+ /* Calculate new_argcv and new_argc to reflect */
+ /* the number of arguments consumed */
+
+ pOpts->new_argc = argc - optind + 1;
+ pOpts->new_argv = argv + optind - 1;
+
+ if( pOpts->new_argc < 2UL )
+ {
+ fprintf( stderr, "Not enough arguments beyond options; must be at least 1\n" );
+ rc = 1;
+ }
+ }
+
+ return rc;
+}
More information about the open-ils-commits
mailing list