Line data Source code
1 : /* FreeTDS - Library of routines accessing Sybase and Microsoft databases
2 : * Copyright (C) 2004-2011 James K. Lowden
3 : *
4 : * This program is free software; you can redistribute it and/or
5 : * modify it under the terms of the GNU General Public
6 : * License as published by the Free Software Foundation; either
7 : * version 2 of the License, or (at your option) any later version.
8 : *
9 : * This library is distributed in the hope that it will be useful,
10 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 : * Library General Public License for more details.
13 : *
14 : * You should have received a copy of the GNU General Public
15 : * License along with this library; if not, write to the
16 : * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17 : * Boston, MA 02111-1307, USA.
18 : */
19 :
20 : #ifdef MicrosoftsDbLib
21 : #ifdef _WIN32
22 : # pragma warning( disable : 4142 )
23 : # include "win32.microsoft/have.h"
24 : # include "../../include/replacements.win32.hacked.h"
25 : int getopt(int argc, const char *argv[], char *optstring);
26 :
27 : # ifndef DBNTWIN32
28 : # define DBNTWIN32
29 :
30 : /*
31 : * As of Visual Studio .NET 2003, define WIN32_LEAN_AND_MEAN to avoid redefining LPCBYTE in sqlfront.h
32 : * Unless it was already defined, undefine it after windows.h has been included.
33 : * (windows.h includes a bunch of stuff needed by sqlfront.h. Bleh.)
34 : */
35 : # ifndef WIN32_LEAN_AND_MEAN
36 : # define WIN32_LEAN_AND_MEAN
37 : # define WIN32_LEAN_AND_MEAN_DEFINED_HERE
38 : # endif
39 : # include <freetds/windows.h>
40 : # ifdef WIN32_LEAN_AND_MEAN_DEFINED_HERE
41 : # undef WIN32_LEAN_AND_MEAN_DEFINED_HERE
42 : # undef WIN32_LEAN_AND_MEAN
43 : # endif
44 : # include <process.h>
45 : # include <sqlfront.h>
46 : # include <sqldb.h>
47 :
48 : #endif /* DBNTWIN32 */
49 : # include "win32.microsoft/syb2ms.h"
50 : #endif
51 : #endif /* MicrosoftsDbLib */
52 :
53 : #include <config.h>
54 :
55 : #include <stdio.h>
56 : #include <assert.h>
57 :
58 : #if HAVE_ERRNO_H
59 : #include <errno.h>
60 : #endif
61 :
62 : #if HAVE_UNISTD_H
63 : #include <unistd.h>
64 : #endif
65 :
66 : #if HAVE_STDLIB_H
67 : #include <stdlib.h>
68 : #endif
69 :
70 : #if HAVE_STRING_H
71 : #include <string.h>
72 : #endif
73 :
74 : #if HAVE_LIBGEN_H
75 : #include <libgen.h>
76 : #endif
77 :
78 : #if HAVE_LOCALE_H
79 : #include <locale.h>
80 : #endif
81 :
82 : #include <sybfront.h>
83 : #include <sybdb.h>
84 : #ifndef MicrosoftsDbLib
85 : #include <freetds/replacements.h>
86 : #else
87 :
88 : #ifndef _WIN32
89 : # include <freetds/replacements.h>
90 : #endif
91 : #endif /* MicrosoftsDbLib */
92 :
93 : #include <freetds/sysdep_private.h>
94 : #include <freetds/utils.h>
95 : #include <freetds/bool.h>
96 : #include <freetds/macros.h>
97 :
98 : #ifndef MicrosoftsDbLib
99 : static int err_handler(DBPROCESS * dbproc, int severity, int dberr, int oserr, char *dberrstr, char *oserrstr);
100 : static int msg_handler(DBPROCESS * dbproc, DBINT msgno, int msgstate, int severity, char *msgtext,
101 : char *srvname, char *procname, int line);
102 : #else
103 : static int err_handler(DBPROCESS * dbproc, int severity, int dberr, int oserr, const char dberrstr[], const char oserrstr[]);
104 : static int msg_handler(DBPROCESS * dbproc, DBINT msgno, int msgstate, int severity, const char msgtext[],
105 : const char srvname[], const char procname[], unsigned short int line);
106 : #endif /* MicrosoftsDbLib */
107 :
108 : typedef struct _options
109 : {
110 : int optind;
111 : bool sybase_syntax;
112 : char *servername,
113 : *database,
114 : *appname,
115 : hostname[128],
116 : *input_filename,
117 : *output_filename;
118 : } OPTIONS;
119 :
120 : static void
121 120 : free_options(OPTIONS *options)
122 : {
123 : /* Memory was allocated with tds_xstrdup() which resolves to strdup(),
124 : * (not with tds_new) */
125 120 : free(options->servername);
126 120 : free(options->database);
127 120 : free(options->input_filename);
128 120 : free(options->output_filename);
129 : /* note: options->appname points into argv[0] */
130 120 : }
131 :
132 : typedef struct _procedure
133 : {
134 : char name[512], owner[512];
135 : } PROCEDURE;
136 :
137 : typedef struct DDL {
138 : char *name;
139 : char *type;
140 : char *length;
141 : char *precision;
142 : char *scale;
143 : char *nullable;
144 : } DDL;
145 :
146 : static int print_ddl(DBPROCESS *dbproc, PROCEDURE *procedure);
147 : static int print_results(DBPROCESS *dbproc);
148 : static LOGINREC* get_login(char *argv0, OPTIONS *poptions);
149 : static void parse_argument(const char argument[], PROCEDURE* procedure);
150 : static void usage(const char invoked_as[]);
151 : static char *rtrim(char *s);
152 : static char *ltrim(char *s);
153 :
154 : typedef struct tmp_buf {
155 : struct tmp_buf *next;
156 : char buf[1];
157 : } tmp_buf;
158 :
159 : static tmp_buf *tmp_list = NULL;
160 :
161 : static void*
162 494 : tmp_malloc(size_t len)
163 : {
164 494 : tmp_buf *tmp = malloc(sizeof(tmp_buf*) + len);
165 494 : if (!tmp) {
166 0 : fprintf(stderr, "Out of memory\n");
167 0 : exit(1);
168 : }
169 494 : tmp->next = tmp_list;
170 494 : tmp_list = tmp;
171 494 : return tmp->buf;
172 : }
173 :
174 : static void
175 : tmp_free(void)
176 : {
177 1028 : while (tmp_list) {
178 494 : tmp_buf *next = tmp_list->next;
179 494 : free(tmp_list);
180 494 : tmp_list = next;
181 : }
182 : }
183 :
184 : static size_t
185 : count_chars(const char *s, char c)
186 : {
187 834 : size_t num = 0;
188 : if (c != 0) {
189 834 : --s;
190 894 : while ((s = strchr(s + 1, c)) != NULL)
191 40 : ++num;
192 : }
193 : return num;
194 : }
195 :
196 : static char *
197 : sql_quote(char *dest, const char *src, char quote_char)
198 : {
199 4356 : for (; *src; ++src) {
200 4356 : if (*src == quote_char)
201 0 : *dest++ = *src;
202 4356 : *dest++ = *src;
203 : }
204 : return dest;
205 : }
206 :
207 : static const char *
208 494 : quote_id(const char *id)
209 : {
210 : size_t n, len;
211 : char *s, *p;
212 :
213 494 : n = count_chars(id, ']');
214 494 : len = 1 + strlen(id) + n + 1 + 1;
215 494 : p = s = tmp_malloc(len);
216 494 : *p++ = '[';
217 494 : p = sql_quote(p, id, ']');
218 494 : *p++ = ']';
219 494 : *p = 0;
220 494 : return s;
221 : }
222 :
223 : static const char *
224 320 : quote_str(const char *str)
225 : {
226 : size_t n, len;
227 : char *s, *p;
228 :
229 320 : n = count_chars(str, '\'');
230 320 : if (!n)
231 : return str;
232 0 : len = strlen(str) + n + 1;
233 0 : s = tmp_malloc(len);
234 0 : p = sql_quote(s, str, '\'');
235 0 : *p = 0;
236 0 : return s;
237 : }
238 :
239 : /* global variables */
240 : static OPTIONS options;
241 : static char use_statement[512];
242 : static DBPROCESS *dbproc;
243 :
244 : /* end global variables */
245 :
246 :
247 : static int process_args(LOGINREC * login, int argc, char **argv, OPTIONS * options);
248 : static int main_processing(LOGINREC * login, int argc, char **argv);
249 :
250 : /**
251 : * The purpose of this program is to load or extract the text of a stored procedure.
252 : * This first cut does extract only.
253 : * TODO: support loading procedures onto the server.
254 : */
255 : int
256 120 : main(int argc, char *argv[])
257 : {
258 : LOGINREC *login;
259 : RETCODE erc;
260 : int result;
261 :
262 120 : setlocale(LC_ALL, "");
263 :
264 : #ifdef __VMS
265 : /* Convert VMS-style arguments to Unix-style */
266 : parse_vms_args(&argc, &argv);
267 : #endif
268 :
269 : /* Initialize db-lib */
270 : #if _WIN32 && defined(MicrosoftsDbLib)
271 : LPCSTR msg = dbinit();
272 : if (msg == NULL) {
273 : #else
274 120 : erc = dbinit();
275 120 : if (erc == FAIL) {
276 : #endif /* MicrosoftsDbLib */
277 0 : fprintf(stderr, "%s:%d: dbinit() failed\n", options.appname, __LINE__);
278 0 : exit(EXIT_FAILURE);
279 : }
280 :
281 120 : memset(&options, 0, sizeof(options));
282 : /* get command-line parameters and call dblogin() */
283 120 : login = get_login(argv[0], &options);
284 120 : result = process_args(login, argc, argv, &options);
285 120 : if (result == 0)
286 120 : result = main_processing(login, argc, argv);
287 : else
288 0 : usage(options.appname);
289 :
290 120 : if (dbproc)
291 120 : bcp_done(dbproc);
292 120 : if (login)
293 120 : dbloginfree(login);
294 120 : dbexit();
295 120 : free_options(&options);
296 : return result;
297 : }
298 :
299 : static int
300 120 : main_processing(LOGINREC *login, int argc, char **argv)
301 : {
302 120 : int i, nrows = 0;
303 : RETCODE erc;
304 : PROCEDURE procedure;
305 :
306 120 : if (!login)
307 : return EXIT_FAILURE;
308 :
309 : /* Install our error and message handlers */
310 120 : dberrhandle(err_handler);
311 120 : dbmsghandle(msg_handler);
312 :
313 : /*
314 : * Override stdin, stdout, and stderr, as required
315 : */
316 120 : if (options.input_filename) {
317 30 : if (freopen(options.input_filename, "r", stdin) == NULL) {
318 0 : fprintf(stderr, "%s: unable to open %s: %s\n", options.appname, options.input_filename, strerror(errno));
319 0 : return EXIT_FAILURE;
320 : }
321 : }
322 :
323 120 : if (options.output_filename) {
324 40 : if (freopen(options.output_filename, "w", stdout) == NULL) {
325 0 : fprintf(stderr, "%s: unable to open %s: %s\n", options.appname, options.output_filename, strerror(errno));
326 0 : return EXIT_FAILURE;
327 : }
328 : }
329 :
330 : /* Select the specified database, if any */
331 120 : if (options.database)
332 120 : DBSETLDBNAME(login, options.database);
333 :
334 : /*
335 : * Connect to the server
336 : */
337 120 : dbproc = dbopen(login, options.servername);
338 120 : if (!dbproc) {
339 0 : fprintf(stderr, "There was a problem connecting to the server.\n");
340 0 : return EXIT_FAILURE;
341 : }
342 :
343 : /* We're in input mode if no output objects were specified. */
344 120 : if (options.optind == argc) {
345 : /* This code is only designed to support the syntax output by DEFNCOPY.
346 : * Refer to fisql.c for a more general input implementation.
347 : */
348 :
349 : char buf[5000];
350 :
351 : i = 0;
352 200 : while (fgets(buf, sizeof buf, stdin)) {
353 160 : ++i;
354 : /* dbcmd does not recognize directive "GO" */
355 160 : if (buf[0] == 'G' && buf[1] == 'O')
356 20 : continue;
357 :
358 140 : erc = dbcmd(dbproc, buf);
359 140 : if (erc != SUCCEED) {
360 0 : fprintf(stderr, "%s: input failed on line %d\n", options.appname, i);
361 0 : return EXIT_FAILURE;
362 : }
363 : }
364 40 : erc = dbsqlexec(dbproc);
365 40 : if (erc != SUCCEED)
366 : return EXIT_FAILURE; /* Error handler would output error messages */
367 40 : if (dbhasretstat(dbproc)) {
368 0 : erc = dbretstatus(dbproc);
369 0 : printf("(return status = %d)\n", erc);
370 : }
371 :
372 : return 0;
373 : }
374 :
375 : /*
376 : * Read the procedure names and move their texts.
377 : */
378 80 : for (i=options.optind; i < argc; i++) {
379 : #ifndef MicrosoftsDbLib
380 : static const char query[] = " select c.text"
381 : #else
382 : static const char query[] = " select cast(c.text as text)"
383 : #endif /* MicrosoftsDbLib */
384 : ", number "
385 : " from syscomments c,"
386 : " sysobjects o"
387 : " where o.id = c.id"
388 : " and o.name = '%s'"
389 : " and o.uid = user_id('%s')"
390 : " and o.type not in ('U', 'S')" /* no user or system tables */
391 : " order by c.number, %sc.colid"
392 : ;
393 : static const char query_table[] = " execute sp_help '%s.%s' ";
394 :
395 80 : parse_argument(argv[i], &procedure);
396 :
397 80 : erc = dbfcmd(dbproc, query, quote_str(procedure.name), quote_str(procedure.owner),
398 80 : (DBTDS(dbproc) == DBTDS_5_0) ? "c.colid2, ":"");
399 80 : tmp_free();
400 :
401 : /* Send the query to the server (we could use dbsqlexec(), instead) */
402 80 : erc = dbsqlsend(dbproc);
403 80 : if (erc == FAIL) {
404 0 : fprintf(stderr, "%s:%d: dbsqlsend() failed\n", options.appname, __LINE__);
405 0 : return EXIT_FAILURE;
406 : }
407 :
408 : /* Wait for it to execute */
409 80 : erc = dbsqlok(dbproc);
410 80 : if (erc == FAIL) {
411 0 : fprintf(stderr, "%s:%d: dbsqlok() failed\n", options.appname, __LINE__);
412 0 : return EXIT_FAILURE;
413 : }
414 :
415 : /* Write the output */
416 80 : nrows = print_results(dbproc);
417 :
418 : /* If the query to retrieve a non-table object failed, try retrieving
419 : * a table instead. Note: Sybase DEFNCOPY does not support table
420 : * definitions; this code is peculiar to FreeTDS DEFNCOPY.
421 : */
422 80 : if (0 == nrows) {
423 80 : erc = dbfcmd(dbproc, query_table, quote_str(procedure.owner), quote_str(procedure.name));
424 80 : tmp_free();
425 80 : assert(SUCCEED == erc);
426 80 : erc = dbsqlexec(dbproc);
427 80 : if (erc == FAIL) {
428 0 : fprintf(stderr, "%s:%d: dbsqlexec() failed\n", options.appname, __LINE__);
429 0 : return EXIT_FAILURE;
430 : }
431 80 : nrows = print_ddl(dbproc, &procedure);
432 : }
433 :
434 80 : switch (nrows) {
435 : case -1:
436 : return EXIT_FAILURE;
437 0 : case 0:
438 0 : fprintf(stderr, "%s: error: %s.%s.%s.%s not found\n", options.appname,
439 : options.servername, options.database, procedure.owner, procedure.name);
440 0 : return 2;
441 : default:
442 : break;
443 : }
444 : }
445 :
446 : return EXIT_SUCCESS;
447 : }
448 :
449 : static void
450 80 : parse_argument(const char argument[], PROCEDURE* procedure)
451 : {
452 80 : const char *s = strchr(argument, '.');
453 :
454 80 : if (s) {
455 80 : size_t len = s - argument;
456 80 : if (len > sizeof(procedure->owner) - 1)
457 0 : len = sizeof(procedure->owner) - 1;
458 80 : memcpy(procedure->owner, argument, len);
459 80 : procedure->owner[len] = '\0';
460 :
461 80 : strlcpy(procedure->name, s+1, sizeof(procedure->name));
462 : } else {
463 0 : strcpy(procedure->owner, "dbo");
464 0 : strlcpy(procedure->name, argument, sizeof(procedure->name));
465 : }
466 80 : }
467 :
468 : static char *
469 240 : rtrim(char *s)
470 : {
471 240 : char *p = strchr(s, '\0');
472 :
473 240 : while (--p >= s && *p == ' ')
474 : ;
475 240 : *(p + 1) = '\0';
476 :
477 240 : return s;
478 : }
479 :
480 : static char *
481 240 : ltrim(char *s)
482 : {
483 240 : char *p = s;
484 :
485 480 : while (*p == ' ')
486 0 : ++p;
487 240 : memmove(s, p, strlen(p) + 1);
488 :
489 240 : return s;
490 : }
491 :
492 : static bool
493 6664 : is_in(const char *item, const char *list)
494 : {
495 6664 : const size_t item_len = strlen(item);
496 7034 : for (;;) {
497 13698 : size_t len = strlen(list);
498 13698 : if (len == 0)
499 : return false;
500 8122 : if (len == item_len && strcasecmp(item, list) == 0)
501 : return true;
502 7034 : list += len + 1;
503 : }
504 : }
505 :
506 : static void
507 150 : search_columns(DBPROCESS *dbproc, int *colmap, const char *const *colmap_names, int num_cols)
508 : {
509 : int i;
510 :
511 150 : assert(dbproc && colmap && colmap_names);
512 150 : assert(num_cols > 0);
513 :
514 : /* Find the columns we need */
515 840 : for (i = 0; i < num_cols; ++i)
516 840 : colmap[i] = -1;
517 1406 : for (i = 1; i <= dbnumcols(dbproc); ++i) {
518 1406 : const char *name = dbcolname(dbproc, i);
519 : int j;
520 :
521 6752 : for (j = 0; j < num_cols; ++j) {
522 6186 : if (is_in(name, colmap_names[j])) {
523 840 : colmap[j] = i;
524 840 : break;
525 : }
526 : }
527 : }
528 840 : for (i = 0; i < num_cols; ++i) {
529 840 : if (colmap[i] == -1) {
530 0 : fprintf(stderr, "Expected column name %s not found\n", colmap_names[i]);
531 0 : exit(1);
532 : }
533 : }
534 150 : }
535 :
536 : static int
537 34 : find_column_name(const char *start, const DDL *columns, int num_columns)
538 : {
539 34 : size_t start_len = strlen(start);
540 34 : size_t found_len = 0;
541 34 : int i, found = -1;
542 :
543 158 : for (i = 0; i < num_columns; ++i) {
544 124 : const char *const name = columns[i].name;
545 124 : const size_t name_len = strlen(name);
546 124 : if (name_len <= start_len && name_len > found_len
547 86 : && (start[name_len] == 0 || strncmp(start+name_len, ", ", 2) == 0)
548 60 : && memcmp(name, start, name_len) == 0) {
549 40 : found_len = name_len;
550 40 : found = i;
551 : }
552 : }
553 34 : return found;
554 : }
555 :
556 : static void
557 80 : definition_delimiter(void)
558 : {
559 80 : if (options.sybase_syntax)
560 30 : printf("/* ### DEFNCOPY: END OF DEFINITION */\n");
561 : else
562 50 : printf("GO\n\n");
563 80 : }
564 :
565 : /* This function split and quote index keys.
566 : * Index keys are separate by a command and a space (", ") however the
567 : * separator (very unlikely but possible can contain that separator)
568 : * so we use search for column names taking the longer we can.
569 : */
570 : static char *
571 20 : quote_index_keys(char *index_keys, const DDL *columns, int num_columns)
572 : {
573 : size_t num_commas = count_chars(index_keys, ',');
574 20 : size_t num_quotes = count_chars(index_keys, ']');
575 20 : size_t max_len = strlen(index_keys) + num_quotes + (num_commas + 1) * 2 + 1;
576 20 : char *const new_index_keys = malloc(max_len);
577 : char *dest;
578 20 : bool first = true;
579 20 : assert(new_index_keys);
580 : dest = new_index_keys;
581 50 : while (*index_keys) {
582 30 : int icol = find_column_name(index_keys, columns, num_columns);
583 : /* Sybase put a space at the beginning, handle it */
584 30 : if (icol < 0 && index_keys[0] == ' ') {
585 4 : icol = find_column_name(index_keys + 1, columns, num_columns);
586 4 : if (icol >= 0)
587 4 : ++index_keys;
588 : }
589 30 : if (!first)
590 10 : *dest++ = ',';
591 30 : *dest++ = '[';
592 30 : if (icol >= 0) {
593 : /* found a column matching, use the name */
594 60 : dest = sql_quote(dest, columns[icol].name, ']');
595 30 : index_keys += strlen(columns[icol].name);
596 : } else {
597 : /* not found, fallback looking for terminator */
598 : char save;
599 0 : char *end = strstr(index_keys, ", ");
600 0 : if (!end)
601 0 : end = strchr(index_keys, 0);
602 0 : save = *end;
603 0 : *end = 0;
604 0 : dest = sql_quote(dest, index_keys, ']');
605 0 : *end = save;
606 0 : index_keys = end;
607 : }
608 30 : *dest++ = ']';
609 30 : if (strncmp(index_keys, ", ", 2) == 0)
610 10 : index_keys += 2;
611 : first = false;
612 : }
613 20 : *dest = 0;
614 20 : return new_index_keys;
615 : }
616 :
617 : static char *
618 20 : parse_index_row(DBPROCESS *dbproc, PROCEDURE *procedure, char *create_index, const DDL *columns, int num_columns)
619 : {
620 : static const char *const colmap_names[3] = {
621 : "index_name\0",
622 : "index_description\0",
623 : "index_keys\0",
624 : };
625 : int colmap[TDS_VECTOR_SIZE(colmap_names)];
626 20 : char *index_name, *index_description, *index_keys, *p, fprimary=0;
627 : char *tmp_str;
628 : DBINT datlen;
629 : int ret, i;
630 :
631 20 : assert(dbnumcols(dbproc) >=3 ); /* column had better be in range */
632 :
633 20 : search_columns(dbproc, colmap, colmap_names, TDS_VECTOR_SIZE(colmap));
634 :
635 : /* name */
636 20 : datlen = dbdatlen(dbproc, 1);
637 20 : index_name = (char *) tds_strndup(dbdata(dbproc, 1), datlen);
638 20 : assert(index_name);
639 :
640 : /* kind */
641 20 : i = colmap[1];
642 20 : datlen = dbdatlen(dbproc, i);
643 20 : index_description = (char *) tds_strndup(dbdata(dbproc, i), datlen);
644 20 : assert(index_description);
645 :
646 : /* columns */
647 20 : i = colmap[2];
648 20 : datlen = dbdatlen(dbproc, i);
649 20 : index_keys = (char *) tds_strndup(dbdata(dbproc, i), datlen);
650 20 : assert(index_keys);
651 :
652 20 : tmp_str = quote_index_keys(index_keys, columns, num_columns);
653 20 : free(index_keys);
654 20 : index_keys = tmp_str;
655 :
656 : /* fix up the index attributes; we're going to use the string verbatim (almost). */
657 20 : p = strstr(index_description, "located");
658 20 : if (p) {
659 16 : *p = '\0'; /* we don't care where it's located */
660 : }
661 : /* Microsoft version: [non]clustered[, unique][, primary key] located on PRIMARY */
662 20 : p = strstr(index_description, "primary key");
663 20 : if (p) {
664 0 : fprimary = 1;
665 0 : *p = '\0'; /* we don't care where it's located */
666 0 : if ((p = strchr(index_description, ',')) != NULL)
667 0 : *p = '\0'; /* we use only the first term (clustered/nonclustered) */
668 : } else {
669 : /* reorder "unique" and "clustered" */
670 20 : char nonclustered[] = "nonclustered", unique[] = "unique";
671 20 : char *pclustering = nonclustered;
672 20 : if (NULL == strstr(index_description, pclustering)) {
673 0 : pclustering += 3;
674 0 : if (NULL == strstr(index_description, pclustering))
675 0 : *pclustering = '\0';
676 : }
677 20 : if (NULL == strstr(index_description, unique))
678 20 : unique[0] = '\0';
679 20 : sprintf(index_description, "%s %s", unique, pclustering);
680 : }
681 : /* Put it to a temporary variable; we'll print it after the CREATE TABLE statement. */
682 20 : tmp_str = create_index;
683 20 : create_index = create_index ? create_index : "";
684 20 : if (fprimary) {
685 0 : ret = asprintf(&create_index,
686 : "%sALTER TABLE %s.%s ADD CONSTRAINT %s PRIMARY KEY %s (%s)\n\n",
687 : create_index,
688 0 : quote_id(procedure->owner), quote_id(procedure->name),
689 : quote_id(index_name), index_description, index_keys);
690 : } else {
691 40 : ret = asprintf(&create_index,
692 : "%sCREATE %s INDEX %s on %s.%s(%s)\n\n",
693 : create_index,
694 : index_description, quote_id(index_name),
695 40 : quote_id(procedure->owner), quote_id(procedure->name), index_keys);
696 : }
697 20 : assert(ret >= 0);
698 20 : free(tmp_str);
699 20 : tmp_free();
700 :
701 20 : free(index_name);
702 20 : free(index_description);
703 20 : free(index_keys);
704 :
705 20 : return create_index;
706 : }
707 :
708 : /*
709 : * Get the table information from sp_help, because it's easier to get the index information (eventually).
710 : * The column descriptions are in resultset #2, which is where we start.
711 : * As shown below, sp_help returns different columns for resultset #2, so we build a map.
712 : * Sybase Microsoft
713 : * ----------------- ----------------
714 : * 1. Column_name Column_name
715 : * 2. Type Type
716 : * 3. Computed
717 : * 4. Length Length
718 : * 5. Prec Prec
719 : * 6. Scale Scale
720 : * 7. Nulls Nullable
721 : * 8. Default_name TrimTrail
722 : * 9. Rule_name FixedLenNullIn
723 : * 10. Access_Rule_name Collation
724 : * 11. Identity
725 : */
726 : static int
727 80 : print_ddl(DBPROCESS *dbproc, PROCEDURE *procedure)
728 : {
729 : static const char *const colmap_names[6] = {
730 : "column_name\0",
731 : "type\0",
732 : "length\0",
733 : "prec\0",
734 : "scale\0",
735 : "nulls\0nullable\0",
736 : };
737 :
738 80 : DDL *ddl = NULL;
739 80 : char *create_index = NULL;
740 : RETCODE erc;
741 : int iresultset, i;
742 80 : int maxnamelen = 0, nrows = 0;
743 : char **p_str;
744 80 : bool is_ms = false;
745 :
746 80 : assert(dbproc);
747 80 : assert(procedure);
748 :
749 : /* sp_help returns several result sets. We want just the second one, for now */
750 488 : for (iresultset=1; (erc = dbresults(dbproc)) != NO_MORE_RESULTS; iresultset++) {
751 : int row_code;
752 :
753 488 : if (erc == FAIL) {
754 0 : fprintf(stderr, "%s:%d: dbresults(), result set %d failed\n", options.appname, __LINE__, iresultset);
755 0 : goto cleanup;
756 : }
757 :
758 : /* Get the data */
759 1010 : while ((row_code = dbnextrow(dbproc)) != NO_MORE_ROWS) {
760 : DDL *p;
761 : char **coldesc[sizeof(DDL)/sizeof(char*)]; /* an array of pointers to the DDL elements */
762 : int colmap[TDS_VECTOR_SIZE(colmap_names)];
763 :
764 522 : assert(row_code == REG_ROW);
765 :
766 : /* Look for index data */
767 522 : if (0 == strcmp("index_name", dbcolname(dbproc, 1))) {
768 20 : create_index = parse_index_row(dbproc, procedure, create_index, ddl, nrows);
769 412 : continue;
770 : }
771 :
772 : /* skip other resultsets that don't describe the table's columns */
773 502 : if (0 != strcasecmp("Column_name", dbcolname(dbproc, 1)))
774 372 : continue;
775 :
776 : /* Find the columns we need */
777 130 : search_columns(dbproc, colmap, colmap_names, TDS_VECTOR_SIZE(colmap));
778 :
779 : /* check if server is Microsoft */
780 1352 : for (i = 1; i <= dbnumcols(dbproc); ++i)
781 1326 : if (strcasecmp(dbcolname(dbproc, i), "Collation") == 0) {
782 : is_ms = true;
783 : break;
784 : }
785 :
786 : /* Make room for the next row */
787 130 : p = (DDL *) realloc(ddl, ++nrows * sizeof(DDL));
788 130 : if (p == NULL) {
789 0 : perror("error: insufficient memory for row DDL");
790 0 : assert(p != NULL);
791 : exit(1);
792 : }
793 130 : ddl = p;
794 :
795 : /* take the address of each member, so we can loop through them */
796 130 : coldesc[0] = &ddl[nrows-1].name;
797 130 : coldesc[1] = &ddl[nrows-1].type;
798 130 : coldesc[2] = &ddl[nrows-1].length;
799 130 : coldesc[3] = &ddl[nrows-1].precision;
800 130 : coldesc[4] = &ddl[nrows-1].scale;
801 130 : coldesc[5] = &ddl[nrows-1].nullable;
802 :
803 910 : for( i=0; i < sizeof(DDL)/sizeof(char*); i++) {
804 780 : const int col_index = colmap[i];
805 780 : const DBINT datlen = dbdatlen(dbproc, col_index);
806 780 : const int type = dbcoltype(dbproc, col_index);
807 :
808 780 : assert(datlen >= 0); /* column had better be in range */
809 :
810 780 : if (datlen == 0) {
811 52 : *coldesc[i] = NULL;
812 52 : continue;
813 : }
814 :
815 728 : if (type == SYBCHAR || type == SYBVARCHAR) {
816 572 : *coldesc[i] = (char *) tds_strndup(dbdata(dbproc, col_index), datlen);
817 572 : if (!*coldesc[i]) {
818 0 : perror("error: insufficient memory for row detail");
819 0 : exit(1);
820 : }
821 : } else {
822 : char buf[256];
823 156 : DBINT len = dbconvert(dbproc, type, dbdata(dbproc, col_index), datlen,
824 : SYBVARCHAR, (BYTE *) buf, -1);
825 156 : if (len < 0) {
826 0 : fprintf(stderr, "Error converting column to char");
827 0 : exit(1);
828 : }
829 156 : *coldesc[i] = strdup(buf);
830 156 : if (!*coldesc[i]) {
831 0 : perror("error: insufficient memory for row detail");
832 0 : exit(1);
833 : }
834 : }
835 :
836 : /*
837 : * maxnamelen will determine how much room we allow for column names
838 : * in the CREATE TABLE statement
839 : */
840 728 : if (i == 0)
841 130 : maxnamelen = (maxnamelen > datlen)? maxnamelen : datlen;
842 : }
843 : } /* wend */
844 : }
845 :
846 : /*
847 : * We've collected the description for the columns in the 'ddl' array.
848 : * Now we'll print the CREATE TABLE statement in jkl's preferred format.
849 : */
850 80 : if (nrows == 0)
851 : goto cleanup;
852 :
853 80 : printf("CREATE TABLE %s.%s\n", quote_id(procedure->owner), quote_id(procedure->name));
854 : tmp_free();
855 130 : for (i=0; i < nrows; i++) {
856 : static const char varytypenames[] = "char\0"
857 : "nchar\0"
858 : "varchar\0"
859 : "nvarchar\0"
860 : "unichar\0"
861 : "univarchar\0"
862 : "binary\0"
863 : "varbinary\0"
864 : ;
865 130 : char *type = NULL;
866 : bool is_null;
867 : int ret;
868 :
869 : /* get size of decimal, numeric, char, and image types */
870 130 : ret = 0;
871 130 : if (is_in(ddl[i].type, "decimal\0numeric\0")) {
872 0 : if (ddl[i].precision && 0 != strcasecmp("NULL", ddl[i].precision)) {
873 0 : rtrim(ddl[i].precision);
874 0 : rtrim(ddl[i].scale);
875 0 : ret = asprintf(&type, "%s(%s,%s)", ddl[i].type, ddl[i].precision, ddl[i].scale);
876 : }
877 130 : } else if (is_in(ddl[i].type, varytypenames)) {
878 110 : ltrim(rtrim(ddl[i].length));
879 110 : if (strcmp(ddl[i].length, "-1") == 0)
880 0 : ret = asprintf(&type, "%s(max)", ddl[i].type);
881 110 : else if (is_ms && is_in(ddl[i].type, "nchar\0nvarchar\0"))
882 176 : ret = asprintf(&type, "%s(%d)", ddl[i].type, atoi(ddl[i].length)/2);
883 : else
884 22 : ret = asprintf(&type, "%s(%s)", ddl[i].type, ddl[i].length);
885 : }
886 110 : assert(ret >= 0);
887 :
888 130 : ltrim(rtrim(ddl[i].nullable));
889 130 : is_null = is_in(ddl[i].nullable, "1\0yes\0");
890 :
891 : /* {(|,} name type [NOT] NULL */
892 260 : printf("\t%c %-*s %-15s %3s NULL\n", (i==0? '(' : ','), maxnamelen+2, quote_id(ddl[i].name),
893 130 : (type? type : ddl[i].type), (is_null? "" : "NOT"));
894 130 : tmp_free();
895 :
896 130 : free(type);
897 : }
898 80 : printf("\t)\n");
899 :
900 : /* print the CREATE INDEX statements */
901 80 : if (create_index != NULL)
902 20 : fputs(create_index, stdout);
903 :
904 80 : definition_delimiter();
905 :
906 80 : cleanup:
907 80 : p_str = (char **) ddl;
908 860 : for (i = 0; i < nrows * (sizeof(DDL) / sizeof(char *)); ++i)
909 780 : free(p_str[i]);
910 80 : free(ddl);
911 80 : free(create_index);
912 80 : return nrows;
913 : }
914 :
915 : static int /* return count of SQL text rows */
916 80 : print_results(DBPROCESS *dbproc)
917 : {
918 : RETCODE erc;
919 : int row_code;
920 : int iresultset;
921 80 : int nrows = 0;
922 :
923 : /* bound variables */
924 : enum column_id { ctext=1, number=2 };
925 : char sql_text[16002];
926 : int sql_text_status;
927 : int procedure_number; /* for create proc abc;2 */
928 : int procedure_number_status;
929 :
930 : /*
931 : * Set up each result set with dbresults()
932 : */
933 80 : for (iresultset=1; (erc = dbresults(dbproc)) != NO_MORE_RESULTS; iresultset++) {
934 80 : if (erc == FAIL) {
935 0 : fprintf(stderr, "%s:%d: dbresults(), result set %d failed\n", options.appname, __LINE__, iresultset);
936 0 : return -1;
937 : }
938 :
939 80 : if (SUCCEED != DBROWS(dbproc)) {
940 : return 0;
941 : }
942 :
943 : /*
944 : * Bind the columns to our variables.
945 : */
946 0 : if (sizeof(sql_text) <= dbcollen(dbproc, ctext) ) {
947 0 : assert(sizeof(sql_text) > dbcollen(dbproc, ctext));
948 : return 0;
949 : }
950 0 : erc = dbbind(dbproc, ctext, STRINGBIND, 0, (BYTE *) sql_text);
951 0 : if (erc == FAIL) {
952 0 : fprintf(stderr, "%s:%d: dbbind(), column %d failed\n", options.appname, __LINE__, ctext);
953 0 : return -1;
954 : }
955 0 : erc = dbnullbind(dbproc, ctext, &sql_text_status);
956 0 : if (erc == FAIL) {
957 0 : fprintf(stderr, "%s:%d: dbnullbind(), column %d failed\n", options.appname, __LINE__, ctext);
958 0 : return -1;
959 : }
960 :
961 0 : erc = dbbind(dbproc, number, INTBIND, -1, (BYTE *) &procedure_number);
962 0 : if (erc == FAIL) {
963 0 : fprintf(stderr, "%s:%d: dbbind(), column %d failed\n", options.appname, __LINE__, number);
964 0 : return -1;
965 : }
966 0 : erc = dbnullbind(dbproc, number, &procedure_number_status);
967 0 : if (erc == FAIL) {
968 0 : fprintf(stderr, "%s:%d: dbnullbind(), column %d failed\n", options.appname, __LINE__, number);
969 0 : return -1;
970 : }
971 :
972 : /*
973 : * Print the data to stdout.
974 : */
975 0 : for (; (row_code = dbnextrow(dbproc)) != NO_MORE_ROWS; nrows++) {
976 0 : switch (row_code) {
977 0 : case REG_ROW:
978 0 : if (-1 == sql_text_status) {
979 0 : fprintf(stderr, "defncopy: error: unexpected NULL row in SQL text\n");
980 : } else {
981 0 : printf("%s", sql_text);
982 : }
983 : break;
984 0 : case BUF_FULL:
985 : default:
986 0 : fprintf(stderr, "defncopy: error: expected REG_ROW (%d), got %d instead\n", REG_ROW, row_code);
987 0 : assert(row_code == REG_ROW);
988 : break;
989 : } /* row_code */
990 :
991 : } /* wend dbnextrow */
992 : /* Sybase DEFNCOPY marker between definitions */
993 0 : definition_delimiter();
994 :
995 : } /* wend dbresults */
996 :
997 : return nrows;
998 : }
999 :
1000 : static void
1001 : usage(const char invoked_as[])
1002 : {
1003 :
1004 0 : fprintf(stderr, "usage: %s \n"
1005 : /*
1006 : " [-X]\n"
1007 : " [-a <display_charset>]\n"
1008 : " [-I <interfaces_file>]\n"
1009 : " [-J [<client_charset>]]\n"
1010 : " [-K <keytab_file>]\n"
1011 : */
1012 : " [-P <password>]\n"
1013 : /*
1014 : " [-R <remote_server_principal>]\n"
1015 : */
1016 : " [-S [<server_name>]]\n"
1017 : " [-U <user_name>]\n"
1018 : /*
1019 : " [-V <security_options>]\n"
1020 : " [-Z <security_mechanism>]\n"
1021 : " [-z <language>]\n"
1022 : */
1023 : " { in <file_name> <database_name> |\n"
1024 : " out <file_name> <database_name> [<owner>.]<object_name>\n"
1025 : " [[<owner>.]<object_name>...] }\n"
1026 : "\n OR\n\n"
1027 : " %s [-U username] [-P password]\n"
1028 : " [-S servername] [-D database]\n"
1029 : " [-i input filename] [-o output filename]\n"
1030 : " [owner.]object_name [[owner.]object_name...]\n",
1031 : invoked_as, invoked_as);
1032 : }
1033 :
1034 : static LOGINREC *
1035 120 : get_login(char *argv0, OPTIONS *options)
1036 : {
1037 : LOGINREC *login;
1038 :
1039 120 : assert(options);
1040 :
1041 120 : options->appname = basename(argv0);
1042 :
1043 120 : login = dblogin();
1044 :
1045 120 : if (!login) {
1046 0 : fprintf(stderr, "%s: unable to allocate login structure\n", options->appname);
1047 0 : return NULL;
1048 : }
1049 :
1050 120 : DBSETLAPP(login, options->appname);
1051 :
1052 120 : if (-1 != gethostname(options->hostname, sizeof(options->hostname))) {
1053 120 : DBSETLHOST(login, options->hostname);
1054 : }
1055 : return login;
1056 : }
1057 :
1058 : static int
1059 120 : process_args(LOGINREC *login, int argc, char **argv, OPTIONS *options)
1060 : {
1061 : char *password;
1062 : int ch;
1063 120 : int fdomain = TRUE;
1064 :
1065 : extern char *optarg;
1066 : extern int optind;
1067 :
1068 120 : if (!login)
1069 : return EXIT_FAILURE;
1070 :
1071 570 : while ((ch = getopt(argc, argv, "U:P:S:d:D:i:o:v")) != -1) {
1072 450 : switch (ch) {
1073 120 : case 'U':
1074 120 : DBSETLUSER(login, optarg);
1075 120 : fdomain = FALSE;
1076 120 : break;
1077 120 : case 'P':
1078 120 : password = tds_getpassarg(optarg);
1079 120 : if (!password) {
1080 0 : fprintf(stderr, "Error getting password\n");
1081 0 : return EXIT_FAILURE;
1082 : }
1083 120 : DBSETLPWD(login, password);
1084 : memset(password, 0, strlen(password));
1085 120 : free(password);
1086 120 : password = NULL;
1087 120 : fdomain = FALSE;
1088 120 : break;
1089 120 : case 'S':
1090 120 : options->servername = xstrdup(optarg);
1091 120 : break;
1092 70 : case 'd':
1093 : case 'D':
1094 70 : options->database = xstrdup(optarg);
1095 70 : break;
1096 10 : case 'i':
1097 10 : options->input_filename = xstrdup(optarg);
1098 10 : break;
1099 10 : case 'o':
1100 10 : options->output_filename = xstrdup(optarg);
1101 10 : break;
1102 0 : case 'v':
1103 0 : printf("%s\n\n%s", argv[0],
1104 : "Copyright (C) 2004-2011 James K. Lowden\n\n"
1105 : "This program is free software; you can redistribute it and/or\n"
1106 : "modify it under the terms of the GNU General Public\n"
1107 : "License as published by the Free Software Foundation; either\n"
1108 : "version 2 of the License, or (at your option) any later version.\n\n"
1109 : "This library is distributed in the hope that it will be useful,\n"
1110 : "but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
1111 : "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n"
1112 : "Library General Public License for more details.\n\n"
1113 : "You should have received a copy of the GNU General Public\n"
1114 : "License along with this library; if not, write to the\n"
1115 : "Free Software Foundation, Inc., 59 Temple Place - Suite 330,\n"
1116 : "Boston, MA 02111-1307, USA.\n");
1117 0 : return EXIT_FAILURE;
1118 : case '?':
1119 : default:
1120 : return EXIT_FAILURE;
1121 : }
1122 : }
1123 :
1124 : #if defined(MicrosoftsDbLib) && defined(_WIN32)
1125 : if (fdomain)
1126 : DBSETLSECURE(login);
1127 : #else
1128 120 : if (fdomain)
1129 0 : DBSETLNETWORKAUTH(login, TRUE);
1130 : #endif /* MicrosoftsDbLib */
1131 :
1132 : /* Server name must be present */
1133 120 : if (!options->servername)
1134 : return EXIT_FAILURE;
1135 :
1136 : /* Does not make sense to specify both input and output filenames */
1137 120 : if (options->input_filename && options->output_filename) {
1138 0 : fprintf(stderr, "%s: Cannot specify both input and output files.\n", options->appname);
1139 0 : return EXIT_FAILURE;
1140 : }
1141 :
1142 : /* We support two different syntaxes: Sybase, and FreeTDS traditional.
1143 : * We will take the presence of -D , -i, or -o as meaning FreeTDS syntax;
1144 : * "in" or "out" as Sybase syntax; and if all are absent, treat it as
1145 : * FreeTDS syntax which allows -i and -o to be omitted, meaning that
1146 : * stdin/stdout should be used.
1147 : */
1148 120 : if (!(options->database || options->input_filename || options->output_filename)
1149 50 : && optind + 3 <= argc) {
1150 : /* If we are using Sybase syntax, then we're going to consume 3
1151 : * non-switch arguments: direction, filename, and database name.
1152 : */
1153 :
1154 50 : if (strncasecmp(argv[optind], "in", 2) == 0) {
1155 20 : optind++; /* consume direction */
1156 20 : options->input_filename = xstrdup(argv[optind]);
1157 20 : optind++; /* consume filename */
1158 20 : options->sybase_syntax = true;
1159 30 : } else if (strncasecmp(argv[optind], "out", 3) == 0) {
1160 30 : optind++; /* consume direction */
1161 30 : options->output_filename = xstrdup(argv[optind]);
1162 30 : optind++; /* consume filename */
1163 30 : options->sybase_syntax = true;
1164 : }
1165 50 : if (options->sybase_syntax) {
1166 50 : options->database = xstrdup(argv[optind]);
1167 50 : optind++; /* consume database name */
1168 : }
1169 : }
1170 :
1171 : /* Does not make sense to request input but provide objects;
1172 : * or request output but not provide them */
1173 120 : if (options->input_filename && optind < argc) {
1174 0 : fprintf(stderr, "%s: Cannot specify both input file and objects to output.\n", options->appname);
1175 0 : return EXIT_FAILURE;
1176 : }
1177 120 : if (options->output_filename && optind == argc) {
1178 0 : fprintf(stderr, "%s: Output mode specified, but no object names given.\n", options->appname);
1179 0 : return EXIT_FAILURE;
1180 : }
1181 :
1182 : /* optind should now point at the objects, if any, to read. */
1183 120 : options->optind = optind;
1184 120 : return 0;
1185 : }
1186 :
1187 : static int
1188 : #ifndef MicrosoftsDbLib
1189 0 : err_handler(DBPROCESS * dbproc TDS_UNUSED, int severity, int dberr, int oserr TDS_UNUSED,
1190 : char *dberrstr, char *oserrstr TDS_UNUSED)
1191 : #else
1192 : err_handler(DBPROCESS * dbproc TDS_UNUSED, int severity, int dberr, int oserr TDS_UNUSED,
1193 : const char dberrstr[], const char oserrstr[] TDS_UNUSED)
1194 : #endif /* MicrosoftsDbLib */
1195 : {
1196 :
1197 0 : if (dberr) {
1198 0 : fprintf(stderr, "%s: Msg %d, Level %d\n", options.appname, dberr, severity);
1199 0 : fprintf(stderr, "%s\n\n", dberrstr);
1200 : } else {
1201 0 : fprintf(stderr, "%s: DB-LIBRARY error:\n\t", options.appname);
1202 0 : fprintf(stderr, "%s\n", dberrstr);
1203 : }
1204 :
1205 0 : return INT_CANCEL;
1206 : }
1207 :
1208 : static int
1209 : #ifndef MicrosoftsDbLib
1210 1164 : msg_handler(DBPROCESS * dbproc TDS_UNUSED, DBINT msgno, int msgstate TDS_UNUSED, int severity TDS_UNUSED, char *msgtext,
1211 : char *srvname TDS_UNUSED, char *procname TDS_UNUSED, int line TDS_UNUSED)
1212 : #else
1213 : msg_handler(DBPROCESS * dbproc TDS_UNUSED, DBINT msgno, int msgstate TDS_UNUSED, int severity TDS_UNUSED, const char msgtext[],
1214 : const char srvname[] TDS_UNUSED, const char procname[] TDS_UNUSED, unsigned short int line TDS_UNUSED)
1215 : #endif /* MicrosoftsDbLib */
1216 : {
1217 : char *dbname, *endquote;
1218 :
1219 1164 : switch (msgno) {
1220 144 : case 5701: /* Print "USE dbname" for "Changed database context to 'dbname'" */
1221 144 : dbname = strchr(msgtext, '\'');
1222 144 : if (!dbname)
1223 : break;
1224 144 : endquote = strchr(++dbname, '\'');
1225 144 : if (!endquote)
1226 : break;
1227 144 : *endquote = '\0';
1228 144 : sprintf(use_statement, "USE %s\nGO\n\n", quote_id(dbname));
1229 : tmp_free();
1230 : return 0;
1231 :
1232 : case 0: /* Ignore print messages */
1233 : case 5703: /* Ignore "Changed language setting to <language>". */
1234 : return 0;
1235 :
1236 : default:
1237 : break;
1238 : }
1239 :
1240 : #if 0
1241 : printf("Msg %ld, Severity %d, State %d\n", (long) msgno, severity, msgstate);
1242 :
1243 : if (strlen(srvname) > 0)
1244 : printf("Server '%s', ", srvname);
1245 : if (strlen(procname) > 0)
1246 : printf("Procedure '%s', ", procname);
1247 : if (line > 0)
1248 : printf("Line %d", line);
1249 : #endif
1250 0 : printf("\t/* %s */\n", msgtext);
1251 :
1252 248 : return 0;
1253 : }
|