]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/c-apps/test_json_query.c
Add support for UNION, INTERSECT, and EXCEPT.
[Evergreen.git] / Open-ILS / src / c-apps / test_json_query.c
1 /*
2 Copyright (C) 2009  Georgia Public Library Service 
3 Scott McKellar <scott@esilibrary.com>
4
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.
9
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.
14
15         Description : Translates a JSON query into SQL and writes the
16         results to standard output.  Synopsis:
17
18         test_json_query [-i IDL_file] [-f file_name] [-v] query
19         
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".
24
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.
29
30         -v verbose; outputs the name of the IDL file and the text of the
31            JSON query.
32
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.
38  */
39
40 #include <stdlib.h>
41 #include <stdio.h>
42 #include <unistd.h>
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>
48 #include <dbi/dbi.h>
49
50 #define DISABLE_I18N    2
51 #define SELECT_DISTINCT 1
52
53 // Prototypes for two functions in oils_cstore.c, which
54 // are not defined in any header
55 char* buildQuery( osrfMethodContext* ctx, jsonObject* query, int flags );
56 void set_cstore_dbi_conn( dbi_conn conn );
57
58 static int obj_is_true( const jsonObject* obj );
59 static int test_json_query( const char* json_query );
60 static char* load_query( const char* filename );
61
62 int main( int argc, char* argv[] ) {
63
64         // Parse command line
65
66         const char* idl_file_name = NULL;
67         const char* query_file_name = NULL;
68         int verbose = 0;                        // boolean
69
70         int opt;
71         opterr = 0;
72         const char optstring[] = ":f:i:v";
73
74         while( ( opt = getopt( argc, argv, optstring ) ) != -1 ) {
75                 switch( opt )
76                 {
77                         case 'f' :  // get file name of query
78                                 if( query_file_name ) {
79                                         fprintf( stderr, "Multiple input files not allowed\n" );
80                                         return EXIT_FAILURE;
81                                 }
82                                 else
83                                         query_file_name = optarg;
84                                 break;
85                         case 'i' :  // get name of IDL file
86                                 if( idl_file_name ) {
87                                         fprintf( stderr, "Multiple IDL file names not allowed\n" );
88                                         return EXIT_FAILURE;
89                                 }
90                                 else
91                                         idl_file_name = optarg;
92                                 break;
93                         case 'v' :  // Verbose
94                                 verbose = 1;
95                                 break;
96                         case '?' :  // Invalid option
97                                 fprintf( stderr, "Invalid option '-%c' on command line\n",
98                                                  (char) optopt );
99                                 return EXIT_FAILURE;
100                         default :  // Huh?
101                                 fprintf( stderr, "Internal error: unexpected value '%c'"
102                                                 "for optopt", (char) optopt );
103                                 return EXIT_FAILURE;
104
105                 }
106         }
107
108         // If the command line doesn't specify an IDL file, get it
109         // from an environmental variable, or apply a default
110         if( NULL == idl_file_name ) {
111                 idl_file_name = getenv( "OILS_IDL_FILENAME" );
112                 if( NULL == idl_file_name )
113                         idl_file_name = "/openils/conf/fm_IDL.xml";
114         }
115
116         if( verbose )
117                 printf( "IDL file: %s\n", idl_file_name );
118
119         char* loaded_json = NULL;
120         const char* json_query = NULL;
121
122         // Get the JSON query into a string
123         if( query_file_name ) {   // Got a file?  Load it
124                 if( optind < argc )
125                         fprintf( stderr, "Extra parameter(s) ignored\n" );
126                 loaded_json = load_query( query_file_name );
127                 if( !loaded_json ) 
128                         return EXIT_FAILURE;
129                 json_query = loaded_json;
130         } else {                  // No file?  Use command line parameter
131                 if ( optind == argc ) {
132                         fprintf( stderr, "No JSON query specified\n" );
133                         return EXIT_FAILURE;
134                 } else
135                         json_query = argv[ optind ];
136         }
137
138         if( verbose )
139                 printf( "JSON query: %s\n", json_query );
140
141         osrfLogSetLevel( OSRF_LOG_WARNING );    // Suppress informational messages
142         (void) oilsIDLInit( idl_file_name );    // Load IDL into memory
143
144         // Load a database driver, connect to it, and install the connection in
145         // the cstore module.  We don't actually connect to a database, but we
146         // need the driver to process quoted strings correctly.
147         if( dbi_initialize( NULL ) < 0 ) {
148                 printf( "Unable to load database driver\n" );
149                 return EXIT_FAILURE;
150         };
151         
152         dbi_conn conn = dbi_conn_new( "pgsql" );  // change string if ever necessary
153         if( !conn ) {
154                 printf( "Unable to establish dbi connection\n" );
155                 dbi_shutdown();
156                 return EXIT_FAILURE;
157         }
158
159         set_cstore_dbi_conn( conn );
160
161         // The foregoing is an inelegant kludge.  The true, proper, and uniquely
162         // correct thing to do is to load the system settings and then call
163         // osrfAppInitialize() and osrfAppChildInit().  Maybe we'll actually
164         // do that some day, but this will do for now.
165
166         // Translate the JSON into SQL
167         int rc = test_json_query( json_query );
168
169         dbi_conn_close( conn );
170         dbi_shutdown();
171         if( loaded_json )
172                 free( loaded_json );
173
174         return rc ? EXIT_FAILURE : EXIT_SUCCESS;
175 }
176
177 static int test_json_query( const char* json_query ) {
178
179         jsonObject* hash = jsonParse( json_query );
180         if( !hash ) {
181                 fprintf( stderr, "Invalid JSON\n" );
182                 return -1;
183         }
184         
185         int flags = 0;
186
187         if ( obj_is_true( jsonObjectGetKey( hash, "distinct" ) ) )
188                 flags |= SELECT_DISTINCT;
189
190         if ( obj_is_true( jsonObjectGetKey( hash, "no_i18n" ) ) )
191                 flags |= DISABLE_I18N;
192
193         char* sql_query = buildQuery( NULL, hash, flags );
194
195         if ( !sql_query ) {
196                 fprintf( stderr, "Invalid query\n" );
197                 return -1;
198         }
199         else
200                 printf( "%s\n", sql_query );
201
202         free( sql_query );
203         jsonObjectFree( hash );
204         return 0;
205 }
206
207 // Interpret a jsonObject as true or false
208 static int obj_is_true( const jsonObject* obj ) {
209         if( !obj )
210                 return 0;
211         else switch( obj->type )
212         {
213                 case JSON_BOOL :
214                         if( obj->value.b )
215                                 return 1;
216                         else
217                                 return 0;
218                 case JSON_STRING :
219                         if( strcasecmp( obj->value.s, "true" ) )
220                                 return 0;
221                         else
222                                 return 1;
223                         case JSON_NUMBER :          // Support 1/0 for perl's sake
224                                 if( jsonObjectGetNumber( obj ) == 1.0 )
225                                         return 1;
226                                 else
227                                         return 0;
228                 default :
229                         return 0;
230         }
231 }
232
233 static char* load_query( const char* filename ) {
234         FILE* fp;
235
236         // Sanity check
237         if( ! filename || ! *filename ) {
238                 fprintf( stderr, "Name of query file is empty or missing\n" );
239                 return NULL;
240         }
241
242         // Open query file, or use standard input
243         if( ! strcmp( filename, "-" ) )
244                 fp = stdin;
245         else {
246                 fp = fopen( filename, "r" );
247                 if( !fp ) {
248                         fprintf( stderr, "Unable to open query file \"%s\"\n", filename );
249                         return NULL;
250                 }
251         }
252
253         // Load file into a growing_buffer
254         size_t num_read;
255         char buf[ BUFSIZ + 1 ];
256         growing_buffer* gb = buffer_init( sizeof( buf ) );
257
258     while( ( num_read = fread( buf, 1, sizeof( buf ) - 1, fp ) ) ) {
259                 buf[ num_read ] = '\0';
260                 buffer_add( gb, buf );
261         }
262
263         if( fp != stdin )
264                 fclose( fp );
265
266         return buffer_release( gb );
267 }