1/*
2 * See the file LICENSE for redistribution information.
3 *
4 * Copyright (c) 1996-2009 Oracle.  All rights reserved.
5 *
6 */
7
8/*
9 * This compilation unit contains functions related to the generation
10 * of a simple smoke test for the generated storage layer.
11 */
12#include "generation.h"
13
14extern int maxbinsz; /* defined in buildpt.c */
15
16static FILE *test_file;   /* stream for generated test code */
17static int test_indent_level = 0;
18
19static void pr_test(char *, ...);
20static void pr_test_comment(char *, ...);
21static void callback_function_enter_entop(ENTITY *);
22static void callback_function_exit_entop(ENTITY *);
23static void callback_function_attrop(ENTITY *, ATTRIBUTE *, int, int);
24static void declare_record_instances_enter_entop(ENTITY *);
25static void initialize_database_enter_entop(ENTITY *);
26static void initialize_index_enter_entop(DB_INDEX *);
27static void insertion_test_enter_entop(ENTITY *);
28static void insertion_test_exit_entop(ENTITY *);
29static void insertion_test_attrop(ENTITY *, ATTRIBUTE *, int, int);
30static void retrieval_test_enter_entop(ENTITY *);
31static void retrieval_test_exit_entop(ENTITY *);
32static void retrieval_test_attrop(ENTITY *, ATTRIBUTE *, int, int);
33static void invoke_full_iteration_enter_entop(ENTITY *);
34static void invoke_full_iteration_exit_entop(ENTITY *e);
35static void invoke_query_iteration_idxop(DB_INDEX *);
36static void deletion_test_enter_entop(ENTITY *);
37static void deletion_test_exit_entop(ENTITY *e);
38static void close_secondary_test_idxop(DB_INDEX *);
39static void close_primary_test_enter_entop(ENTITY *);
40static void remove_secondary_test_idxop(DB_INDEX *);
41static void remove_primary_test_enter_entop(ENTITY *);
42
43/*
44 * Generate_test is the sole entry point in this module.  It
45 * orchestrates the generation of the C code for the smoke test.
46 */
47void
48generate_test(tfile, hfilename)
49	FILE *tfile;
50	char *hfilename;
51{
52	test_file = tfile;
53
54	pr_test_comment(
55"Simple test for a Berkeley DB implementation                               \n\
56generated from SQL DDL by db_sql                                            \n\
57");
58
59	pr_test("\n#include \"%s\"\n\n", hfilename);
60
61	if (maxbinsz != 0) {
62        	pr_test_comment("Test data for raw binary types");
63        	pr_test("#define MAXBINSZ %d\n", maxbinsz);
64        	pr_test("char binary_data[MAXBINSZ];\n\n");
65        	pr_test_comment("A very simple binary comparison function");
66		pr_test(
67"char * compare_binary(char *p, int len)                                    \n\
68{                                                                           \n\
69 if (memcmp(p, binary_data, len) == 0)                                      \n\
70  return \"*binary values match*\";                                         \n\
71 return \"*binary values don't match*\";                                    \n\
72}                                                                           \n\
73                                                                            \n\
74");
75	}
76
77	pr_test_comment(
78"These are the iteration callback functions.  One is defined per            \n\
79database(table).  They are used for both full iterations and for            \n\
80secondary index queries.  When a retrieval returns multiple records,        \n\
81as in full iteration over an entire database, one of these functions        \n\
82is called for each record found");
83
84	iterate_over_entities(&callback_function_enter_entop,
85	    &callback_function_exit_entop,
86	    &callback_function_attrop);
87
88	pr_test(
89"                                                                           \n\
90main(int argc, char **argv)                                                 \n\
91{                                                                           \n\
92 int i;                                                                     \n\
93 int ret; 								    \n\
94									    \n\
95");
96	test_indent_level++;
97	iterate_over_entities(&declare_record_instances_enter_entop,
98	    NULL,
99	    NULL);
100
101	iterate_over_entities(&initialize_database_enter_entop,
102	    NULL,
103	    NULL);
104
105	iterate_over_indexes(&initialize_index_enter_entop);
106
107	pr_test("\n");
108
109	if (maxbinsz != 0) {
110		pr_test_comment(
111                    "Fill the binary test data with random values");
112		pr_test(
113"for (i = 0; i < MAXBINSZ; i++) binary_data[i] = rand();\n\n");
114	}
115
116	pr_test_comment(
117"Use the convenience method to initialize the environment.                  \n\
118The initializations for each entity and environment can be                  \n\
119done discretely if you prefer, but this is the easy way.");
120	pr_test(
121"ret = initialize_%s_environment();                                         \n\
122if (ret != 0){                                                              \n\
123printf(\"Initialize error\");                                               \n\
124return ret;                                                                 \n\
125}                                                                           \n\
126                                                                            \n\
127",
128		    the_schema.environment.name);
129
130	pr_test_comment(
131"Now that everything is initialized, insert a single                        \n\
132record into each database, using the ...insert_fields                       \n\
133functions.  These functions take each field of the                          \n\
134record as a separate argument");
135
136	iterate_over_entities(&insertion_test_enter_entop,
137	    &insertion_test_exit_entop,
138	    &insertion_test_attrop);
139
140	pr_test("\n");
141	pr_test_comment(
142"Next, retrieve the records just inserted, looking them up                  \n\
143by their key values");
144
145	iterate_over_entities(&retrieval_test_enter_entop,
146	    &retrieval_test_exit_entop,
147	    &retrieval_test_attrop);
148
149	pr_test("\n");
150	pr_test_comment(
151"Now try iterating over every record, using the ...full_iteration           \n\
152functions for each database.  For each record found, the                    \n\
153appropriate ...iteration_callback_test function will be invoked             \n\
154(these are defined above).");
155
156	iterate_over_entities(&invoke_full_iteration_enter_entop,
157	    &invoke_full_iteration_exit_entop,
158	    NULL);
159
160	pr_test("\n");
161	pr_test_comment(
162"For the secondary indexes, query for the known keys.  This also            \n\
163results in the ...iteration_callback_test function's being called           \n\
164for each record found.");
165
166	iterate_over_indexes(&invoke_query_iteration_idxop);
167
168	pr_test("\n");
169	pr_test_comment(
170"Now delete a record from each database using its primary key.");
171
172	iterate_over_entities(&deletion_test_enter_entop,
173	    &deletion_test_exit_entop,
174	    NULL);
175
176	pr_test("\n");
177	test_indent_level--;
178        pr_test("exit_error:\n");
179        test_indent_level++;
180
181	pr_test_comment("Close the secondary index databases");
182	iterate_over_indexes(&close_secondary_test_idxop);
183
184	pr_test("\n");
185	pr_test_comment("Close the primary databases");
186	iterate_over_entities(&close_primary_test_enter_entop,
187	    NULL,
188	    NULL);
189
190	pr_test("\n");
191	pr_test_comment("Delete the secondary index databases");
192	iterate_over_indexes(&remove_secondary_test_idxop);
193
194	pr_test("\n");
195	pr_test_comment("Delete the primary databases");
196	iterate_over_entities(&remove_primary_test_enter_entop,
197	    NULL,
198	    NULL);
199
200	pr_test("\n");
201	pr_test_comment("Finally, close the environment");
202	pr_test("%s_envp->close(%s_envp, 0);\n",
203	    the_schema.environment.name, the_schema.environment.name);
204
205        pr_test("return ret;\n");
206	test_indent_level--;
207	pr_test("}\n");
208}
209
210/*
211 * Emit a printf-formatted string into the test code file.
212 */
213static void
214pr_test(char *fmt, ...)
215{
216	va_list ap;
217	char *s;
218	static int enable_indent = 1;
219
220	s = prepare_string(fmt,
221	    enable_indent ? test_indent_level : 0,
222	    0);
223
224	va_start(ap, fmt);
225	vfprintf(test_file, s, ap);
226	va_end(ap);
227
228        /*
229	 * If the last char emitted was a newline, enable indentation
230	 * for the next time.
231	 */
232	if (s[strlen(s) - 1] == '\n')
233		enable_indent = 1;
234	else
235		enable_indent = 0;
236}
237
238/*
239 * Emit a formatted comment into the test file.
240 */
241static void
242pr_test_comment(char *fmt, ...)
243{
244	va_list ap;
245	char *s;
246
247	s = prepare_string(fmt, test_indent_level, 1);
248
249	va_start(ap, fmt);
250	vfprintf(test_file, s, ap);
251	va_end(ap);
252}
253
254/*
255 * Return the appropriate printf format string for the given type.
256 */
257static char *
258format_string_for_type(ATTR_TYPE *t)
259{
260	char *c_type = t->c_type;
261
262	if (is_array(t)) {
263		return ("%s");
264	} else if (strcmp(c_type, "char") == 0 ||
265	    strcmp(c_type, "short") == 0 ||
266	    strcmp(c_type, "int") == 0) {
267		return("%d");
268	} else  if (strcmp(c_type, "long") == 0) {
269		return("%ld");
270	} else if (strcmp(c_type, "float") == 0) {
271		return("%f");
272	} else if (strcmp(c_type, "double") == 0) {
273		return("%lf");
274	} else {
275		fprintf(stderr,
276		    "Unexpected C type in schema: %s", c_type);
277		assert(0);
278	}
279	return NULL; /*NOTREACHED*/
280}
281
282/*
283 * Return a data literal appropriate for the given type, to use as test data.
284 */
285static char *
286data_value_for_type(ATTR_TYPE *t)
287{
288	char *c_type = t->c_type;
289
290	if (is_string(t)) {
291		/* If the field is really short, use a tiny string */
292		if (t->array_dim < 12)
293			return("\"n\"");
294		return("\"ninety-nine\"");
295	} else if (is_array(t)) {
296		return("binary_data");
297	} else if (strcmp(c_type, "char") == 0 ||
298	    strcmp(c_type, "short") == 0 ||
299	    strcmp(c_type, "int") == 0 ||
300	    strcmp(c_type, "long") == 0) {
301		return("99");
302	} else if (strcmp(c_type, "float") == 0 ||
303	    strcmp(c_type, "double") == 0) {
304		return("99.5");
305	} else {
306		fprintf(stderr,
307		    "Unexpected C type in schema: %s", c_type);
308		assert(0);
309	}
310	return NULL; /*NOTREACHED*/
311}
312
313/*
314 * This entity operation function is called by the attribute iterator
315 * when producing test code that declares record instances.
316 */
317static void
318declare_record_instances_enter_entop(ENTITY *e)
319{
320	pr_test("%s_data %s_record;\n", e->name, e->name);
321}
322
323/*
324 * This entity operation function is called by the attribute iterator
325 * when producing test code that initialized database handle.
326 */
327static void
328initialize_database_enter_entop(ENTITY *e)
329{
330	pr_test("%s_dbp = NULL;\n", e->name);
331}
332
333/*
334 * This entity operation function is called by the attribute iterator
335 * when producing test code that initialized index handle.
336 */
337static void
338initialize_index_enter_entop(DB_INDEX *idx)
339{
340	pr_test("%s_dbp = NULL;\n", idx->name);
341}
342
343static void
344invoke_full_iteration_enter_entop(ENTITY *e)
345{
346	pr_test(
347"ret = %s_full_iteration(%s_dbp, &%s_iteration_callback_test,               \n\
348  \"retrieval of %s record through full iteration\");\n",
349	    e->name, e->name, e->name, e->name);
350}
351static void
352invoke_full_iteration_exit_entop(ENTITY *e)
353{
354        COMPQUIET(e, NULL);
355	pr_test(
356"if (ret != 0){                                                             \n\
357 printf(\"Full Iteration Error\\n\");                                       \n\
358 goto exit_error;                                                           \n\
359}                                                                           \n\
360                                                                            \n\
361");
362}
363
364/*
365 * This index operation function is called by the attribute iterator
366 * when producing test code that creates the secondary databases.
367 */
368static void
369invoke_query_iteration_idxop(DB_INDEX *idx)
370{
371	ATTR_TYPE *key_type = idx->attribute->type;
372
373	pr_test("%s_query_iteration(%s_dbp, ",
374	    idx->name, idx->name);
375
376	pr_test("%s", data_value_for_type(key_type));
377
378	pr_test(
379",\n  &%s_iteration_callback_test,                                          \n\
380  \"retrieval of %s record through %s query\");\n",
381		    idx->primary->name, idx->primary->name, idx->name);
382}
383
384/*
385 * This next group of entity and attribute operation functions are
386 * called by the attribute iterator when generating insertion test code.
387 */
388static void
389insertion_test_enter_entop(ENTITY *e)
390{
391	pr_test("ret = %s_insert_fields( %s_dbp, ", e->name, e->name);
392}
393static void
394insertion_test_exit_entop(ENTITY *e)
395{
396	COMPQUIET(e, NULL);
397	pr_test(");\n");
398        pr_test(
399"if (ret != 0){                                                            \n\
400 printf(\"Insert error\\n\");                                              \n\
401 goto exit_error;                                                          \n\
402}                                                                          \n\
403                                                                           \n\
404");
405}
406static void
407insertion_test_attrop(ENTITY *e, ATTRIBUTE *a, int first, int last)
408{
409	COMPQUIET(e, NULL);
410	COMPQUIET(first, 0);
411
412	pr_test("%s", data_value_for_type(a->type));
413	if (!last)
414		pr_test(", ");
415}
416
417/*
418 * This next group of index and attribute operation functions are
419 * called by the attribute iterator when generating the iteration
420 * callback function.
421 */
422static void
423callback_function_attrop(ENTITY *e, ATTRIBUTE *a, int first, int last)
424{
425	COMPQUIET(first, 0);
426	COMPQUIET(last, 0);
427
428	pr_test("printf(\"%s->%s: ", e->name, a->name);
429
430	pr_test("%s", format_string_for_type(a->type));
431
432	if (is_array(a->type) && !is_string(a->type)) {
433		pr_test("\\n\", compare_binary(%s_record->%s, %s));\n",
434		    e->name, a->name, array_dim_name(e, a) );
435	} else {
436		pr_test("\\n\", %s_record->%s);\n", e->name, a->name);
437	}
438}
439static void
440callback_function_enter_entop(ENTITY *e)
441{
442	pr_test(
443"                                                                           \n\
444void %s_iteration_callback_test(void *msg, %s_data *%s_record)              \n\
445{                                                                           \n\
446 printf(\"In iteration callback, message is: %%s\\n\", (char *)msg);\n\n",
447	    e->name, e->name, e->name);
448
449	test_indent_level++;
450}
451static void
452callback_function_exit_entop(ENTITY *e)
453{
454	COMPQUIET(e, NULL);
455
456	test_indent_level--;
457	pr_test("}\n\n");
458}
459
460/*
461 * This next group of entity and attribute operation functions are
462 * called by the attribute iterator when generating retrieval test code
463 */
464static void
465retrieval_test_enter_entop(ENTITY *e)
466{
467        pr_test("\nprintf(\"Retrieval of %s record by key\\n\");\n", e->name);
468	pr_test("ret = get_%s_data( %s_dbp, %s, &%s_record);\n\n",
469	    e->name, e->name,
470	    data_value_for_type(e->primary_key->type), e->name);
471}
472static void
473retrieval_test_exit_entop(ENTITY *e)
474{
475	COMPQUIET(e, NULL);
476        pr_test(
477"if (ret != 0)                                                              \n\
478{                                                                           \n\
479 printf(\"Retrieve error\\n\");                                             \n\
480 goto exit_error;                                                           \n\
481}                                                                           \n\
482                                                                            \n\
483");
484}
485
486static void
487retrieval_test_attrop(ENTITY *e, ATTRIBUTE *a, int first, int last)
488{
489	COMPQUIET(first, 0);
490	COMPQUIET(last, 0);
491
492	pr_test("printf(\"%s.%s: ", e->name, a->name);
493
494	pr_test("%s", format_string_for_type(a->type));
495
496	if (is_array(a->type) && !is_string(a->type)) {
497		pr_test("\\n\", compare_binary(%s_record.%s, %s));\n",
498		    e->name, a->name, array_dim_name(e, a) );
499	} else {
500		pr_test("\\n\", %s_record.%s);\n", e->name, a->name);
501	}
502}
503
504/*
505 * This entity operation function is
506 * called by the attribute iterator when generating deletion test code.
507 */
508static void
509deletion_test_enter_entop(ENTITY *e)
510{
511        pr_test("ret = delete_%s_key( %s_dbp, %s);\n",
512            e->name, e->name, data_value_for_type(e->primary_key->type));
513}
514static void
515deletion_test_exit_entop(ENTITY *e)
516{
517        COMPQUIET(e, NULL);
518	pr_test(
519"if (ret != 0) {                                                            \n\
520 printf(\"Delete error\\n\");                                               \n\
521 goto exit_error;                                                           \n\
522}                                                                           \n\
523                                                                            \n\
524");
525}
526
527/* This entity operation function generates primary database closures. */
528static void
529close_primary_test_enter_entop(ENTITY *e)
530{
531        pr_test(
532"if (%s_dbp != NULL)                                                        \n\
533 %s_dbp->close(%s_dbp, 0);                                                  \n\
534									    \n\
535",
536 	    e->name, e->name, e->name);
537}
538
539/* This entity operation function generates secondary database closures. */
540static void
541close_secondary_test_idxop(DB_INDEX *idx)
542{
543        pr_test(
544"if (%s_dbp != NULL)                                                       \n\
545 %s_dbp->close(%s_dbp, 0);                                                 \n\
546                                                                           \n\
547",
548	    idx->name, idx->name, idx->name);
549}
550
551/* This entity operation function generates primary database closures. */
552static void
553remove_primary_test_enter_entop(ENTITY *e)
554{
555	pr_test("remove_%s_database(%s_envp);\n", e->name,
556	    the_schema.environment.name);
557}
558
559/* This entity operation function generates secondary database closures. */
560static void
561remove_secondary_test_idxop(DB_INDEX *idx)
562{
563	pr_test("remove_%s_index(%s_envp);\n", idx->name,
564	    the_schema.environment.name);
565}
566