]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/c-apps/test_json_query.c
Use the new JSON parser
[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* SELECT (
56                 /* method context */ osrfMethodContext* ctx,
57                 
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,
65                 /* flags    */ int flags
66 );
67 void set_cstore_dbi_conn( dbi_conn conn );
68
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 );
72
73 int main( int argc, char* argv[] ) {
74
75         // Parse command line
76
77         const char* idl_file_name = NULL;
78         const char* query_file_name = NULL;
79         int verbose = 0;                        // boolean
80
81         int opt;
82         opterr = 0;
83         const char optstring[] = ":f:i:v";
84
85         while( ( opt = getopt( argc, argv, optstring ) ) != -1 ) {
86                 switch( opt )
87                 {
88                         case 'f' :  // get file name of query
89                                 if( query_file_name ) {
90                                         fprintf( stderr, "Multiple input files not allowed\n" );
91                                         return EXIT_FAILURE;
92                                 }
93                                 else
94                                         query_file_name = optarg;
95                                 break;
96                         case 'i' :  // get name of IDL file
97                                 if( idl_file_name ) {
98                                         fprintf( stderr, "Multiple IDL file names not allowed\n" );
99                                         return EXIT_FAILURE;
100                                 }
101                                 else
102                                         idl_file_name = optarg;
103                                 break;
104                         case 'v' :  // Verbose
105                                 verbose = 1;
106                                 break;
107                         case '?' :  // Invalid option
108                                 fprintf( stderr, "Invalid option '-%c' on command line\n",
109                                                  (char) optopt );
110                                 return EXIT_FAILURE;
111                         default :  // Huh?
112                                 fprintf( stderr, "Internal error: unexpected value '%c'"
113                                                 "for optopt", (char) optopt );
114                                 return EXIT_FAILURE;
115
116                 }
117         }
118
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";
125         }
126
127         if( verbose )
128                 printf( "IDL file: %s\n", idl_file_name );
129
130         char* loaded_json = NULL;
131         const char* json_query = NULL;
132
133         // Get the JSON query into a string
134         if( query_file_name ) {   // Got a file?  Load it
135                 if( optind < argc )
136                         fprintf( stderr, "Extra parameter(s) ignored\n" );
137                 loaded_json = load_query( query_file_name );
138                 if( !loaded_json ) 
139                         return EXIT_FAILURE;
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" );
144                         return EXIT_FAILURE;
145                 } else
146                         json_query = argv[ optind ];
147         }
148
149         if( verbose )
150                 printf( "JSON query: %s\n", json_query );
151
152         osrfLogSetLevel( OSRF_LOG_WARNING );    // Suppress informational messages
153         (void) oilsIDLInit( idl_file_name );    // Load IDL into memory
154
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" );
160                 return EXIT_FAILURE;
161         };
162         
163         dbi_conn conn = dbi_conn_new( "pgsql" );  // change string if ever necessary
164         if( !conn ) {
165                 printf( "Unable to establish dbi connection\n" );
166                 dbi_shutdown();
167                 return EXIT_FAILURE;
168         }
169
170         set_cstore_dbi_conn( conn );
171
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.
176
177         // Translate the JSON into SQL
178         int rc = test_json_query( json_query );
179
180         dbi_conn_close( conn );
181         dbi_shutdown();
182         if( loaded_json )
183                 free( loaded_json );
184
185         return rc ? EXIT_FAILURE : EXIT_SUCCESS;
186 }
187
188 static int test_json_query( const char* json_query ) {
189
190         jsonObject* hash = jsonParse( json_query );
191         if( !hash ) {
192                 fprintf( stderr, "Invalid JSON\n" );
193                 return -1;
194         }
195         
196         int flags = 0;
197
198         if ( obj_is_true( jsonObjectGetKey( hash, "distinct" ) ) )
199                 flags |= SELECT_DISTINCT;
200
201         if ( obj_is_true( jsonObjectGetKey( hash, "no_i18n" ) ) )
202                 flags |= DISABLE_I18N;
203
204         char* sql_query = SELECT(
205                 NULL,
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" ),
213                 flags
214         );
215
216         if ( !sql_query ) {
217                 fprintf( stderr, "Invalid query\n" );
218                 return -1;
219         }
220         else
221                 printf( "%s\n", sql_query );
222
223         free( sql_query );
224         jsonObjectFree( hash );
225         return 0;
226 }
227
228 // Interpret a jsonObject as true or false
229 static int obj_is_true( const jsonObject* obj ) {
230         if( !obj )
231                 return 0;
232         else switch( obj->type )
233         {
234                 case JSON_BOOL :
235                         if( obj->value.b )
236                                 return 1;
237                         else
238                                 return 0;
239                 case JSON_STRING :
240                         if( strcasecmp( obj->value.s, "true" ) )
241                                 return 0;
242                         else
243                                 return 1;
244                         case JSON_NUMBER :          // Support 1/0 for perl's sake
245                                 if( jsonObjectGetNumber( obj ) == 1.0 )
246                                         return 1;
247                                 else
248                                         return 0;
249                 default :
250                         return 0;
251         }
252 }
253
254 static char* load_query( const char* filename ) {
255         FILE* fp;
256
257         // Sanity check
258         if( ! filename || ! *filename ) {
259                 fprintf( stderr, "Name of query file is empty or missing\n" );
260                 return NULL;
261         }
262
263         // Open query file, or use standard input
264         if( ! strcmp( filename, "-" ) )
265                 fp = stdin;
266         else {
267                 fp = fopen( filename, "r" );
268                 if( !fp ) {
269                         fprintf( stderr, "Unable to open query file \"%s\"\n", filename );
270                         return NULL;
271                 }
272         }
273
274         // Load file into a growing_buffer
275         size_t num_read;
276         char buf[ BUFSIZ + 1 ];
277         growing_buffer* gb = buffer_init( sizeof( buf ) );
278
279     while( ( num_read = fread( buf, 1, sizeof( buf ) - 1, fp ) ) ) {
280                 buf[ num_read ] = '\0';
281                 buffer_add( gb, buf );
282         }
283
284         if( fp != stdin )
285                 fclose( fp );
286
287         return buffer_release( gb );
288 }