1 // Copyright (c) 1999-2018 David Muse
2 // See the file COPYING for more information
3 
4 #include <sqlrelay/sqlrclient.h>
5 #include <sqlrelay/sqlrutil.h>
6 #include <rudiments/file.h>
7 #include <rudiments/permissions.h>
8 #include <rudiments/filesystem.h>
9 #include <rudiments/filedescriptor.h>
10 #include <rudiments/process.h>
11 #include <rudiments/environment.h>
12 #include <rudiments/datetime.h>
13 #include <rudiments/signalclasses.h>
14 #include <rudiments/xmldom.h>
15 #include <rudiments/stdio.h>
16 #include <rudiments/character.h>
17 #include <rudiments/memorypool.h>
18 #include <rudiments/prompt.h>
19 #include <config.h>
20 #include <defaults.h>
21 #define NEED_IS_BIT_TYPE_CHAR 1
22 #define NEED_IS_NUMBER_TYPE_CHAR 1
23 #define NEED_IS_FLOAT_TYPE_CHAR 1
24 #define NEED_IS_NONSCALE_FLOAT_TYPE_CHAR 1
25 #include <datatypes.h>
26 #include <defines.h>
27 #include <parsedatetime.h>
28 #include <version.h>
29 // FIXME: use rudiments locale class instead
30 #include <locale.h>
31 #include <math.h>
32 
33 class sqlrshbindvalue {
34 	public:
35 		union {
36 			char	*stringval;
37 			int64_t	integerval;
38 			struct {
39 				double		value;
40 				uint32_t	precision;
41 				uint32_t	scale;
42 			} doubleval;
43 			struct {
44 				int16_t		year;
45 				int16_t		month;
46 				int16_t		day;
47 				int16_t		hour;
48 				int16_t		minute;
49 				int16_t		second;
50 				int32_t		microsecond;
51 				const char	*tz;
52 				bool		isnegative;
53 			} dateval;
54 		};
55 		sqlrclientbindvartype_t	type;
56 		uint32_t		outputstringbindlength;
57 
print()58 		void	print() {}
59 };
60 
61 enum sqlrshformat {
62 	SQLRSH_FORMAT_PLAIN=0,
63 	SQLRSH_FORMAT_CSV
64 };
65 
66 class sqlrshenv {
67 	public:
68 			sqlrshenv();
69 			~sqlrshenv();
70 		void	 clearbinds(
71 			dictionary<char *, sqlrshbindvalue *> *binds);
72 
73 		bool		headers;
74 		bool		divider;
75 		bool		stats;
76 		uint64_t	rsbs;
77 		bool		final;
78 		bool		autocommit;
79 		bool		lazyfetch;
80 		char		delimiter;
81 		dictionary<char *, sqlrshbindvalue *>	inputbinds;
82 		memorypool	inbindpool;
83 		dictionary<char *, sqlrshbindvalue *>	outputbinds;
84 		dictionary<char *, sqlrshbindvalue *>	inputoutputbinds;
85 		char		*cacheto;
86 		sqlrshformat	format;
87 		bool		getasnumber;
88 		bool		noelapsed;
89 		bool		nextresultset;
90 };
91 
sqlrshenv()92 sqlrshenv::sqlrshenv() {
93 	headers=true;
94 	divider=true;
95 	stats=true;
96 	rsbs=100;
97 	final=false;
98 	autocommit=false;
99 	lazyfetch=false;
100 	delimiter=';';
101 	cacheto=NULL;
102 	format=SQLRSH_FORMAT_PLAIN;
103 	getasnumber=false;
104 	noelapsed=false;
105 	nextresultset=false;
106 }
107 
~sqlrshenv()108 sqlrshenv::~sqlrshenv() {
109 	clearbinds(&inputbinds);
110 	clearbinds(&outputbinds);
111 	clearbinds(&inputoutputbinds);
112 	delete[] cacheto;
113 }
114 
clearbinds(dictionary<char *,sqlrshbindvalue * > * binds)115 void sqlrshenv::clearbinds(dictionary<char *, sqlrshbindvalue *> *binds) {
116 
117 	for (linkedlistnode<dictionarynode<char *, sqlrshbindvalue *> *>
118 					*node=binds->getList()->getFirst();
119 		node; node=node->getNext()) {
120 
121 		delete[] node->getValue()->getKey();
122 		sqlrshbindvalue	*bv=node->getValue()->getValue();
123 		if (bv->type==SQLRCLIENTBINDVARTYPE_STRING) {
124 			delete[] bv->stringval;
125 		}
126 		delete bv;
127 	}
128 	binds->clear();
129 	inbindpool.clear();
130 }
131 
132 enum querytype_t {
133 	SHOW_DATABASES_QUERY=0,
134 	SHOW_TABLES_QUERY,
135 	SHOW_COLUMNS_QUERY,
136 	SHOW_PRIMARY_KEYS_QUERY,
137 	DESCRIBE_QUERY
138 };
139 
140 class	sqlrsh {
141 	public:
142 			sqlrsh();
143 			~sqlrsh();
144 		bool	execute(int argc, const char **argv);
145 	private:
146 		void	startupMessage(sqlrshenv *env,
147 					const char *host,
148 					uint16_t port,
149 					const char *user);
150 		void	userRcFile(sqlrconnection *sqlrcon,
151 					sqlrcursor *sqlrcur,
152 					sqlrshenv *env);
153 		bool	runScript(sqlrconnection *sqlrcon,
154 					sqlrcursor *sqlrcur,
155 					sqlrshenv *env,
156 					const char *filename,
157 					bool displayerror);
158 		bool	runCommands(sqlrconnection *sqlrcon,
159 					sqlrcursor *sqlrcur,
160 					sqlrshenv *env,
161 					const char *commands,
162 					bool *exitprogram);
163 		bool	getCommandFromFileOrString(file *fl,
164 					const char *string,
165 					const char **stringpos,
166 					stringbuffer *cmdbuffer,
167 					sqlrshenv *env);
168 		bool	runCommand(sqlrconnection *sqlrcon,
169 					sqlrcursor *sqlrcur,
170 					sqlrshenv *env,
171 					const char *command,
172 					bool *exitprogram);
173 		int	commandType(const char *command);
174 		bool	internalCommand(sqlrconnection *sqlrcon,
175 					sqlrcursor *sqlrcur,
176 					sqlrshenv *env,
177 					const char *command);
178 		bool	externalCommand(sqlrconnection *sqlrcon,
179 					sqlrcursor *sqlrcur,
180 					sqlrshenv *env,
181 					const char *command);
182 		void	executeQuery(sqlrcursor *sqlrcur,
183 					sqlrshenv *env);
184 		char	*getWild(const char *command);
185 		char	*getTable(const char *command, bool in);
186 		char	*getProcedure(const char *command);
187 		char	*getType(const char *command);
188 		void	initStats(sqlrshenv *env);
189 		void	displayError(sqlrshenv *env,
190 					const char *message,
191 					const char *error,
192 					int64_t errornumber);
193 		void	displayHeader(sqlrcursor *sqlrcur,
194 						sqlrshenv *env);
195 		void	displayResultSet(sqlrcursor *sqlrcur,
196 						sqlrshenv *env);
197 		void	displayStats(sqlrcursor *sqlrcur,
198 						sqlrshenv *env);
199 		bool	ping(sqlrconnection *sqlrcon,
200 						sqlrshenv *env);
201 		bool	identify(sqlrconnection *sqlrcon,
202 						sqlrshenv *env);
203 		bool	dbversion(sqlrconnection *sqlrcon,
204 						sqlrshenv *env);
205 		bool	dbhostname(sqlrconnection *sqlrcon,
206 						sqlrshenv *env);
207 		bool	dbipaddress(sqlrconnection *sqlrcon,
208 						sqlrshenv *env);
209 		void	clientversion(sqlrconnection *sqlrcon,
210 						sqlrshenv *env);
211 		bool	serverversion(sqlrconnection *sqlrcon,
212 						sqlrshenv *env);
213 		bool	lastinsertid(sqlrconnection *sqlrcon,
214 						sqlrshenv *env);
215 		bool	inputbind(sqlrcursor *sqlrcur,
216 						sqlrshenv *env,
217 						const char *command);
218 		bool	inputbindblob(sqlrcursor *sqlrcur,
219 						sqlrshenv *env,
220 						const char *command);
221 		bool	outputbind(sqlrcursor *sqlrcur,
222 						sqlrshenv *env,
223 						const char *command);
224 		bool	inputoutputbind(sqlrcursor *sqlrcur,
225 						sqlrshenv *env,
226 						const char *command);
227 		void	printbinds(const char *type,
228 				dictionary<char *, sqlrshbindvalue *> *binds);
229 		void	clearbinds(
230 				dictionary<char *, sqlrshbindvalue *> *binds);
231 		void	setclientinfo(sqlrconnection *sqlrcon,
232 						const char *command);
233 		void	getclientinfo(sqlrconnection *sqlrcon);
234 		void	responseTimeout(sqlrconnection *sqlrcon,
235 						const char *command);
236 		bool	cache(sqlrshenv *env, sqlrcursor *sqlrcur,
237 							const char *command);
238 		bool	openCache(sqlrshenv *env, sqlrcursor *sqlrcur,
239 							const char *command);
240 		void	displayHelp(sqlrshenv *env);
241 		void	interactWithUser(sqlrconnection *sqlrcon,
242 						sqlrcursor *sqlrcur,
243 						sqlrshenv *env);
244 
245 		sqlrcmdline	*cmdline;
246 		sqlrpaths	*sqlrpth;
247 
248 		datetime	start;
249 
250 		prompt		pr;
251 };
252 
sqlrsh()253 sqlrsh::sqlrsh() {
254 	cmdline=NULL;
255 	sqlrpth=NULL;
256 }
257 
~sqlrsh()258 sqlrsh::~sqlrsh() {
259 	delete cmdline;
260 	delete sqlrpth;
261 }
262 
userRcFile(sqlrconnection * sqlrcon,sqlrcursor * sqlrcur,sqlrshenv * env)263 void sqlrsh::userRcFile(sqlrconnection *sqlrcon, sqlrcursor *sqlrcur,
264 						sqlrshenv *env) {
265 
266 	// get user's home directory
267 	const char	*home=environment::getValue("HOME");
268 	if (!home) {
269 		home="~";
270 	}
271 
272 	// build rcfilename
273 	size_t	userrcfilelen=charstring::length(home)+10+1;
274 	char	*userrcfile=new char[userrcfilelen];
275 	charstring::copy(userrcfile,home);
276 	charstring::append(userrcfile,"/.sqlrshrc");
277 
278 	// process the file
279 	runScript(sqlrcon,sqlrcur,env,userrcfile,false);
280 	delete[] userrcfile;
281 }
282 
runScript(sqlrconnection * sqlrcon,sqlrcursor * sqlrcur,sqlrshenv * env,const char * filename,bool displayerror)283 bool sqlrsh::runScript(sqlrconnection *sqlrcon, sqlrcursor *sqlrcur,
284 			sqlrshenv *env, const char *filename,
285 			bool displayerror) {
286 
287 	bool	retval=true;
288 
289 	char	*trimmedfilename=charstring::duplicate(filename);
290 	charstring::bothTrim(trimmedfilename);
291 
292 	// open the file
293 	file	scriptfile;
294 	if (scriptfile.open(trimmedfilename,O_RDONLY)) {
295 
296 		// optimize
297 		filesystem	fs;
298 		if (fs.open(trimmedfilename)) {
299 			scriptfile.setReadBufferSize(
300 				fs.getOptimumTransferBlockSize());
301 		}
302 
303 		for (;;) {
304 
305 			// get a command
306 			stringbuffer	command;
307 			if (!getCommandFromFileOrString(
308 					&scriptfile,NULL,NULL,&command,env)) {
309 				retval=false;
310 				break;
311 			}
312 
313 			// run the command
314 			if (!runCommand(sqlrcon,sqlrcur,env,
315 						command.getString(),
316 						NULL)) {
317 				retval=false;
318 				break;
319 			}
320 		}
321 
322 		// close the file
323 		scriptfile.close();
324 
325 	} else {
326 
327 		// error message
328 		if (displayerror) {
329 			stderror.printf("Couldn't open file: %s\n\n",
330 							trimmedfilename);
331 		}
332 		retval=false;
333 	}
334 
335 	delete[] trimmedfilename;
336 
337 	return retval;
338 }
339 
runCommands(sqlrconnection * sqlrcon,sqlrcursor * sqlrcur,sqlrshenv * env,const char * commands,bool * exitprogram)340 bool sqlrsh::runCommands(sqlrconnection *sqlrcon,
341 				sqlrcursor *sqlrcur,
342 				sqlrshenv *env,
343 				const char *commands,
344 				bool *exitprogram) {
345 
346 	const char	*nextcommand=commands;
347 	for (;;) {
348 		stringbuffer	command;
349 		if (!getCommandFromFileOrString(NULL,
350 						nextcommand,
351 						&nextcommand,
352 						&command,
353 						env)) {
354 			break;
355 		}
356 		if (!runCommand(sqlrcon,sqlrcur,env,
357 					command.getString(),
358 					exitprogram)) {
359 			return false;
360 		}
361 	}
362 	return true;
363 }
364 
getCommandFromFileOrString(file * fl,const char * string,const char ** stringpos,stringbuffer * cmdbuffer,sqlrshenv * env)365 bool sqlrsh::getCommandFromFileOrString(file *fl,
366 					const char *string,
367 					const char **stringpos,
368 					stringbuffer *cmdbuffer,
369 					sqlrshenv *env) {
370 
371 	bool	ininitialwhitespace=true;
372 	bool	insinglequotes=false;
373 	bool	indoublequotes=false;
374 	char	ch;
375 
376 	for (;;) {
377 
378 		// get a character from the file or string
379 		if (fl) {
380 			if (fl->read(&ch)!=sizeof(ch)) {
381 				// end of the command...
382 				// only return false if we're at the
383 				// beginning, prior to any actual command
384 				return !ininitialwhitespace;
385 			}
386 		} else {
387 			if (!*string) {
388 				// end of the command...
389 				// only return false if we're at the
390 				// beginning, prior to any actual command
391 				if (stringpos) {
392 					*stringpos=string;
393 				}
394 				return !ininitialwhitespace;
395 			}
396 			ch=*string;
397 			string++;
398 		}
399 
400 		// skip whitespace at the beginning
401 		if (ininitialwhitespace) {
402 			if (character::isWhitespace(ch)) {
403 				continue;
404 			}
405 			ininitialwhitespace=false;
406 		}
407 
408 		// handle single-quoted strings, with escaping
409 		if (ch=='\'') {
410 			if (insinglequotes) {
411 				cmdbuffer->append(ch);
412 				if (fl) {
413 					if (fl->read(&ch)!=sizeof(ch)) {
414 						return true;
415 					}
416 				} else {
417 					ch=*string;
418 					string++;
419 				}
420 				if (ch!='\'') {
421 					insinglequotes=false;
422 				}
423 			} else {
424 				insinglequotes=true;
425 			}
426 		}
427 
428 		// handle double-quoted strings, with escaping
429 		if (ch=='"') {
430 			if (indoublequotes) {
431 				cmdbuffer->append(ch);
432 				if (fl) {
433 					if (fl->read(&ch)!=sizeof(ch)) {
434 						return true;
435 					}
436 				} else {
437 					ch=*string;
438 					string++;
439 				}
440 				if (ch!='"') {
441 					indoublequotes=false;
442 				}
443 			} else {
444 				indoublequotes=true;
445 			}
446 		}
447 
448 		// look for an end of command delimiter
449 		if (!insinglequotes && !indoublequotes && ch==env->delimiter) {
450 			if (string && stringpos) {
451 				*stringpos=string;
452 			}
453 			return true;
454 		}
455 
456 		// write character to buffer and move on
457 		cmdbuffer->append(ch);
458 	}
459 }
460 
runCommand(sqlrconnection * sqlrcon,sqlrcursor * sqlrcur,sqlrshenv * env,const char * command,bool * exitprogram)461 bool sqlrsh::runCommand(sqlrconnection *sqlrcon,
462 					sqlrcursor *sqlrcur,
463 					sqlrshenv *env,
464 					const char *command,
465 					bool *exitprogram) {
466 
467 	int	cmdtype=commandType(command);
468 	if (exitprogram) {
469 		*exitprogram=false;
470 	}
471 
472 	// init stats
473 	initStats(env);
474 
475 	if (cmdtype>0) {
476 		// if the command an internal command, run it as one
477 		return internalCommand(sqlrcon,sqlrcur,env,command);
478 	} else if (cmdtype==0) {
479 		// if the command is not an internal command,
480 		// execute it as a query and display the result set
481 		return externalCommand(sqlrcon,sqlrcur,env,command);
482 	}
483 
484 	// exit
485 	if (exitprogram) {
486 		*exitprogram=true;
487 	}
488 	return true;
489 }
490 
commandType(const char * command)491 int sqlrsh::commandType(const char *command) {
492 
493 	// skip white space
494 	char	*ptr=(char *)command;
495 	while (*ptr==' ' || *ptr=='	' || *ptr=='\n') {
496 		ptr++;
497 	}
498 
499 	// compare to known internal commands
500 	if (!charstring::compareIgnoringCase(ptr,"headers",7) ||
501 		!charstring::compareIgnoringCase(ptr,"divider",7) ||
502 		!charstring::compareIgnoringCase(ptr,"stats",5) ||
503 		!charstring::compareIgnoringCase(ptr,"format",6) ||
504 		!charstring::compareIgnoringCase(ptr,"debug",5) ||
505 		!charstring::compareIgnoringCase(ptr,"nullsasnulls",12) ||
506 		!charstring::compareIgnoringCase(ptr,"autocommit",10) ||
507 		!charstring::compareIgnoringCase(ptr,"final",5) ||
508 		!charstring::compareIgnoringCase(ptr,"help") ||
509 		!charstring::compareIgnoringCase(ptr,"ping") ||
510 		!charstring::compareIgnoringCase(ptr,"identify") ||
511 		!charstring::compareIgnoringCase(ptr,"dbversion") ||
512 		!charstring::compareIgnoringCase(ptr,"dbhostname") ||
513 		!charstring::compareIgnoringCase(ptr,"dbipaddress") ||
514 		!charstring::compareIgnoringCase(ptr,"clientversion") ||
515 		!charstring::compareIgnoringCase(ptr,"serverversion") ||
516 		!charstring::compareIgnoringCase(ptr,"use ",4) ||
517 		!charstring::compareIgnoringCase(ptr,"currentdb") ||
518 		!charstring::compareIgnoringCase(ptr,"currentschema") ||
519 		!charstring::compareIgnoringCase(ptr,"run",3) ||
520 		!charstring::compareIgnoringCase(ptr,"@",1) ||
521 		!charstring::compareIgnoringCase(ptr,"delimiter",9) ||
522 		!charstring::compareIgnoringCase(ptr,"delimeter",9) ||
523 		!charstring::compareIgnoringCase(ptr,"inputbind ",10) ||
524 		!charstring::compareIgnoringCase(ptr,"inputbindblob ",14) ||
525 		!charstring::compareIgnoringCase(ptr,"outputbind ",11) ||
526 		!charstring::compareIgnoringCase(ptr,"inputoutputbind ",16) ||
527 		!charstring::compareIgnoringCase(ptr,"printinputbind",14) ||
528 		!charstring::compareIgnoringCase(ptr,"printoutputbind",15) ||
529 		!charstring::compareIgnoringCase(
530 					ptr,"printinputoutputbind",20) ||
531 		!charstring::compareIgnoringCase(ptr,"printbinds") ||
532 		!charstring::compareIgnoringCase(ptr,"clearinputbind",14) ||
533 		!charstring::compareIgnoringCase(ptr,"clearoutputbind",15) ||
534 		!charstring::compareIgnoringCase(
535 					ptr,"clearinputoutputbind",20) ||
536 		!charstring::compareIgnoringCase(ptr,"clearbinds") ||
537 		!charstring::compareIgnoringCase(ptr,"lastinsertid") ||
538 		!charstring::compareIgnoringCase(ptr,"setclientinfo ",14) ||
539 		!charstring::compareIgnoringCase(ptr,"getclientinfo") ||
540 		!charstring::compareIgnoringCase(ptr,
541 					"setresultsetbuffersize ",23) ||
542 		!charstring::compareIgnoringCase(ptr,
543 					"getresultsetbuffersize") ||
544 		!charstring::compareIgnoringCase(ptr,"lazyfetch ",10) ||
545 		!charstring::compareIgnoringCase(ptr,"endsession") ||
546 		!charstring::compareIgnoringCase(ptr,"querytree") ||
547 		!charstring::compareIgnoringCase(ptr,"translatedquery") ||
548 		!charstring::compareIgnoringCase(ptr,"response timeout",16) ||
549 		!charstring::compareIgnoringCase(ptr,"cache ",6) ||
550 		!charstring::compareIgnoringCase(ptr,"opencache ",10)) {
551 
552 		// return value of 1 is internal command
553 		return 1;
554 	}
555 
556 	// look for an exit command
557 	if (!charstring::compareIgnoringCase(ptr,"quit",4) ||
558 		!charstring::compareIgnoringCase(ptr,"exit",4)) {
559 		return -1;
560 	}
561 
562 	// return value of 0 is external command
563 	return 0;
564 }
565 
internalCommand(sqlrconnection * sqlrcon,sqlrcursor * sqlrcur,sqlrshenv * env,const char * command)566 bool sqlrsh::internalCommand(sqlrconnection *sqlrcon, sqlrcursor *sqlrcur,
567 					sqlrshenv *env, const char *command) {
568 
569 	// skip white space
570 	char	*ptr=(char *)command;
571 	while (*ptr==' ' || *ptr=='	' || *ptr=='\n') {
572 		ptr++;
573 	}
574 
575 	// compare to known internal commands
576 	int	cmdtype=0;
577 	if (!charstring::compareIgnoringCase(ptr,"headers",7)) {
578 		ptr=ptr+7;
579 		cmdtype=2;
580 	} else if (!charstring::compareIgnoringCase(ptr,"divider",7)) {
581 		ptr=ptr+7;
582 		cmdtype=11;
583 	} else if (!charstring::compareIgnoringCase(ptr,"stats",5)) {
584 		ptr=ptr+5;
585 		cmdtype=3;
586 	} else if (!charstring::compareIgnoringCase(ptr,"format",6)) {
587 		ptr=ptr+6;
588 		cmdtype=10;
589 	} else if (!charstring::compareIgnoringCase(ptr,"debug",5)) {
590 		ptr=ptr+5;
591 		cmdtype=4;
592 	} else if (!charstring::compareIgnoringCase(ptr,"nullsasnulls",12)) {
593 		ptr=ptr+13;
594 		cmdtype=9;
595 	} else if (!charstring::compareIgnoringCase(ptr,"autocommit",10)) {
596 		ptr=ptr+10;
597 		cmdtype=8;
598 	} else if (!charstring::compareIgnoringCase(ptr,"final",5)) {
599 		ptr=ptr+5;
600 		cmdtype=5;
601 	} else if (!charstring::compareIgnoringCase(ptr,"help")) {
602 		displayHelp(env);
603 		return true;
604 	} else if (!charstring::compareIgnoringCase(ptr,"ping")) {
605 		return ping(sqlrcon,env);
606 	} else if (!charstring::compareIgnoringCase(ptr,"use ",4)) {
607 		if (!sqlrcon->selectDatabase(ptr+4)) {
608 			displayError(env,NULL,
609 					sqlrcon->errorMessage(),
610 					sqlrcon->errorNumber());
611 			return false;
612 		}
613 		return true;
614 	} else if (!charstring::compareIgnoringCase(ptr,"currentdb")) {
615 		const char	*currentdb=sqlrcon->getCurrentDatabase();
616 		if (currentdb) {
617 			stdoutput.printf("%s\n",currentdb);
618 		} else if (sqlrcon->errorMessage()) {
619 			displayError(env,NULL,
620 					sqlrcon->errorMessage(),
621 					sqlrcon->errorNumber());
622 			return false;
623 		} else {
624 			stdoutput.printf("\n");
625 		}
626 		return true;
627 	} else if (!charstring::compareIgnoringCase(ptr,"currentschema")) {
628 		const char	*currentschema=sqlrcon->getCurrentSchema();
629 		if (currentschema) {
630 			stdoutput.printf("%s\n",currentschema);
631 		} else if (sqlrcon->errorMessage()) {
632 			displayError(env,NULL,
633 					sqlrcon->errorMessage(),
634 					sqlrcon->errorNumber());
635 			return false;
636 		} else {
637 			stdoutput.printf("\n");
638 		}
639 		return true;
640 	} else if (!charstring::compareIgnoringCase(ptr,"run",3)) {
641 		ptr=ptr+3;
642 		cmdtype=6;
643 	} else if (!charstring::compareIgnoringCase(ptr,"@",1)) {
644 		ptr=ptr+1;
645 		cmdtype=6;
646 	} else if (!charstring::compareIgnoringCase(ptr,"delimiter",9) ||
647 			!charstring::compareIgnoringCase(ptr,"delimeter",9)) {
648 		ptr=ptr+9;
649 		cmdtype=7;
650 	} else if (!charstring::compareIgnoringCase(ptr,"identify")) {
651 		return identify(sqlrcon,env);
652 	} else if (!charstring::compareIgnoringCase(ptr,"dbversion")) {
653 		return dbversion(sqlrcon,env);
654 	} else if (!charstring::compareIgnoringCase(ptr,"dbhostname")) {
655 		return dbhostname(sqlrcon,env);
656 	} else if (!charstring::compareIgnoringCase(ptr,"dbipaddress")) {
657 		return dbipaddress(sqlrcon,env);
658 	} else if (!charstring::compareIgnoringCase(ptr,"clientversion")) {
659 		clientversion(sqlrcon,env);
660 		return true;
661 	} else if (!charstring::compareIgnoringCase(ptr,"serverversion")) {
662 		return serverversion(sqlrcon,env);
663 	} else if (!charstring::compareIgnoringCase(ptr,"inputbind ",10)) {
664 		return inputbind(sqlrcur,env,command);
665 	} else if (!charstring::compareIgnoringCase(ptr,"inputbindblob ",14)) {
666 		return inputbindblob(sqlrcur,env,command);
667 	} else if (!charstring::compareIgnoringCase(ptr,"outputbind ",11)) {
668 		return outputbind(sqlrcur,env,command);
669 	} else if (!charstring::compareIgnoringCase(
670 						ptr,"inputoutputbind ",16)) {
671 		return inputoutputbind(sqlrcur,env,command);
672 	} else if (!charstring::compareIgnoringCase(ptr,"printbinds")) {
673 		printbinds("Input",&env->inputbinds);
674 		stdoutput.printf("\n");
675 		printbinds("Output",&env->outputbinds);
676 		stdoutput.printf("\n");
677 		printbinds("Input/Output",&env->inputoutputbinds);
678 		return true;
679 	} else if (!charstring::compareIgnoringCase(ptr,"clearinputbind",14)) {
680 		env->clearbinds(&env->inputbinds);
681 		return true;
682 	} else if (!charstring::compareIgnoringCase(ptr,"clearoutputbind",15)) {
683 		env->clearbinds(&env->outputbinds);
684 		return true;
685 	} else if (!charstring::compareIgnoringCase(ptr,
686 						"clearinputoutputbind",20)) {
687 		env->clearbinds(&env->inputoutputbinds);
688 		return true;
689 	} else if (!charstring::compareIgnoringCase(ptr,"clearbinds")) {
690 		env->clearbinds(&env->inputbinds);
691 		env->clearbinds(&env->outputbinds);
692 		env->clearbinds(&env->inputoutputbinds);
693 		return true;
694 	} else if (!charstring::compareIgnoringCase(ptr,"lastinsertid")) {
695 		if (!lastinsertid(sqlrcon,env)) {
696 			displayError(env,NULL,
697 					sqlrcon->errorMessage(),
698 					sqlrcon->errorNumber());
699 			return false;
700 		}
701 		return true;
702 	} else if (!charstring::compareIgnoringCase(ptr,"setclientinfo ",14)) {
703 		setclientinfo(sqlrcon,command);
704 		return true;
705 	} else if (!charstring::compareIgnoringCase(ptr,"getclientinfo")) {
706 		getclientinfo(sqlrcon);
707 		return true;
708 	} else if (!charstring::compareIgnoringCase(
709 					ptr,"setresultsetbuffersize ",23)) {
710 		ptr=ptr+23;
711 		env->rsbs=charstring::toInteger(ptr);
712 		return true;
713 	} else if (!charstring::compareIgnoringCase(ptr,"lazyfetch ",10)) {
714 		ptr=ptr+10;
715 		cmdtype=12;
716 	} else if (!charstring::compareIgnoringCase(
717 					ptr,"getresultsetbuffersize")) {
718 		stdoutput.printf("%lld\n",(long long)env->rsbs);
719 		return true;
720 	} else if (!charstring::compareIgnoringCase(ptr,"endsession")) {
721 		sqlrcon->endSession();
722 		return true;
723 	} else if (!charstring::compareIgnoringCase(ptr,"querytree")) {
724 		xmldom	xmld;
725 		if (xmld.parseString(sqlrcur->getQueryTree())) {
726 			xmld.getRootNode()->write(&stdoutput,true);
727 		}
728 		return true;
729 	} else if (!charstring::compareIgnoringCase(ptr,"translatedquery")) {
730 		stdoutput.printf("%s\n",sqlrcur->getTranslatedQuery());
731 		return true;
732 	} else if (!charstring::compareIgnoringCase(
733 					ptr,"response timeout",16)) {
734 		responseTimeout(sqlrcon,command);
735 		return true;
736 	} else if (!charstring::compareIgnoringCase(ptr,"cache ",6)) {
737 		return cache(env,sqlrcur,command);
738 	} else if (!charstring::compareIgnoringCase(ptr,"opencache ",10)) {
739 		return openCache(env,sqlrcur,command);
740 	} else {
741 		return false;
742 	}
743 
744 	// skip white space
745 	while (*ptr==' ' || *ptr=='	' || *ptr=='\n') {
746 		ptr++;
747 	}
748 
749 	// handle scripts
750 	if (cmdtype==6) {
751 		return runScript(sqlrcon,sqlrcur,env,ptr,true);
752 	}
753 
754 	// handle debug
755 	if (cmdtype==4) {
756 		if (!charstring::compareIgnoringCase(ptr,"on",2)) {
757 			sqlrcon->debugOn();
758 			sqlrcon->setDebugFile(NULL);
759 		} else if (!charstring::compareIgnoringCase(ptr,"off",3)) {
760 			sqlrcon->debugOff();
761 			sqlrcon->setDebugFile(NULL);
762 		} else {
763 			sqlrcon->debugOn();
764 			sqlrcon->setDebugFile(ptr);
765 		}
766 		return true;
767 	}
768 
769 	// handle nullsasnulls
770 	if (cmdtype==9) {
771 		if (!charstring::compareIgnoringCase(ptr,"on",2)) {
772 			sqlrcur->getNullsAsNulls();
773 		} else if (!charstring::compareIgnoringCase(ptr,"off",3)) {
774 			sqlrcur->getNullsAsEmptyStrings();
775 		}
776 		return true;
777 	}
778 
779 	// handle format
780 	if (cmdtype==10) {
781 		if (!charstring::compareIgnoringCase(ptr,"csv",3)) {
782 			env->format=SQLRSH_FORMAT_CSV;
783 		} else {
784 			env->format=SQLRSH_FORMAT_PLAIN;
785 		}
786 		return true;
787 	}
788 
789 	// on or off?
790 	bool	toggle=false;
791 	if (!charstring::compareIgnoringCase(ptr,"on",2)) {
792 		toggle=true;
793 	}
794 
795 	// set parameter
796 	switch (cmdtype) {
797 		case 2:
798 			env->headers=toggle;
799 			break;
800 		case 11:
801 			env->divider=toggle;
802 			break;
803 		case 3:
804 			env->stats=toggle;
805 			break;
806 		case 5:
807 			env->final=toggle;
808 			break;
809 		case 7:
810 			env->delimiter=ptr[0];
811 			stdoutput.printf("Delimiter set to %c\n",
812 							env->delimiter);
813 			break;
814 		case 8:
815 			if (toggle) {
816 				if (sqlrcon->autoCommitOn()) {
817 					stdoutput.printf(
818 						"Autocommit set on\n");
819 				} else {
820 					displayError(env,NULL,
821 						sqlrcon->errorMessage(),
822 						sqlrcon->errorNumber());
823 					return false;
824 				}
825 			} else {
826 				if (sqlrcon->autoCommitOff()) {
827 					stdoutput.printf(
828 						"Autocommit set off\n");
829 				} else {
830 					displayError(env,NULL,
831 						sqlrcon->errorMessage(),
832 						sqlrcon->errorNumber());
833 					return false;
834 				}
835 			}
836 			break;
837 		case 12:
838 			env->lazyfetch=toggle;
839 			break;
840 	}
841 	return true;
842 }
843 
externalCommand(sqlrconnection * sqlrcon,sqlrcursor * sqlrcur,sqlrshenv * env,const char * command)844 bool sqlrsh::externalCommand(sqlrconnection *sqlrcon,
845 				sqlrcursor *sqlrcur, sqlrshenv *env,
846 				const char *command) {
847 
848 	bool	retval=true;
849 
850 	// handle begin, commit and rollback
851 	if (!charstring::compareIgnoringCase(command,"begin")) {
852 
853 		if (!sqlrcon->begin()) {
854 			displayError(env,NULL,
855 					sqlrcon->errorMessage(),
856 					sqlrcon->errorNumber());
857 			retval=false;
858 		}
859 
860 	} else if (!charstring::compareIgnoringCase(command,"commit")) {
861 
862 		if (!sqlrcon->commit()) {
863 			displayError(env,NULL,
864 					sqlrcon->errorMessage(),
865 					sqlrcon->errorNumber());
866 			retval=false;
867 		}
868 
869 	} else if (!charstring::compareIgnoringCase(command,"rollback")) {
870 
871 		if (!sqlrcon->rollback()) {
872 			displayError(env,NULL,
873 					sqlrcon->errorMessage(),
874 					sqlrcon->errorNumber());
875 			retval=false;
876 		}
877 
878 	} else if (!charstring::compareIgnoringCase(command,"fields ",7)) {
879 
880 		char	*table=getTable(command,false);
881 		sqlrcur->getColumnList(table,NULL);
882 		delete[] table;
883 
884 		for (uint64_t j=0; j<sqlrcur->rowCount(); j++) {
885 			if (j>0) {
886 				stdoutput.printf(",");
887 			}
888 			stdoutput.printf("%s",sqlrcur->getField(j,(uint32_t)0));
889 		}
890 		stdoutput.printf("\n");
891 
892 		if (env->final) {
893 			sqlrcon->endSession();
894 		}
895 
896 	} else {
897 
898 		sqlrcur->setResultSetBufferSize(env->rsbs);
899 
900 		if (env->lazyfetch) {
901 			sqlrcur->lazyFetch();
902 		} else {
903 			sqlrcur->dontLazyFetch();
904 		}
905 
906 		// send the query
907 		if (!charstring::compareIgnoringCase(command,
908 						"show databases odbc",19)) {
909 			char	*wild=getWild(command);
910 			sqlrcur->getDatabaseList(wild,
911 					SQLRCLIENTLISTFORMAT_ODBC);
912 			delete[] wild;
913 		} else if (!charstring::compareIgnoringCase(command,
914 						"show databases",14)) {
915 			char	*wild=getWild(command);
916 			sqlrcur->getDatabaseList(wild);
917 			delete[] wild;
918 		} else if (!charstring::compareIgnoringCase(command,
919 						"show schemas",12)) {
920 			char	*wild=getWild(command);
921 			sqlrcur->getSchemaList(wild);
922 			delete[] wild;
923 		} else if (!charstring::compareIgnoringCase(command,
924 						"show tables odbc",16)) {
925 			char	*wild=getWild(command);
926 			sqlrcur->getTableList(wild,
927 					SQLRCLIENTLISTFORMAT_ODBC,
928 					DB_OBJECT_TABLE|
929 					DB_OBJECT_VIEW|
930 					DB_OBJECT_ALIAS|
931 					DB_OBJECT_SYNONYM);
932 			delete[] wild;
933 		} else if (!charstring::compareIgnoringCase(command,
934 						"show only tables odbc",21)) {
935 			char	*wild=getWild(command);
936 			sqlrcur->getTableList(wild,
937 					SQLRCLIENTLISTFORMAT_ODBC,
938 					DB_OBJECT_TABLE);
939 			delete[] wild;
940 		} else if (!charstring::compareIgnoringCase(command,
941 						"show only views odbc",20)) {
942 			char	*wild=getWild(command);
943 			sqlrcur->getTableList(wild,
944 					SQLRCLIENTLISTFORMAT_ODBC,
945 					DB_OBJECT_VIEW);
946 			delete[] wild;
947 		} else if (!charstring::compareIgnoringCase(command,
948 						"show only aliases odbc",22)) {
949 			char	*wild=getWild(command);
950 			sqlrcur->getTableList(wild,
951 					SQLRCLIENTLISTFORMAT_ODBC,
952 					DB_OBJECT_ALIAS);
953 			delete[] wild;
954 		} else if (!charstring::compareIgnoringCase(command,
955 						"show only synonyms odbc",23)) {
956 			char	*wild=getWild(command);
957 			sqlrcur->getTableList(wild,
958 					SQLRCLIENTLISTFORMAT_ODBC,
959 					DB_OBJECT_SYNONYM);
960 			delete[] wild;
961 		} else if (!charstring::compareIgnoringCase(command,
962 							"show tables",11)) {
963 			char	*wild=getWild(command);
964 			sqlrcur->getTableList(wild);
965 			delete[] wild;
966 		} else if (!charstring::compareIgnoringCase(command,
967 						"show table types",16)) {
968 			char	*wild=getWild(command);
969 			sqlrcur->getTableTypeList(wild);
970 			delete[] wild;
971 		} else if (!charstring::compareIgnoringCase(command,
972 						"show columns odbc",17)) {
973 			char	*table=getTable(command,true);
974 			char	*wild=getWild(command);
975 			sqlrcur->getColumnList(table,wild,
976 					SQLRCLIENTLISTFORMAT_ODBC);
977 			delete[] table;
978 			delete[] wild;
979 		} else if (!charstring::compareIgnoringCase(command,
980 							"show columns",12)) {
981 			char	*table=getTable(command,true);
982 			char	*wild=getWild(command);
983 			sqlrcur->getColumnList(table,wild);
984 			delete[] table;
985 			delete[] wild;
986 		} else if (!charstring::compareIgnoringCase(command,
987 						"show primary keys",17)) {
988 			char	*table=getTable(command,true);
989 			char	*wild=getWild(command);
990 			sqlrcur->getPrimaryKeysList(table,wild);
991 			delete[] table;
992 			delete[] wild;
993 		} else if (!charstring::compareIgnoringCase(command,
994 						"show keys and indexes",21)) {
995 			char	*table=getTable(command,true);
996 			char	*wild=getWild(command);
997 			sqlrcur->getKeyAndIndexList(table,wild);
998 			delete[] table;
999 			delete[] wild;
1000 		} else if (!charstring::compareIgnoringCase(command,
1001 							"describe ",9)) {
1002 			char	*table=getTable(command,false);
1003 			char	*wild=getWild(command);
1004 			sqlrcur->getColumnList(table,wild);
1005 			delete[] table;
1006 			delete[] wild;
1007 		} else if (!charstring::compareIgnoringCase(command,
1008 				"show procedure binds and columns",32)) {
1009 			char	*procedure=getProcedure(command);
1010 			char	*wild=getWild(command);
1011 			sqlrcur->getProcedureBindAndColumnList(procedure,wild);
1012 			delete[] procedure;
1013 			delete[] wild;
1014 		} else if (!charstring::compareIgnoringCase(command,
1015 							"show type info",14)) {
1016 			char	*type=getType(command);
1017 			sqlrcur->getTypeInfoList(type,NULL);
1018 			delete[] type;
1019 		} else if (!charstring::compareIgnoringCase(command,
1020 						"show procedures",15)) {
1021 			char	*wild=getWild(command);
1022 			sqlrcur->getProcedureList(wild);
1023 			delete[] wild;
1024 		} else if (!charstring::compareIgnoringCase(command,
1025 							"reexecute")) {
1026 			executeQuery(sqlrcur,env);
1027 		} else {
1028 			sqlrcur->prepareQuery(command);
1029 			executeQuery(sqlrcur,env);
1030 		}
1031 
1032 		// look for an error
1033 		if (sqlrcur->errorMessage()) {
1034 
1035 			// display the error
1036 			displayError(env,NULL,
1037 					sqlrcur->errorMessage(),
1038 					sqlrcur->errorNumber());
1039 			retval=false;
1040 
1041 		} else if (env->nextresultset) {
1042 
1043 			do {
1044 
1045 				// display the header
1046 				displayHeader(sqlrcur,env);
1047 
1048 				// display the result set
1049 				displayResultSet(sqlrcur,env);
1050 
1051 				// display any errors
1052 				if (sqlrcur->errorMessage()) {
1053 					displayError(env,NULL,
1054 						sqlrcur->errorMessage(),
1055 						sqlrcur->errorNumber());
1056 					retval=false;
1057 				}
1058 
1059 			} while (sqlrcur->nextResultSet());
1060 
1061 		} else {
1062 
1063 			// display the header
1064 			displayHeader(sqlrcur,env);
1065 
1066 			// display the result set
1067 			displayResultSet(sqlrcur,env);
1068 		}
1069 
1070 		if (env->final) {
1071 			sqlrcon->endSession();
1072 		}
1073 	}
1074 
1075 	// display statistics
1076 	displayStats(sqlrcur,env);
1077 
1078 	return retval;
1079 }
1080 
executeQuery(sqlrcursor * sqlrcur,sqlrshenv * env)1081 void sqlrsh::executeQuery(sqlrcursor *sqlrcur, sqlrshenv *env) {
1082 
1083 	sqlrcur->clearBinds();
1084 
1085 	if (env->inputbinds.getList()->getLength()) {
1086 
1087 		for (linkedlistnode<dictionarynode<char *, sqlrshbindvalue *> *>
1088 				*node=env->inputbinds.getList()->getFirst();
1089 				node; node=node->getNext()) {
1090 
1091 			const char	*name=node->getValue()->getKey();
1092 			sqlrshbindvalue	*bv=node->getValue()->getValue();
1093 			if (bv->type==SQLRCLIENTBINDVARTYPE_STRING) {
1094 				sqlrcur->inputBind(name,bv->stringval);
1095 			} else if (bv->type==SQLRCLIENTBINDVARTYPE_INTEGER) {
1096 				sqlrcur->inputBind(name,bv->integerval);
1097 			} else if (bv->type==SQLRCLIENTBINDVARTYPE_DOUBLE) {
1098 				sqlrcur->inputBind(name,bv->doubleval.value,
1099 							bv->doubleval.precision,
1100 							bv->doubleval.scale);
1101 			} else if (bv->type==SQLRCLIENTBINDVARTYPE_DATE) {
1102 				sqlrcur->inputBind(name,
1103 						bv->dateval.year,
1104 						bv->dateval.month,
1105 						bv->dateval.day,
1106 						bv->dateval.hour,
1107 						bv->dateval.minute,
1108 						bv->dateval.second,
1109 						bv->dateval.microsecond,
1110 						bv->dateval.tz,
1111 						bv->dateval.isnegative);
1112 			} else if (bv->type==SQLRCLIENTBINDVARTYPE_BLOB) {
1113 				sqlrcur->inputBindBlob(name,bv->stringval,
1114 					charstring::length(bv->stringval));
1115 			} else if (bv->type==SQLRCLIENTBINDVARTYPE_NULL) {
1116 				sqlrcur->inputBind(name,(const char *)NULL);
1117 			}
1118 		}
1119 	}
1120 
1121 	if (env->outputbinds.getList()->getLength()) {
1122 
1123 		for (linkedlistnode<dictionarynode<char *, sqlrshbindvalue *> *>
1124 			*node=env->outputbinds.getList()->getFirst();
1125 			node; node=node->getNext()) {
1126 
1127 			const char	*name=node->getValue()->getKey();
1128 			sqlrshbindvalue	*bv=node->getValue()->getValue();
1129 			if (bv->type==SQLRCLIENTBINDVARTYPE_STRING) {
1130 				// FIXME: make buffer length variable
1131 				sqlrcur->defineOutputBindString(name,
1132 						bv->outputstringbindlength);
1133 			} else if (bv->type==SQLRCLIENTBINDVARTYPE_INTEGER) {
1134 				sqlrcur->defineOutputBindInteger(name);
1135 			} else if (bv->type==SQLRCLIENTBINDVARTYPE_DOUBLE) {
1136 				sqlrcur->defineOutputBindDouble(name);
1137 			} else if (bv->type==SQLRCLIENTBINDVARTYPE_DATE) {
1138 				sqlrcur->defineOutputBindDate(name);
1139 			}
1140 		}
1141 	}
1142 
1143 	if (env->inputoutputbinds.getList()->getLength()) {
1144 
1145 		for (linkedlistnode<dictionarynode<char *, sqlrshbindvalue *> *>
1146 			*node=env->inputoutputbinds.getList()->getFirst();
1147 			node; node=node->getNext()) {
1148 
1149 			const char	*name=node->getValue()->getKey();
1150 			sqlrshbindvalue	*bv=node->getValue()->getValue();
1151 			if (bv->type==SQLRCLIENTBINDVARTYPE_STRING) {
1152 				sqlrcur->defineInputOutputBindString(name,
1153 						bv->stringval,
1154 						bv->outputstringbindlength);
1155 			} else if (bv->type==SQLRCLIENTBINDVARTYPE_INTEGER) {
1156 				sqlrcur->defineInputOutputBindInteger(name,
1157 						bv->integerval);
1158 			} else if (bv->type==SQLRCLIENTBINDVARTYPE_DOUBLE) {
1159 				sqlrcur->defineInputOutputBindDouble(name,
1160 						bv->doubleval.value,
1161 						bv->doubleval.precision,
1162 						bv->doubleval.scale);
1163 			} else if (bv->type==SQLRCLIENTBINDVARTYPE_DATE) {
1164 				sqlrcur->defineInputOutputBindDate(name,
1165 						bv->dateval.year,
1166 						bv->dateval.month,
1167 						bv->dateval.day,
1168 						bv->dateval.hour,
1169 						bv->dateval.minute,
1170 						bv->dateval.second,
1171 						bv->dateval.microsecond,
1172 						bv->dateval.tz,
1173 						bv->dateval.isnegative);
1174 			}
1175 		}
1176 	}
1177 
1178 	sqlrcur->executeQuery();
1179 
1180 	if (env->outputbinds.getList()->getLength()) {
1181 
1182 		for (linkedlistnode<dictionarynode<char *, sqlrshbindvalue *> *>
1183 			*node=env->outputbinds.getList()->getFirst();
1184 			node; node=node->getNext()) {
1185 
1186 			const char	*name=node->getValue()->getKey();
1187 			sqlrshbindvalue	*bv=node->getValue()->getValue();
1188 			if (bv->type==SQLRCLIENTBINDVARTYPE_STRING) {
1189 				delete[] bv->stringval;
1190 				bv->stringval=charstring::duplicate(
1191 					sqlrcur->getOutputBindString(name));
1192 			} else if (bv->type==SQLRCLIENTBINDVARTYPE_INTEGER) {
1193 				bv->integerval=
1194 					sqlrcur->getOutputBindInteger(name);
1195 			} else if (bv->type==SQLRCLIENTBINDVARTYPE_DOUBLE) {
1196 				bv->doubleval.value=
1197 					sqlrcur->getOutputBindDouble(name);
1198 			} else if (bv->type==SQLRCLIENTBINDVARTYPE_DATE) {
1199 				sqlrcur->getOutputBindDate(name,
1200 						&(bv->dateval.year),
1201 						&(bv->dateval.month),
1202 						&(bv->dateval.day),
1203 						&(bv->dateval.hour),
1204 						&(bv->dateval.minute),
1205 						&(bv->dateval.second),
1206 						&(bv->dateval.microsecond),
1207 						&(bv->dateval.tz),
1208 						&(bv->dateval.isnegative));
1209 			}
1210 		}
1211 	}
1212 
1213 	if (env->inputoutputbinds.getList()->getLength()) {
1214 
1215 		for (linkedlistnode<dictionarynode<char *, sqlrshbindvalue *> *>
1216 			*node=env->inputoutputbinds.getList()->getFirst();
1217 			node; node=node->getNext()) {
1218 
1219 			const char	*name=node->getValue()->getKey();
1220 			sqlrshbindvalue	*bv=node->getValue()->getValue();
1221 			if (bv->type==SQLRCLIENTBINDVARTYPE_STRING) {
1222 				delete[] bv->stringval;
1223 				bv->stringval=charstring::duplicate(
1224 				sqlrcur->getInputOutputBindString(name));
1225 			} else if (bv->type==SQLRCLIENTBINDVARTYPE_INTEGER) {
1226 				bv->integerval=
1227 				sqlrcur->getInputOutputBindInteger(name);
1228 			} else if (bv->type==SQLRCLIENTBINDVARTYPE_DOUBLE) {
1229 				bv->doubleval.value=
1230 				sqlrcur->getInputOutputBindDouble(name);
1231 			} else if (bv->type==SQLRCLIENTBINDVARTYPE_DATE) {
1232 				sqlrcur->getInputOutputBindDate(name,
1233 						&(bv->dateval.year),
1234 						&(bv->dateval.month),
1235 						&(bv->dateval.day),
1236 						&(bv->dateval.hour),
1237 						&(bv->dateval.minute),
1238 						&(bv->dateval.second),
1239 						&(bv->dateval.microsecond),
1240 						&(bv->dateval.tz),
1241 						&(bv->dateval.isnegative));
1242 			}
1243 		}
1244 	}
1245 }
1246 
getWild(const char * command)1247 char *sqlrsh::getWild(const char *command) {
1248 	const char	*wildptr=charstring::findFirst(command,"'");
1249 	if (!wildptr) {
1250 		return NULL;
1251 	}
1252 	wildptr++;
1253 	const char	*endptr=charstring::findLast(wildptr,"'");
1254 	if (!endptr) {
1255 		return NULL;
1256 	}
1257 
1258 	// unescape single quotes
1259 	stringbuffer	output;
1260 	for (const char *ch=wildptr; ch<endptr; ch++) {
1261 		if (*ch=='\'' && *(ch+1)=='\'') {
1262 			ch++;
1263 		}
1264 		output.append(*ch);
1265 	}
1266 
1267 	return output.detachString();
1268 }
1269 
getTable(const char * command,bool in)1270 char *sqlrsh::getTable(const char *command, bool in) {
1271 	const char	*tableptr=NULL;
1272 	if (in) {
1273 		tableptr=charstring::findFirst(command," in ");
1274 		if (!tableptr) {
1275 			return NULL;
1276 		}
1277 		tableptr=tableptr+4;
1278 		const char	*endptr=charstring::findFirst(tableptr," ");
1279 		if (!endptr) {
1280 			return charstring::duplicate(tableptr);
1281 		}
1282 		return charstring::duplicate(tableptr,endptr-tableptr);
1283 	} else {
1284 		tableptr=charstring::findFirst(command," ");
1285 		if (!tableptr) {
1286 			return NULL;
1287 		}
1288 		return charstring::duplicate(tableptr+1);
1289 	}
1290 	return NULL;
1291 }
1292 
getProcedure(const char * command)1293 char *sqlrsh::getProcedure(const char *command) {
1294 	const char	*procptr=charstring::findFirst(command," in ");
1295 	if (!procptr) {
1296 		return NULL;
1297 	}
1298 	procptr=procptr+4;
1299 	const char	*endptr=charstring::findFirst(procptr," ");
1300 	if (!endptr) {
1301 		return charstring::duplicate(procptr);
1302 	}
1303 	return charstring::duplicate(procptr,endptr-procptr);
1304 }
1305 
getType(const char * command)1306 char *sqlrsh::getType(const char *command) {
1307 	const char	*procptr=charstring::findFirst(command," for ");
1308 	if (!procptr) {
1309 		return NULL;
1310 	}
1311 	procptr=procptr+5;
1312 	const char	*endptr=charstring::findFirst(procptr," ");
1313 	if (!endptr) {
1314 		return charstring::duplicate(procptr);
1315 	}
1316 	return charstring::duplicate(procptr,endptr-procptr);
1317 }
1318 
initStats(sqlrshenv * env)1319 void sqlrsh::initStats(sqlrshenv *env) {
1320 
1321 	if (!env->stats) {
1322 		return;
1323 	}
1324 
1325 	start.getSystemDateAndTime();
1326 }
1327 
displayError(sqlrshenv * env,const char * message,const char * error,int64_t errornumber)1328 void sqlrsh::displayError(sqlrshenv *env,
1329 				const char *message,
1330 				const char *error,
1331 				int64_t errornumber) {
1332 	if (!charstring::isNullOrEmpty(message)) {
1333 		stderror.printf("%s\n",message);
1334 	}
1335 	stderror.printf("%lld:\n",(long long)errornumber);
1336 	if (!charstring::isNullOrEmpty(error)) {
1337 		stderror.printf("%s\n\n",error);
1338 	}
1339 }
1340 
displayHeader(sqlrcursor * sqlrcur,sqlrshenv * env)1341 void sqlrsh::displayHeader(sqlrcursor *sqlrcur, sqlrshenv *env) {
1342 
1343 	if (!env->headers) {
1344 		return;
1345 	}
1346 
1347 	// display column names
1348 	uint32_t	charcount=0;
1349 	uint32_t	colcount=sqlrcur->colCount();
1350 	const char	*name;
1351 	uint32_t	namelen;
1352 	uint32_t	longest;
1353 
1354 	if (!colcount) {
1355 		return;
1356 	}
1357 
1358 	// iterate through columns
1359 	for (uint32_t ci=0; ci<sqlrcur->colCount(); ci++) {
1360 
1361 		// put a comma or extra space between field names
1362 		if (ci) {
1363 			if (env->format==SQLRSH_FORMAT_CSV) {
1364 				stdoutput.write(',');
1365 			} else {
1366 				stdoutput.write(' ');
1367 			}
1368 			charcount=charcount+1;
1369 		}
1370 
1371 		// write the column name
1372 		if (env->format==SQLRSH_FORMAT_CSV) {
1373 			stdoutput.write('\"');
1374 		}
1375 		name=sqlrcur->getColumnName(ci);
1376 		stdoutput.write(name);
1377 		if (env->format==SQLRSH_FORMAT_CSV) {
1378 			stdoutput.write('\"');
1379 		}
1380 		namelen=charstring::length(name);
1381 
1382 		// space-pad after the name, if necessary
1383 		if (env->format==SQLRSH_FORMAT_PLAIN) {
1384 			longest=sqlrcur->getLongest(ci);
1385 			if (namelen>longest) {
1386 				longest=namelen;
1387 			}
1388 			charcount=charcount+longest;
1389 
1390 			// pad after the name with spaces
1391 			for (uint32_t j=namelen; j<longest; j++) {
1392 				stdoutput.write(' ');
1393 			}
1394 		} else {
1395 			charcount=charcount+namelen+2;
1396 		}
1397 	}
1398 	stdoutput.printf("\n");
1399 
1400 	// display divider
1401 	if (env->divider) {
1402 		for (uint32_t i=0; i<charcount; i++) {
1403 			stdoutput.printf("=");
1404 		}
1405 		stdoutput.printf("\n");
1406 	}
1407 }
1408 
displayResultSet(sqlrcursor * sqlrcur,sqlrshenv * env)1409 void sqlrsh::displayResultSet(sqlrcursor *sqlrcur, sqlrshenv *env) {
1410 
1411 	uint32_t	colcount=sqlrcur->colCount();
1412 	if (!colcount) {
1413 		return;
1414 	}
1415 
1416 	uint32_t	namelen;
1417 	uint32_t	longest;
1418 	const char	*field;
1419 	uint32_t	fieldlength;
1420 	const char	*fieldtype;
1421 	char		numberfieldbuffer[256];
1422 
1423 	bool		done=false;
1424 	for (uint64_t row=0; !done; row++) {
1425 
1426 		for (uint32_t col=0; col<colcount; col++) {
1427 
1428 			// put a comma or extra space between fields
1429 			if (col) {
1430 				if (env->format==SQLRSH_FORMAT_CSV) {
1431 					stdoutput.write(',');
1432 				} else {
1433 					stdoutput.write(' ');
1434 				}
1435 			}
1436 
1437 			// get the field
1438 			field=sqlrcur->getField(row,col);
1439 			fieldlength=sqlrcur->getFieldLength(row,col);
1440 			fieldtype=sqlrcur->getColumnType(col);
1441 
1442 			// FIXME: move this down below the end-of-rs check?
1443 			// The purpose of this is to verify the functionality
1444 			// of the getFieldAsXXX() methods.
1445 			if (field && env->getasnumber &&
1446 				(isBitTypeChar(fieldtype) ||
1447 					isNumberTypeChar(fieldtype))) {
1448 
1449 				if (isFloatTypeChar(fieldtype)) {
1450 					double	fd=sqlrcur->getFieldAsDouble(row,col);
1451 					if (isNonScaleFloatTypeChar(fieldtype)) {
1452 						int32_t	precision=sqlrcur->getColumnPrecision(col);
1453 						// here precision is a number of bits, but printf %g wants digits.
1454 						// FIXME: precision should actually be the number of digits, not bits...
1455 						int32_t	digits=(int32_t)(ceil(precision/3.33));
1456 						charstring::printf(&numberfieldbuffer[0],sizeof(numberfieldbuffer),"%.*g",digits,fd);
1457 					} else {
1458 						int	scale=sqlrcur->getColumnScale(col);
1459 						// NOTE: we are not using the precision to format the number to a string.
1460 						charstring::printf(&numberfieldbuffer[0],sizeof(numberfieldbuffer),"%.*f",scale,fd);
1461 					}
1462 				} else {
1463 					int64_t fi = sqlrcur->getFieldAsInteger(row,col);
1464 					charstring::printf(&numberfieldbuffer[0], sizeof(numberfieldbuffer), "%ld", fi);
1465 				}
1466 				field=numberfieldbuffer;
1467 				fieldlength=charstring::length(field);
1468 			}
1469 
1470 			// check for end-of-result-set condition
1471 			// (since nullsasnulls might be set, we have to do
1472 			// a bit more than just check for a NULL)
1473 			if (!col && !field &&
1474 				sqlrcur->endOfResultSet() &&
1475 				row==sqlrcur->rowCount()) {
1476 				done=true;
1477 				break;
1478 			}
1479 
1480 			// handle nulls
1481 			if (!field) {
1482 				field="NULL";
1483 				fieldlength=4;
1484 			}
1485 
1486 			// write the field
1487 			if (env->format==SQLRSH_FORMAT_CSV) {
1488 				stdoutput.write('\"');
1489 			}
1490 			stdoutput.write(field);
1491 			if (env->format==SQLRSH_FORMAT_CSV) {
1492 				stdoutput.write('\"');
1493 			}
1494 
1495 			// space-pad after the field, if necessary
1496 			if (env->format==SQLRSH_FORMAT_PLAIN) {
1497 				longest=sqlrcur->getLongest(col);
1498 				if (env->headers) {
1499 					namelen=charstring::length(
1500 						sqlrcur->getColumnName(col));
1501 					if (namelen>longest) {
1502 						longest=namelen;
1503 					}
1504 				}
1505 				for (uint32_t i=fieldlength; i<longest; i++) {
1506 					stdoutput.write(' ');
1507 				}
1508 			}
1509 		}
1510 		stdoutput.write('\n');
1511 	}
1512 }
1513 
displayStats(sqlrcursor * sqlrcur,sqlrshenv * env)1514 void sqlrsh::displayStats(sqlrcursor *sqlrcur, sqlrshenv *env) {
1515 
1516 	if (!env->stats) {
1517 		return;
1518 	}
1519 
1520 	// calculate elapsed time
1521 	datetime	end;
1522 	end.getSystemDateAndTime();
1523 	uint64_t	startusec=start.getEpoch()*1000000+
1524 					start.getMicroseconds();
1525 	uint64_t	endusec=end.getEpoch()*1000000+
1526 					end.getMicroseconds();
1527 	double		time=((double)(endusec-startusec))/1000000;
1528 
1529 	// display stats
1530 	stdoutput.printf("	Rows Returned   : ");
1531 	stdoutput.printf("%lld\n",(long long)sqlrcur->rowCount());
1532 	stdoutput.printf("	Fields Returned : ");
1533 	stdoutput.printf("%lld\n",
1534 			(long long)sqlrcur->rowCount()*sqlrcur->colCount());
1535 	if (!env->noelapsed) {
1536 		stdoutput.printf("	Elapsed Time    : ");
1537 		stdoutput.printf("%.6f sec\n",time);
1538 	}
1539 	stdoutput.printf("\n");
1540 }
1541 
ping(sqlrconnection * sqlrcon,sqlrshenv * env)1542 bool sqlrsh::ping(sqlrconnection *sqlrcon, sqlrshenv *env) {
1543 	bool	result=sqlrcon->ping();
1544 	if (result) {
1545 		stdoutput.printf("	The database is up.\n");
1546 	} else if (sqlrcon->errorMessage()) {
1547 		displayError(env,NULL,
1548 				sqlrcon->errorMessage(),
1549 				sqlrcon->errorNumber());
1550 		return false;
1551 	} else {
1552 		stdoutput.printf("	The database is down.\n");
1553 	}
1554 	return true;
1555 }
1556 
lastinsertid(sqlrconnection * sqlrcon,sqlrshenv * env)1557 bool sqlrsh::lastinsertid(sqlrconnection *sqlrcon, sqlrshenv *env) {
1558 	bool		retval=false;
1559 	uint64_t	id=sqlrcon->getLastInsertId();
1560 	if (id!=0 || !sqlrcon->errorMessage()) {
1561 		stdoutput.printf("%lld\n",(long long)id);
1562 		retval=true;
1563 	}
1564 	return retval;
1565 }
1566 
identify(sqlrconnection * sqlrcon,sqlrshenv * env)1567 bool sqlrsh::identify(sqlrconnection *sqlrcon, sqlrshenv *env) {
1568 	const char	*value=sqlrcon->identify();
1569 	if (value) {
1570 		stdoutput.printf("%s\n",value);
1571 	} else if (sqlrcon->errorMessage()) {
1572 		displayError(env,NULL,
1573 				sqlrcon->errorMessage(),
1574 				sqlrcon->errorNumber());
1575 		return false;
1576 	} else {
1577 		stdoutput.printf("\n");
1578 	}
1579 	return true;
1580 }
1581 
dbversion(sqlrconnection * sqlrcon,sqlrshenv * env)1582 bool sqlrsh::dbversion(sqlrconnection *sqlrcon, sqlrshenv *env) {
1583 	const char	*value=sqlrcon->dbVersion();
1584 	if (value) {
1585 		stdoutput.printf("%s\n",value);
1586 	} else if (sqlrcon->errorMessage()) {
1587 		displayError(env,NULL,
1588 				sqlrcon->errorMessage(),
1589 				sqlrcon->errorNumber());
1590 		return false;
1591 	} else {
1592 		stdoutput.printf("\n");
1593 	}
1594 	return true;
1595 }
1596 
dbhostname(sqlrconnection * sqlrcon,sqlrshenv * env)1597 bool sqlrsh::dbhostname(sqlrconnection *sqlrcon, sqlrshenv *env) {
1598 	const char	*value=sqlrcon->dbHostName();
1599 	if (value) {
1600 		stdoutput.printf("%s\n",value);
1601 	} else if (sqlrcon->errorMessage()) {
1602 		displayError(env,NULL,
1603 				sqlrcon->errorMessage(),
1604 				sqlrcon->errorNumber());
1605 		return false;
1606 	} else {
1607 		stdoutput.printf("\n");
1608 	}
1609 	return true;
1610 }
1611 
dbipaddress(sqlrconnection * sqlrcon,sqlrshenv * env)1612 bool sqlrsh::dbipaddress(sqlrconnection *sqlrcon, sqlrshenv *env) {
1613 	const char	*value=sqlrcon->dbIpAddress();
1614 	if (value) {
1615 		stdoutput.printf("%s\n",value);
1616 	} else if (sqlrcon->errorMessage()) {
1617 		displayError(env,NULL,
1618 				sqlrcon->errorMessage(),
1619 				sqlrcon->errorNumber());
1620 		return false;
1621 	} else {
1622 		stdoutput.printf("\n");
1623 	}
1624 	return true;
1625 }
1626 
clientversion(sqlrconnection * sqlrcon,sqlrshenv * env)1627 void sqlrsh::clientversion(sqlrconnection *sqlrcon, sqlrshenv *env) {
1628 	stdoutput.printf("%s\n",sqlrcon->clientVersion());
1629 }
1630 
serverversion(sqlrconnection * sqlrcon,sqlrshenv * env)1631 bool sqlrsh::serverversion(sqlrconnection *sqlrcon, sqlrshenv *env) {
1632 	const char	*value=sqlrcon->serverVersion();
1633 	if (value) {
1634 		stdoutput.printf("%s\n",value);
1635 	} else if (sqlrcon->errorMessage()) {
1636 		displayError(env,NULL,
1637 				sqlrcon->errorMessage(),
1638 				sqlrcon->errorNumber());
1639 		return false;
1640 	} else {
1641 		stdoutput.printf("\n");
1642 	}
1643 	return true;
1644 }
1645 
inputbind(sqlrcursor * sqlrcur,sqlrshenv * env,const char * command)1646 bool sqlrsh::inputbind(sqlrcursor *sqlrcur,
1647 				sqlrshenv *env, const char *command) {
1648 
1649 	// sanity check
1650 	const char	*ptr=command+10;
1651 	const char	*space=charstring::findFirst(ptr,' ');
1652 	if (!space) {
1653 		stderror.printf("usage: inputbind [variable] = [value]\n");
1654 		return false;
1655 	}
1656 
1657 	// get the variable name
1658 	char	*variable=charstring::duplicate(ptr,space-ptr);
1659 
1660 	// move on
1661 	ptr=space;
1662 	if (*(ptr+1)=='=' && *(ptr+2)==' ') {
1663 		ptr=ptr+3;
1664 	} else if (!charstring::compareIgnoringCase(ptr+1,"is null")) {
1665 		ptr=NULL;
1666 	} else {
1667 		stderror.printf("usage: inputbind [variable] = [value]\n");
1668 		stderror.printf("       inputbind [variable] is null\n");
1669 		return false;
1670 	}
1671 
1672 	// get the value
1673 	char	*value=charstring::duplicate(ptr);
1674 	charstring::bothTrim(value);
1675 	size_t	valuelen=charstring::length(value);
1676 
1677 	// if the bind variable is already defined, clear it...
1678 	sqlrshbindvalue	*bv=NULL;
1679 	if (env->inputbinds.getValue(variable,&bv)) {
1680 		if (bv->type==SQLRCLIENTBINDVARTYPE_STRING) {
1681 			delete[] bv->stringval;
1682 		}
1683 		delete bv;
1684 	}
1685 
1686 	// define the variable
1687 	bv=new sqlrshbindvalue;
1688 
1689 	// first handle nulls, then...
1690 	// anything enclosed in quotes is a string
1691 	// if it's unquoted, check to see if it's an integer, float or date
1692 	// if it's not, then it's a string
1693 	if (!value) {
1694 		bv->type=SQLRCLIENTBINDVARTYPE_NULL;
1695 	} else if ((value[0]=='\'' && value[valuelen-1]=='\'') ||
1696 			(value[0]=='"' && value[valuelen-1]=='"')) {
1697 
1698 		bv->type=SQLRCLIENTBINDVARTYPE_STRING;
1699 
1700 		// trim off quotes
1701 		char	*newvalue=charstring::duplicate(value+1);
1702 		newvalue[valuelen-2]='\0';
1703 		delete[] value;
1704 
1705 		// unescape the string
1706 		bv->stringval=charstring::unescape(newvalue);
1707 		delete[] newvalue;
1708 
1709 	} else if (charstring::contains(value,"/") &&
1710 			charstring::contains(value,":")) {
1711 		int16_t	year;
1712 		int16_t	month;
1713 		int16_t	day;
1714 		int16_t	hour;
1715 		int16_t	minute;
1716 		int16_t	second;
1717 		int32_t	microsecond;
1718 		bool	isnegative;
1719 		parseDateTime(value,false,false,"/",
1720 					&year,&month,&day,
1721 					&hour,&minute,&second,
1722 					&microsecond,&isnegative);
1723 		bv->type=SQLRCLIENTBINDVARTYPE_DATE;
1724 		bv->dateval.year=year;
1725 		bv->dateval.month=month;
1726 		bv->dateval.day=day;
1727 		bv->dateval.hour=hour;
1728 		bv->dateval.minute=minute;
1729 		bv->dateval.second=second;
1730 		bv->dateval.microsecond=microsecond;
1731 		bv->dateval.tz="";
1732 		bv->dateval.isnegative=isnegative;
1733 		delete[] value;
1734 	} else if (charstring::isInteger(value)) {
1735 		bv->type=SQLRCLIENTBINDVARTYPE_INTEGER;
1736 		bv->integerval=charstring::toInteger(value);
1737 		delete[] value;
1738 	} else if (charstring::isNumber(value)) {
1739 		bv->type=SQLRCLIENTBINDVARTYPE_DOUBLE;
1740 		bv->doubleval.value=charstring::toFloatC(value);
1741 		bv->doubleval.precision=valuelen-((value[0]=='-')?2:1);
1742 		bv->doubleval.scale=
1743 			charstring::findFirst(value,'.')-value+
1744 			((value[0]=='-')?0:1);
1745 		delete[] value;
1746 	} else {
1747 		bv->type=SQLRCLIENTBINDVARTYPE_STRING;
1748 		bv->stringval=value;
1749 	}
1750 
1751 	// put the bind variable in the list
1752 	env->inputbinds.setValue(variable,bv);
1753 
1754 	return true;
1755 }
1756 
inputbindblob(sqlrcursor * sqlrcur,sqlrshenv * env,const char * command)1757 bool sqlrsh::inputbindblob(sqlrcursor *sqlrcur,
1758 				sqlrshenv *env, const char *command) {
1759 
1760 	// sanity check
1761 	const char	*ptr=command+14;
1762 	const char	*space=charstring::findFirst(ptr,' ');
1763 	if (!space) {
1764 		stderror.printf("usage: inputbindblob [variable] = [value]\n");
1765 		return false;
1766 	}
1767 
1768 	// get the variable name
1769 	char	*variable=charstring::duplicate(ptr,space-ptr);
1770 
1771 	// move on
1772 	ptr=space;
1773 	if (*(ptr+1)=='=' && *(ptr+2)==' ') {
1774 		ptr=ptr+3;
1775 	} else if (!charstring::compareIgnoringCase(ptr+1,"is null")) {
1776 		ptr=NULL;
1777 	} else {
1778 		stderror.printf("usage: inputbindblob [variable] = [value]\n");
1779 		stderror.printf("       inputbindblob [variable] is null\n");
1780 		return false;
1781 	}
1782 
1783 	// get the value
1784 	char	*value=charstring::duplicate(ptr);
1785 	charstring::bothTrim(value);
1786 	size_t	valuelen=charstring::length(value);
1787 
1788 	// if the bind variable is already defined, clear it...
1789 	sqlrshbindvalue	*bv=NULL;
1790 	if (env->inputbinds.getValue(variable,&bv)) {
1791 		if (bv->type==SQLRCLIENTBINDVARTYPE_STRING) {
1792 			delete[] bv->stringval;
1793 		}
1794 		delete bv;
1795 	}
1796 
1797 	// define the variable
1798 	bv=new sqlrshbindvalue;
1799 
1800 	// first handle nulls, then...
1801 	// anything enclosed in quotes is a string
1802 	// if it's unquoted, check to see if it's an integer, float or date
1803 	// if it's not, then it's a string
1804 	if (!value) {
1805 		bv->type=SQLRCLIENTBINDVARTYPE_NULL;
1806 	} else if ((value[0]=='\'' && value[valuelen-1]=='\'') ||
1807 			(value[0]=='"' && value[valuelen-1]=='"')) {
1808 
1809 		bv->type=SQLRCLIENTBINDVARTYPE_BLOB;
1810 
1811 		// trim off quotes
1812 		char	*newvalue=charstring::duplicate(value+1);
1813 		newvalue[valuelen-2]='\0';
1814 		delete[] value;
1815 
1816 		// unescape the string
1817 		bv->stringval=charstring::unescape(newvalue);
1818 		delete[] newvalue;
1819 
1820 	} else {
1821 		bv->type=SQLRCLIENTBINDVARTYPE_BLOB;
1822 		bv->stringval=value;
1823 	}
1824 
1825 	// put the bind variable in the list
1826 	env->inputbinds.setValue(variable,bv);
1827 
1828 	return true;
1829 }
1830 
outputbind(sqlrcursor * sqlrcur,sqlrshenv * env,const char * command)1831 bool sqlrsh::outputbind(sqlrcursor *sqlrcur,
1832 				sqlrshenv *env, const char *command) {
1833 
1834 	// split the command on ' '
1835 	char		**parts;
1836 	uint64_t	partcount;
1837 	charstring::split(command," ",true,&parts,&partcount);
1838 
1839 	// sanity check...
1840 	bool	sane=true;
1841 	if (partcount>2 && !charstring::compare(parts[0],"outputbind")) {
1842 
1843 		// if the bind variable is already defined, clear it...
1844 		sqlrshbindvalue	*bv=NULL;
1845 		if (env->outputbinds.getValue(parts[1],&bv)) {
1846 			if (bv->type==SQLRCLIENTBINDVARTYPE_STRING) {
1847 				delete[] bv->stringval;
1848 			}
1849 			delete bv;
1850 		}
1851 
1852 		// define the variable
1853 		bv=new sqlrshbindvalue;
1854 
1855 		if (!charstring::compareIgnoringCase(
1856 						parts[2],"string") &&
1857 						partcount==4) {
1858 			bv->type=SQLRCLIENTBINDVARTYPE_STRING;
1859 			bv->stringval=NULL;
1860 			bv->outputstringbindlength=
1861 				charstring::toInteger(parts[3]);
1862 		} else if (!charstring::compareIgnoringCase(
1863 						parts[2],"integer") &&
1864 						partcount==3) {
1865 			bv->type=SQLRCLIENTBINDVARTYPE_INTEGER;
1866 			bv->integerval=0;
1867 		} else if (!charstring::compareIgnoringCase(
1868 						parts[2],"double") &&
1869 						partcount==5) {
1870 			bv->type=SQLRCLIENTBINDVARTYPE_DOUBLE;
1871 			bv->doubleval.value=0.0;
1872 			bv->doubleval.precision=
1873 				charstring::toInteger(parts[3]);
1874 			bv->doubleval.scale=
1875 				charstring::toInteger(parts[4]);
1876 		} else if (!charstring::compareIgnoringCase(
1877 						parts[2],"date") &&
1878 						partcount==3) {
1879 			bv->type=SQLRCLIENTBINDVARTYPE_DATE;
1880 			bv->dateval.year=0;
1881 			bv->dateval.month=0;
1882 			bv->dateval.day=0;
1883 			bv->dateval.hour=0;
1884 			bv->dateval.minute=0;
1885 			bv->dateval.second=0;
1886 			bv->dateval.microsecond=0;
1887 			bv->dateval.tz="";
1888 			bv->dateval.isnegative=false;
1889 		} else {
1890 			sane=false;
1891 		}
1892 
1893 		// put the bind variable in the list
1894 		if (sane) {
1895 			env->outputbinds.setValue(parts[1],bv);
1896 		}
1897 
1898 	} else {
1899 		sane=false;
1900 	}
1901 
1902 	// clean up
1903 	if (sane) {
1904 		delete[] parts[0];
1905 	} else {
1906 		stderror.printf("usage: outputbind "
1907 				// FIXME: not entirely accurate
1908 				"[variable] [type] [length] [scale]\n");
1909 		for (uint64_t i=0; i<partcount; i++) {
1910 			delete[] parts[i];
1911 		}
1912 	}
1913 	delete[] parts;
1914 
1915 	return sane;
1916 }
1917 
inputoutputbind(sqlrcursor * sqlrcur,sqlrshenv * env,const char * command)1918 bool sqlrsh::inputoutputbind(sqlrcursor *sqlrcur,
1919 				sqlrshenv *env, const char *command) {
1920 
1921 	// get the value
1922 	char		*value=NULL;
1923 	const char	*equals=charstring::findFirst(command,'=');
1924 	if (equals) {
1925 		value=charstring::duplicate(equals+1);
1926 		charstring::bothTrim(value);
1927 		charstring::bothTrim(value,'\'');
1928 	} else if (charstring::compare(
1929 			command+charstring::length(command)-8," is null")) {
1930 		// FIXME: usage...
1931 		return false;
1932 	}
1933 
1934 	// split the command on ' '
1935 	char		**parts;
1936 	uint64_t	partcount;
1937 	charstring::split(command," ",true,&parts,&partcount);
1938 
1939 	// sanity check...
1940 	bool	sane=true;
1941 	if (partcount>=5 && !charstring::compare(parts[0],"inputoutputbind")) {
1942 
1943 		// if the bind variable is already defined, clear it...
1944 		sqlrshbindvalue	*bv=NULL;
1945 		if (env->inputoutputbinds.getValue(parts[1],&bv)) {
1946 			if (bv->type==SQLRCLIENTBINDVARTYPE_STRING) {
1947 				delete[] bv->stringval;
1948 			}
1949 			delete bv;
1950 		}
1951 
1952 		// define the variable
1953 		bv=new sqlrshbindvalue;
1954 
1955 		if (!charstring::compareIgnoringCase(
1956 						parts[2],"string") &&
1957 						partcount>=6) {
1958 			// inputoutputbind 1 string length = 'string'
1959 			bv->type=SQLRCLIENTBINDVARTYPE_STRING;
1960 			bv->outputstringbindlength=
1961 				charstring::toInteger(parts[3]);
1962 			bv->stringval=charstring::unescape(value);
1963 		} else if (!charstring::compareIgnoringCase(
1964 						parts[2],"integer") &&
1965 						partcount==5) {
1966 			// inputoutputbind 1 integer = value
1967 			bv->type=SQLRCLIENTBINDVARTYPE_INTEGER;
1968 			bv->integerval=charstring::toInteger(value);
1969 		} else if (!charstring::compareIgnoringCase(
1970 						parts[2],"double") &&
1971 						partcount==7) {
1972 			// inputoutputbind 1 double prec scale = value
1973 			bv->type=SQLRCLIENTBINDVARTYPE_DOUBLE;
1974 			bv->doubleval.value=charstring::toFloatC(value);
1975 			bv->doubleval.precision=
1976 				charstring::toInteger(parts[3]);
1977 			bv->doubleval.scale=
1978 				charstring::toInteger(parts[4]);
1979 		} else if (!charstring::compareIgnoringCase(
1980 						parts[2],"date") &&
1981 						partcount>=5) {
1982 			// inputoutputbind 1 date = '...'
1983 			int16_t	year;
1984 			int16_t	month;
1985 			int16_t	day;
1986 			int16_t	hour;
1987 			int16_t	minute;
1988 			int16_t	second;
1989 			int32_t	microsecond;
1990 			bool	isnegative;
1991 			parseDateTime(value,false,false,"/",
1992 						&year,&month,&day,
1993 						&hour,&minute,&second,
1994 						&microsecond,&isnegative);
1995 			bv->type=SQLRCLIENTBINDVARTYPE_DATE;
1996 			bv->dateval.year=year;
1997 			bv->dateval.month=month;
1998 			bv->dateval.day=day;
1999 			bv->dateval.hour=hour;
2000 			bv->dateval.minute=minute;
2001 			bv->dateval.second=second;
2002 			bv->dateval.microsecond=microsecond;
2003 			bv->dateval.tz="";
2004 			bv->dateval.isnegative=isnegative;
2005 		} else {
2006 			sane=false;
2007 		}
2008 
2009 		// put the bind variable in the list
2010 		if (sane) {
2011 			env->inputoutputbinds.setValue(parts[1],bv);
2012 		}
2013 
2014 	} else {
2015 		sane=false;
2016 	}
2017 
2018 	// clean up
2019 	if (sane) {
2020 		delete[] parts[0];
2021 	} else {
2022 		stderror.printf("usage: inputoutputbind "
2023 				// FIXME: not entirely accurate
2024 				"[variable] [type] [length] [scale]\n");
2025 		for (uint64_t i=0; i<partcount; i++) {
2026 			delete[] parts[i];
2027 		}
2028 	}
2029 	delete[] parts;
2030 	delete[] value;
2031 
2032 	return sane;
2033 }
2034 
printbinds(const char * type,dictionary<char *,sqlrshbindvalue * > * binds)2035 void sqlrsh::printbinds(const char *type,
2036 			dictionary<char *, sqlrshbindvalue *> *binds) {
2037 
2038 	stdoutput.printf("%s bind variables:\n",type);
2039 
2040 	for (linkedlistnode<dictionarynode<char *, sqlrshbindvalue *> *>
2041 					*node=binds->getList()->getFirst();
2042 		node; node=node->getNext()) {
2043 
2044 		stdoutput.printf("    %s ",node->getValue()->getKey());
2045 		sqlrshbindvalue	*bv=node->getValue()->getValue();
2046 		if (bv->type==SQLRCLIENTBINDVARTYPE_STRING) {
2047 			stdoutput.printf("(STRING) = %s\n",bv->stringval);
2048 		} else if (bv->type==SQLRCLIENTBINDVARTYPE_INTEGER) {
2049 			stdoutput.printf("(INTEGER) = %lld\n",
2050 						(long long)bv->integerval);
2051 		} else if (bv->type==SQLRCLIENTBINDVARTYPE_DOUBLE) {
2052 			stdoutput.printf("(DOUBLE %d,%d) = %*.*f\n",
2053 						bv->doubleval.precision,
2054 						bv->doubleval.scale,
2055 						(int)bv->doubleval.precision,
2056 						(int)bv->doubleval.scale,
2057 						bv->doubleval.value);
2058 		} else if (bv->type==SQLRCLIENTBINDVARTYPE_DATE) {
2059 			stdoutput.printf("(DATE) = %02d/%02d/%04d "
2060 						"%s%02d:%02d:%02d.%06d %s\n",
2061 						bv->dateval.month,
2062 						bv->dateval.day,
2063 						bv->dateval.year,
2064 						(bv->dateval.isnegative)?"-":"",
2065 						bv->dateval.hour,
2066 						bv->dateval.minute,
2067 						bv->dateval.second,
2068 						bv->dateval.microsecond,
2069 						bv->dateval.tz);
2070 		} else if (bv->type==SQLRCLIENTBINDVARTYPE_BLOB) {
2071 			stdoutput.printf("(BLOB) = ");
2072 			stdoutput.safePrint(bv->stringval,
2073 					charstring::length(bv->stringval));
2074 			stdoutput.printf("\n");
2075 		} else if (bv->type==SQLRCLIENTBINDVARTYPE_NULL) {
2076 			stdoutput.printf("NULL\n");
2077 		}
2078 	}
2079 }
2080 
setclientinfo(sqlrconnection * sqlrcon,const char * command)2081 void sqlrsh::setclientinfo(sqlrconnection *sqlrcon, const char *command) {
2082 	sqlrcon->setClientInfo(command+14);
2083 }
2084 
getclientinfo(sqlrconnection * sqlrcon)2085 void sqlrsh::getclientinfo(sqlrconnection *sqlrcon) {
2086 	const char	*ci=sqlrcon->getClientInfo();
2087 	stdoutput.printf("%s\n",(ci)?ci:"");
2088 }
2089 
responseTimeout(sqlrconnection * sqlrcon,const char * command)2090 void sqlrsh::responseTimeout(sqlrconnection *sqlrcon, const char *command) {
2091 
2092 	// skip to timeout itself
2093 	const char	*value=command+16;
2094 	while (character::isWhitespace(*value)) {
2095 		value++;
2096 	}
2097 
2098 	// get seconds
2099 	uint32_t	sec=charstring::toInteger(value);
2100 
2101 	// get milliseconds
2102 	char	msecbuf[5];
2103 	bytestring::set(msecbuf,'0',4);
2104 	msecbuf[4]='\0';
2105 	const char	*dot=charstring::findFirst(value,'.');
2106 	if (dot) {
2107 		value=dot+1;
2108 		for (uint8_t i=0; i<4 && *value; i++) {
2109 			msecbuf[i]=*value;
2110 			value++;
2111 		}
2112 	}
2113 	uint32_t	msec=charstring::toInteger(msecbuf);
2114 
2115 	// set timeout
2116 	sqlrcon->setResponseTimeout(sec,msec);
2117 	stdoutput.printf("Response Timeout set to %d.%04d seconds\n",sec,msec);
2118 }
2119 
cache(sqlrshenv * env,sqlrcursor * sqlrcur,const char * command)2120 bool sqlrsh::cache(sqlrshenv *env, sqlrcursor *sqlrcur, const char *command) {
2121 
2122 	// move to file name
2123 	const char	*ptr=command+6;
2124 
2125 	// skip whitespace
2126 	while (*ptr==' ') {
2127 		ptr++;
2128 	}
2129 
2130 	// bail if no file name was given
2131 	if (!*ptr) {
2132 		stderror.printf("	No file name given\n\n");
2133 		return false;
2134 	}
2135 
2136 	// build filename
2137 	stringbuffer	fn;
2138 	fn.append(sqlrpth->getCacheDir());
2139 	bool	inquotes=false;
2140 	while (*ptr) {
2141 		if (*ptr=='"') {
2142 			inquotes=!inquotes;
2143 		}
2144 		if (*ptr==' ' && !inquotes) {
2145 			break;
2146 		}
2147 		fn.append(*ptr);
2148 		ptr++;
2149 	}
2150 	delete[] env->cacheto;
2151 	env->cacheto=fn.detachString();
2152 
2153 	// find ttl
2154 	while (*ptr==' ') {
2155 		ptr++;
2156 	}
2157 	uint32_t	cachettl=600;
2158 	if (*ptr) {
2159 		cachettl=charstring::toInteger(ptr);
2160 	}
2161 
2162 	stdoutput.printf("	Caching To       : %s\n",env->cacheto);
2163 	stdoutput.printf("	Cache TTL Set To : %lld seconds\n\n",cachettl);
2164 
2165 	// begin caching
2166 	sqlrcur->cacheToFile(env->cacheto);
2167 	sqlrcur->setCacheTtl(cachettl);
2168 
2169 	return true;
2170 }
2171 
openCache(sqlrshenv * env,sqlrcursor * sqlrcur,const char * command)2172 bool sqlrsh::openCache(sqlrshenv *env,
2173 			sqlrcursor *sqlrcur, const char *command) {
2174 
2175 	// move to file name
2176 	command=command+10;
2177 
2178 	// skip whitespace
2179 	while (*command==' ') {
2180 		command++;
2181 	}
2182 
2183 	// bail if no file name was given
2184 	if (!*command) {
2185 		stderror.printf("	No file name given\n\n");
2186 		return false;
2187 	}
2188 
2189 	// if the file name starts with a slash then use it as-is, otherwise
2190 	// prepend the default cache directory.
2191 	stringbuffer	fn;
2192 	fn.append(sqlrpth->getCacheDir())->append(command);
2193 
2194 	// open the cached result set
2195 	if (!sqlrcur->openCachedResultSet(fn.getString())) {
2196 		stderror.printf("	Cannot open cache file\n\n");
2197 		return false;
2198 	}
2199 
2200 	// display the header
2201 	displayHeader(sqlrcur,env);
2202 
2203 	// display the result set
2204 	displayResultSet(sqlrcur,env);
2205 
2206 	// display statistics
2207 	displayStats(sqlrcur,env);
2208 
2209 	return true;
2210 }
2211 
displayHelp(sqlrshenv * env)2212 void sqlrsh::displayHelp(sqlrshenv *env) {
2213 
2214 	stdoutput.printf("\n");
2215 	stdoutput.printf("	To run a query, simply type it at the prompt,\n"
2216 			"	followed by a semicolon.  Queries may be \n"
2217 			"	split over multiple lines.\n\n");
2218 	stdoutput.printf("	ping			- ");
2219 	stdoutput.printf("pings the database\n");
2220 	stdoutput.printf("	identify		- ");
2221 	stdoutput.printf("returns the type of database\n");
2222 	stdoutput.printf("	dbversion		- ");
2223 	stdoutput.printf("returns the version of the database\n");
2224 	stdoutput.printf("	dbhostname		- ");
2225 	stdoutput.printf("returns the host name of the database\n");
2226 	stdoutput.printf("	dbipaddress		- ");
2227 	stdoutput.printf("returns the ip address of the database\n");
2228 	stdoutput.printf("	clientversion		- ");
2229 	stdoutput.printf("returns the version of the client library\n");
2230 	stdoutput.printf("	serverversion		- ");
2231 	stdoutput.printf("returns the version of the server\n");
2232 	stdoutput.printf("	use [database]		- ");
2233 	stdoutput.printf("change the current database/schema\n");
2234 	stdoutput.printf("	currentdb		- ");
2235 	stdoutput.printf("shows the current database/schema\n");
2236 	stdoutput.printf("	run script		- ");
2237 	stdoutput.printf("runs commands contained in file \"script\"\n");
2238 	stdoutput.printf("	headers on|off		- ");
2239 	stdoutput.printf("toggles column descriptions before result set\n");
2240 	stdoutput.printf("	divider on|off		- ");
2241 	stdoutput.printf("toggles the divider before the result set\n");
2242 	stdoutput.printf("	stats on|off		- ");
2243 	stdoutput.printf("toggles statistics after result set\n");
2244 	stdoutput.printf("	format plain|csv	- ");
2245 	stdoutput.printf("sets output format to plain or csv\n");
2246 	stdoutput.printf("	debug on|off		- ");
2247 	stdoutput.printf("toggles debug messages\n");
2248 	stdoutput.printf("	nullsasnulls on|off	- ");
2249 	stdoutput.printf("toggles getting nulls as nulls\n"
2250 			"					"
2251 			"(rather than as empty strings)\n");
2252 	stdoutput.printf("	autocommit on|off	- ");
2253 	stdoutput.printf("toggles autocommit\n");
2254 	stdoutput.printf("	final on|off		- ");
2255 	stdoutput.printf("toggles use of one session per query\n");
2256 	stdoutput.printf("	delimiter [character]	- ");
2257 	stdoutput.printf("sets delimiter character to [character]\n\n");
2258 	stdoutput.printf("	response timeout [sec.msec]   - ");
2259 	stdoutput.printf("sets response timeout to [sec.msec]\n\n");
2260 	stdoutput.printf("	inputbind ...                 - ");
2261 	stdoutput.printf("defines an input bind variable\n");
2262 	stdoutput.printf("		inputbind [variable] is null\n");
2263 	stdoutput.printf("		inputbind [variable] = [stringvalue]\n");
2264 	stdoutput.printf("		inputbind [variable] = [integervalue]\n");
2265 	stdoutput.printf("		inputbind [variable] = [doublevalue]\n");
2266 	stdoutput.printf("		inputbind [variable] = [MM/DD/YYYY HH:MM:SS:uS TZN]\n");
2267 	stdoutput.printf("		inputbindblob [variable] = [value]\n");
2268 	stdoutput.printf("	outputbind ...                 - ");
2269 	stdoutput.printf("defines an output bind variable\n");
2270 	stdoutput.printf("		outputbind [variable] string [length]\n");
2271 	stdoutput.printf("		outputbind [variable] integer\n");
2272 	stdoutput.printf("		outputbind [variable] double [precision] [scale}\n");
2273 	stdoutput.printf("		outputbind [variable] date\n");
2274 	stdoutput.printf("	printbinds                     - ");
2275 	stdoutput.printf("prints all bind variables\n");
2276 	stdoutput.printf("	clearinputbind [variable]      - ");
2277 	stdoutput.printf("clears an input bind variable\n");
2278 	stdoutput.printf("	clearoutputbind [variable]     - ");
2279 	stdoutput.printf("clears an output bind variable\n");
2280 	stdoutput.printf("	clearbinds                     - ");
2281 	stdoutput.printf("clears all bind variables\n");
2282 	stdoutput.printf("	reexecute                      - ");
2283 	stdoutput.printf("reexecutes the previous query\n\n");
2284 	stdoutput.printf("	lastinsertid                   - ");
2285 	stdoutput.printf("returns the value of the most recently\n");
2286 	stdoutput.printf("\t\t\t\t\t updated auto-increment or identity\n");
2287 	stdoutput.printf("\t\t\t\t\t column, if the database supports it\n\n");
2288 	stdoutput.printf("	show databases [like pattern]		-\n");
2289 	stdoutput.printf("		returns a list of known databases/schemas\n");
2290 	stdoutput.printf("	show tables [like pattern]		-\n");
2291 	stdoutput.printf("		returns a list of known tables\n");
2292 	stdoutput.printf("	show columns in table [like pattern]	-\n");
2293 	stdoutput.printf("		returns a list of column metadata for the table \"table\"\n");
2294 	stdoutput.printf("	describe table				-\n");
2295 	stdoutput.printf("		returns a list of column metadata for the table \"table\"\n");
2296 	stdoutput.printf("	fields table				-\n");
2297 	stdoutput.printf("		returns a list of column names for the table \"table\"\n\n");
2298 	stdoutput.printf("	setclientinfo info	- sets the client info\n");
2299 	stdoutput.printf("	getclientinfo		- displays the client info\n\n");
2300 	stdoutput.printf("	setresultsetbuffersize size	- fetch size rows at a time\n");
2301 	stdoutput.printf("	getresultsetbuffersize 		- shows rows fetched at a time\n\n");
2302 	stdoutput.printf("	endsession		- ends the current session\n\n");
2303 	stdoutput.printf("	cache [filename] [ttl]	- caches the next result set to \"filename\"\n	                      	  with ttl of \"ttl\"\n");
2304 	stdoutput.printf("	opencache [filename] 	- opens and displays cached result set \n				  in \"filename\"\n\n");
2305 	stdoutput.printf("	exit/quit		- ");
2306 	stdoutput.printf("exits\n\n");
2307 	stdoutput.printf("	All commands must be followed by the delimiter: %c\n",
2308 								env->delimiter);
2309 }
2310 
startupMessage(sqlrshenv * env,const char * host,uint16_t port,const char * user)2311 void sqlrsh::startupMessage(sqlrshenv *env, const char *host,
2312 					uint16_t port, const char *user) {
2313 
2314 	stdoutput.printf("%ssh - ",SQLR);
2315 	stdoutput.printf("Version %s\n",SQLR_VERSION);
2316 	stdoutput.printf("	Connected to: ");
2317 	stdoutput.printf("%s:%d as %s\n\n",host,port,user);
2318 	stdoutput.printf("	type help; for help.\n\n");
2319 }
2320 
interactWithUser(sqlrconnection * sqlrcon,sqlrcursor * sqlrcur,sqlrshenv * env)2321 void sqlrsh::interactWithUser(sqlrconnection *sqlrcon, sqlrcursor *sqlrcur,
2322 							sqlrshenv *env) {
2323 
2324 	// init some variables
2325 	stringbuffer	command;
2326 	stringbuffer	prmpt;
2327 	bool		exitprogram=false;
2328 	uint32_t	promptcount;
2329 
2330 	// Blocking mode is apparently not the default on some systems
2331 	// (Syllable for sure, maybe others) and this causes hilariously
2332 	// odd behavior when reading standard input.
2333 	stdinput.useBlockingMode();
2334 
2335 	while (!exitprogram) {
2336 
2337 		// prompt the user
2338 		promptcount=0;
2339 
2340 		// get the command
2341 		bool	done=false;
2342 		while (!done) {
2343 
2344 			prmpt.append(promptcount);
2345 			prmpt.append("> ");
2346 			pr.setPrompt(prmpt.getString());
2347 			prmpt.clear();
2348 
2349 			char	*cmd=pr.read();
2350 
2351 			// cmd is NULL if you hit ctrl-D
2352 			if (!cmd) {
2353 				return;
2354 			}
2355 
2356 			size_t	len=charstring::length(cmd);
2357 
2358 			// len=0 and cmd="" if you just hit return
2359 			if (len) {
2360 				command.append(cmd);
2361 				done=(cmd[len-1]==env->delimiter);
2362 			}
2363 
2364 			if (!done) {
2365 				promptcount++;
2366 				command.append('\n');
2367 			}
2368 		}
2369 
2370 		char	*cmd=command.detachString();
2371 
2372 		// run the command
2373 		runCommands(sqlrcon,sqlrcur,env,cmd,&exitprogram);
2374 
2375 		// clean up
2376 		delete[] cmd;
2377 	}
2378 }
2379 
execute(int argc,const char ** argv)2380 bool sqlrsh::execute(int argc, const char **argv) {
2381 
2382 	cmdline=new sqlrcmdline(argc,argv);
2383 	sqlrpth=new sqlrpaths(cmdline);
2384 	sqlrconfigs	sqlrcfgs(sqlrpth);
2385 
2386 	// get command-line options
2387 	const char	*configurl=sqlrpth->getConfigUrl();
2388 	const char	*id=cmdline->getValue("id");
2389 	const char	*host=cmdline->getValue("host");
2390 	uint16_t	port=charstring::toInteger(
2391 				(cmdline->found("port"))?
2392 				cmdline->getValue("port"):DEFAULT_PORT);
2393 	const char	*socket=cmdline->getValue("socket");
2394 	const char	*user=cmdline->getValue("user");
2395 	const char	*password=cmdline->getValue("password");
2396 	bool		usekrb=cmdline->found("krb");
2397 	const char	*krbservice=cmdline->getValue("krbservice");
2398 	const char	*krbmech=cmdline->getValue("krbmech");
2399 	const char	*krbflags=cmdline->getValue("krbflags");
2400 	bool		usetls=cmdline->found("tls");
2401 	const char	*tlsversion=cmdline->getValue("tlsversion");
2402 	const char	*tlscert=cmdline->getValue("tlscert");
2403 	const char	*tlspassword=cmdline->getValue("tlspassword");
2404 	const char	*tlsciphers=cmdline->getValue("tlsciphers");
2405 	const char	*tlsvalidate="no";
2406 	if (cmdline->found("tlsvalidate")) {
2407 		tlsvalidate=cmdline->getValue("tlsvalidate");
2408 	}
2409 	const char	*tlsca=cmdline->getValue("tlsca");
2410 	uint16_t	tlsdepth=charstring::toUnsignedInteger(
2411 					cmdline->getValue("tlsdepth"));
2412 	const char	*localeargument=cmdline->getValue("locale");
2413 	const char	*script=cmdline->getValue("script");
2414 	const char	*command=cmdline->getValue("command");
2415 
2416 	// at least id, host, or socket is required
2417 	if (charstring::isNullOrEmpty(id) &&
2418 		charstring::isNullOrEmpty(host) &&
2419 		charstring::isNullOrEmpty(socket)) {
2420 
2421 		stderror.printf("usage:\n"
2422 			" %ssh -host host -port port -socket socket\n"
2423 			"        [-user user] [-password password]\n"
2424 			"        [-krb] [-krbservice svc] [-krbmech mech] "
2425 			"[-krbflags flags]\n"
2426 			"        [-tls] [-tlsversion version]\n"
2427 			"        [-tlscert certfile] [-tlspassword password]\n"
2428 			"        [-tlsciphers cipherlist]\n"
2429 			"        [-tlsvalidate (no|ca|ca+domain|ca+host)] "
2430 			"[-tlsca ca] [-tlsdepth depth]\n"
2431 			"        [-script script | -command command] [-quiet] "
2432 			"[-format (plain|csv)] [-locale (env|name)] "
2433 			"[-getasnumber] [-noelapsed] [-nextresultset]\n"
2434 			"        [-resultsetbuffersize rows]\n"
2435 			"  or\n"
2436 			" %ssh [-config config] -id id\n"
2437 			"        [-script script | -command command] [-quiet] "
2438 			"[-format (plain|csv)] [-locale (env|name)] "
2439 			"[-getasnumber] [-noelapsed] [-nextresultset]\n"
2440 			"        [-resultsetbuffersize rows]\n",
2441 			SQLR,SQLR);
2442 		process::exit(1);
2443 	}
2444 
2445 	// if an id was specified, then get various values from the config file
2446 	if (!charstring::isNullOrEmpty(id)) {
2447 		sqlrconfig	*cfg=sqlrcfgs.load(configurl,id);
2448 		if (cfg) {
2449 			if (!cmdline->found("host")) {
2450 				host="localhost";
2451 			}
2452 			if (!cmdline->found("port")) {
2453 				port=cfg->getDefaultPort();
2454 			}
2455 			if (!cmdline->found("socket")) {
2456 				socket=cfg->getDefaultSocket();
2457 			}
2458 			if (!cmdline->found("krb")) {
2459 				usekrb=cfg->getDefaultKrb();
2460 			}
2461 			if (!cmdline->found("krbservice")) {
2462 				krbservice=cfg->getDefaultKrbService();
2463 			}
2464 			if (!cmdline->found("krbmech")) {
2465 				krbmech=cfg->getDefaultKrbMech();
2466 			}
2467 			if (!cmdline->found("krbflags")) {
2468 				krbflags=cfg->getDefaultKrbFlags();
2469 			}
2470 			if (!cmdline->found("tls")) {
2471 				usetls=cfg->getDefaultTls();
2472 			}
2473 			if (!cmdline->getValue("tlsciphers")) {
2474 				tlsciphers=cfg->getDefaultTlsCiphers();
2475 			}
2476 			if (!cmdline->found("user")) {
2477 				user=cfg->getDefaultUser();
2478 				password=cfg->getDefaultPassword();
2479 			}
2480 		}
2481 	}
2482 
2483 	if (!charstring::isNullOrEmpty(localeargument)) {
2484 		// This is useful for making sure that decimals still work
2485 		// when the locale is changed to say, de_DE that has different
2486 		// number formats.
2487 		char	*localeresult=setlocale(LC_ALL,
2488 				(!charstring::compare(localeargument,"env"))?
2489 							"":localeargument);
2490 		if (!localeresult) {
2491 			stderror.printf("ERROR: setlocale failed\n");
2492 			return false;
2493 		}
2494 	}
2495 
2496 	// configure sql relay connection
2497 	sqlrconnection	sqlrcon(host,port,socket,user,password,0,1);
2498 	sqlrcursor	sqlrcur(&sqlrcon);
2499 
2500 	// configure kerberos/tls
2501 	if (usekrb) {
2502 		sqlrcon.enableKerberos(krbservice,krbmech,krbflags);
2503 	} else if (usetls) {
2504 		sqlrcon.enableTls(tlsversion,tlscert,tlspassword,tlsciphers,
2505 						tlsvalidate,tlsca,tlsdepth);
2506 	}
2507 
2508 	// set up an sqlrshenv
2509 	sqlrshenv	env;
2510 
2511 	// handle quiet flag
2512 	if (cmdline->found("quiet")) {
2513 		env.headers=false;
2514 		env.stats=false;
2515 	}
2516 
2517 	// handle the result set format
2518 	if (!charstring::compare(cmdline->getValue("format"),"csv")) {
2519 		env.format=SQLRSH_FORMAT_CSV;
2520 	}
2521 
2522 	// handle the result set buffer size
2523 	if (cmdline->found("resultsetbuffersize")) {
2524 		env.rsbs=charstring::toInteger(
2525 				cmdline->getValue("resultsetbuffersize"));
2526 	}
2527 
2528 	// FIXME: make these commands instead of commandline args
2529 	env.getasnumber=cmdline->found("getasnumber");
2530 	env.noelapsed=cmdline->found("noelapsed");
2531 	env.nextresultset=cmdline->found("nextresultset");
2532 
2533 	// process RC files
2534 	userRcFile(&sqlrcon,&sqlrcur,&env);
2535 
2536 
2537 	// handle the history file
2538 	const char	*home=environment::getValue("HOME");
2539 	if (!charstring::isNullOrEmpty(home)) {
2540 		char	*filename=new char[charstring::length(home)+16+1];
2541 		charstring::copy(filename,home);
2542 		charstring::append(filename,"/.sqlrsh_history");
2543 		pr.setHistoryFile(filename);
2544 		pr.setMaxHistoryLines(100);
2545 	}
2546 
2547 	bool	retval=true;
2548 
2549 	if (!charstring::isNullOrEmpty(script)) {
2550 		// if a script was specified, run it
2551 		retval=runScript(&sqlrcon,&sqlrcur,&env,script,true);
2552 	} else if (!charstring::isNullOrEmpty(command)) {
2553 		// if a command was specified, run it
2554 		retval=runCommands(&sqlrcon,&sqlrcur,&env,command,NULL);
2555 	} else {
2556 		// otherwise go into interactive mode
2557 		startupMessage(&env,host,port,user);
2558 		interactWithUser(&sqlrcon,&sqlrcur,&env);
2559 	}
2560 
2561 	// clean up
2562 	pr.flushHistory();
2563 
2564 	return retval;
2565 }
2566 
helpmessage(const char * progname)2567 static void helpmessage(const char *progname) {
2568 	stdoutput.printf(
2569 		"%s is the %s command line database shell.\n"
2570 		"\n"
2571 		"It can be used interactively, or non-interactively to run queries directly from the command line, or scripts containing queries.\n"
2572 		"\n"
2573 		"Usage: %s [OPTIONS]\n"
2574 		"\n"
2575 		"Options:\n"
2576 		"\n"
2577 		CONNECTIONOPTIONS
2578 		"\n"
2579 		"Command options:\n"
2580 		"	-script filename	Run the specified script which contains	commands\n"
2581 		"				or queries that could otherwise be run at the\n"
2582 		"				%s prompt.\n"
2583 		"\n"
2584 		"	-command \"commands\"	Run the provided string which contains commands\n"
2585 		"				or queries that could otherwise be run at the\n"
2586 		"				%s prompt.\n"
2587 		"\n"
2588 		"	-quiet			Omit headers and stats in output.\n"
2589 		"\n"
2590 		"	-format plain|csv	Format the output as specified.\n"
2591 		"				Defaults to plain.\n"
2592 		"\n"
2593 		"	-locale env|locale_name	calls setlocale(LC_ALL, locale_name).\n"
2594 		"				env means use LC variables.\n"
2595 		"\n"
2596 		"	-getasnumber		calls getFieldAs(Integer|Double) as appropriate\n"
2597 		"\n"
2598 		"	-noelapsed		do not print elapsed time\n"
2599 		"\n"
2600 		"	-nextresultset		attempt to fetch multiple resultsets\n"
2601 		"\n"
2602 		"	-resultsetbuffersize rows\n"
2603 		"				Fetch result sets using the specified number of\n"
2604 		"				rows at once.\n"
2605 		"\n"
2606 		"Examples:\n"
2607 		"\n"
2608 		"Interactive session with server at svr:9000 as usr/pwd.\n"
2609 		"\n"
2610 		"	%s -host svr -port 9000 -user usr -password pwd\n"
2611 		"\n"
2612 		"Interactive session with local server on socket /tmp/svr.sock as usr/pwd.\n"
2613 		"\n"
2614 		"	%s -socket /tmp/svr.sock -user usr -password pwd\n"
2615 		"\n"
2616 		"Interactive session using connection info and credentials from an instance\n"
2617 		"defined in the default configuration.\n"
2618 		"	%s -id myinst\n"
2619 		"\n"
2620 		"Interactive session using connection info and credentials from an instance\n"
2621 		"defined in the config file ./myconfig.conf\n"
2622 		"\n"
2623 		"	%s -config ./myconfig.conf -id myinst\n"
2624 		"\n"
2625 		"Non-interactive session, running commands from ./script.sql\n"
2626 		"\n"
2627 		"	%s -id myinst -script ./script.sql\n"
2628 		"\n"
2629 		"Non-interactive session, running query \"select * from mytable\" with csv output.\n"
2630 		"\n"
2631 		"	%s -id myinst -command \"select * from mytable\" -quiet -format csv\n"
2632 		"\n",
2633 		progname,SQL_RELAY,progname,progname,progname,progname,
2634 		progname,progname,progname,progname,progname);
2635 }
2636 
main(int argc,const char ** argv)2637 int main(int argc, const char **argv) {
2638 
2639 	version(argc,argv);
2640 	help(argc,argv);
2641 
2642 	#ifdef SIGPIPE
2643 	// ignore SIGPIPE
2644 	signalset	set;
2645 	set.removeAllSignals();
2646 	set.addSignal(SIGPIPE);
2647 	signalmanager::ignoreSignals(&set);
2648 	#endif
2649 
2650 	int32_t	exitcode=0;
2651 	{
2652 		sqlrsh	s;
2653 		exitcode=!s.execute(argc,argv);
2654 	}
2655 	process::exit(exitcode);
2656 }
2657