2 Copyright (C) 2009 Georgia Public Library Service
3 Scott McKellar <scott@esilibrary.com>
5 This program is free software; you can redistribute it and/or
6 modify it under the terms of the GNU General Public License
7 as published by the Free Software Foundation; either version 2
8 of the License, or (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 Description : Translates a JSON query into SQL and writes the
16 results to standard output. Synopsis:
18 test_json_query [-i IDL_file] [-f file_name] [-v] query
20 -i supplies the name of the IDL file. If no IDL file is specified,
21 json_test_query uses the value of the environmental variable
22 OILS_IDL_FILENAME, if it is defined, or defaults to
23 "/openils/conf/fm_IDL.xml".
25 -f supplies the name of a text file containing the JSON query to
26 be translated. A file name constisting of a single hyphen
27 denotes standard input. If this option is present, all
28 non-option arguments are ignored.
30 -v verbose; outputs the name of the IDL file and the text of the
33 If there is no -f option supplied, json_query translates the
34 first non-option parameter. This parameter is subject to the
35 usual mangling by the shell. In most cases it will be sufficient
36 to enclose it in single quotes, but of course any single quotes
37 embedded within the query will need to be escaped.
43 #include <opensrf/utils.h>
44 #include <opensrf/osrf_json.h>
45 #include <opensrf/osrf_application.h>
46 #include <opensrf/osrf_app_session.h>
47 #include <openils/oils_idl.h>
50 #define DISABLE_I18N 2
51 #define SELECT_DISTINCT 1
53 // Prototypes for two functions in oils_cstore.c, which
54 // are not defined in any header
56 /* method context */ osrfMethodContext* ctx,
58 /* SELECT */ jsonObject* selhash,
59 /* FROM */ jsonObject* join_hash,
60 /* WHERE */ jsonObject* search_hash,
61 /* HAVING */ jsonObject* having_hash,
62 /* ORDER BY */ jsonObject* order_hash,
63 /* LIMIT */ jsonObject* limit,
64 /* OFFSET */ jsonObject* offset,
67 void set_cstore_dbi_conn( dbi_conn conn );
69 static int obj_is_true( const jsonObject* obj );
70 static int test_json_query( const char* json_query );
71 static char* load_query( const char* filename );
73 int main( int argc, char* argv[] ) {
77 const char* idl_file_name = NULL;
78 const char* query_file_name = NULL;
79 int verbose = 0; // boolean
83 const char optstring[] = ":f:i:v";
85 while( ( opt = getopt( argc, argv, optstring ) ) != -1 ) {
88 case 'f' : // get file name of query
89 if( query_file_name ) {
90 fprintf( stderr, "Multiple input files not allowed\n" );
94 query_file_name = optarg;
96 case 'i' : // get name of IDL file
98 fprintf( stderr, "Multiple IDL file names not allowed\n" );
102 idl_file_name = optarg;
104 case 'v' : // Verbose
107 case '?' : // Invalid option
108 fprintf( stderr, "Invalid option '-%c' on command line\n",
112 fprintf( stderr, "Internal error: unexpected value '%c'"
113 "for optopt", (char) optopt );
119 // If the command line doesn't specify an IDL file, get it
120 // from an environmental variable, or apply a default
121 if( NULL == idl_file_name ) {
122 idl_file_name = getenv( "OILS_IDL_FILENAME" );
123 if( NULL == idl_file_name )
124 idl_file_name = "/openils/conf/fm_IDL.xml";
128 printf( "IDL file: %s\n", idl_file_name );
130 char* loaded_json = NULL;
131 const char* json_query = NULL;
133 // Get the JSON query into a string
134 if( query_file_name ) { // Got a file? Load it
136 fprintf( stderr, "Extra parameter(s) ignored\n" );
137 loaded_json = load_query( query_file_name );
140 json_query = loaded_json;
141 } else { // No file? Use command line parameter
142 if ( optind == argc ) {
143 fprintf( stderr, "No JSON query specified\n" );
146 json_query = argv[ optind ];
150 printf( "JSON query: %s\n", json_query );
152 osrfLogSetLevel( OSRF_LOG_WARNING ); // Suppress informational messages
153 (void) oilsIDLInit( idl_file_name ); // Load IDL into memory
155 // Load a database driver, connect to it, and install the connection in
156 // the cstore module. We don't actually connect to a database, but we
157 // need the driver to process quoted strings correctly.
158 if( dbi_initialize( NULL ) < 0 ) {
159 printf( "Unable to load database driver\n" );
163 dbi_conn conn = dbi_conn_new( "pgsql" ); // change string if ever necessary
165 printf( "Unable to establish dbi connection\n" );
170 set_cstore_dbi_conn( conn );
172 // The foregoing is an inelegant kludge. The true, proper, and uniquely
173 // correct thing to do is to load the system settings and then call
174 // osrfAppInitialize() and osrfAppChildInit(). Maybe we'll actually
175 // do that some day, but this will do for now.
177 // Translate the JSON into SQL
178 int rc = test_json_query( json_query );
180 dbi_conn_close( conn );
185 return rc ? EXIT_FAILURE : EXIT_SUCCESS;
188 static int test_json_query( const char* json_query ) {
190 jsonObject* hash = jsonParse( json_query );
192 fprintf( stderr, "Invalid JSON\n" );
198 if ( obj_is_true( jsonObjectGetKey( hash, "distinct" ) ) )
199 flags |= SELECT_DISTINCT;
201 if ( obj_is_true( jsonObjectGetKey( hash, "no_i18n" ) ) )
202 flags |= DISABLE_I18N;
204 char* sql_query = SELECT(
206 jsonObjectGetKey( hash, "select" ),
207 jsonObjectGetKey( hash, "from" ),
208 jsonObjectGetKey( hash, "where" ),
209 jsonObjectGetKey( hash, "having" ),
210 jsonObjectGetKey( hash, "order_by" ),
211 jsonObjectGetKey( hash, "limit" ),
212 jsonObjectGetKey( hash, "offset" ),
217 fprintf( stderr, "Invalid query\n" );
221 printf( "%s\n", sql_query );
224 jsonObjectFree( hash );
228 // Interpret a jsonObject as true or false
229 static int obj_is_true( const jsonObject* obj ) {
232 else switch( obj->type )
240 if( strcasecmp( obj->value.s, "true" ) )
244 case JSON_NUMBER : // Support 1/0 for perl's sake
245 if( jsonObjectGetNumber( obj ) == 1.0 )
254 static char* load_query( const char* filename ) {
258 if( ! filename || ! *filename ) {
259 fprintf( stderr, "Name of query file is empty or missing\n" );
263 // Open query file, or use standard input
264 if( ! strcmp( filename, "-" ) )
267 fp = fopen( filename, "r" );
269 fprintf( stderr, "Unable to open query file \"%s\"\n", filename );
274 // Load file into a growing_buffer
276 char buf[ BUFSIZ + 1 ];
277 growing_buffer* gb = buffer_init( sizeof( buf ) );
279 while( ( num_read = fread( buf, 1, sizeof( buf ) - 1, fp ) ) ) {
280 buf[ num_read ] = '\0';
281 buffer_add( gb, buf );
287 return buffer_release( gb );