diff --git a/include/sybdb.h b/include/sybdb.h index 7765f495f7..824b2dba9a 100644 --- a/include/sybdb.h +++ b/include/sybdb.h @@ -1321,6 +1321,7 @@ RETCODE bcp_colfmt_ps(DBPROCESS * dbproc, int host_column, int host_type, int ho RETCODE bcp_colptr(DBPROCESS * dbproc, BYTE * colptr, int table_column); RETCODE bcp_control(DBPROCESS * dbproc, int field, DBINT value); int bcp_getbatchsize(DBPROCESS * dbproc); /* FreeTDS only */ +int bcp_gethostcolcount(DBPROCESS * dbproc); /* FreeTDS only */ RETCODE bcp_exec(DBPROCESS * dbproc, DBINT * rows_copied); DBBOOL bcp_getl(LOGINREC * login); RETCODE bcp_options(DBPROCESS * dbproc, int option, BYTE * value, int valuelen); diff --git a/src/apps/freebcp.c b/src/apps/freebcp.c index 4a5553b0af..d567dad9f3 100644 --- a/src/apps/freebcp.c +++ b/src/apps/freebcp.c @@ -66,21 +66,29 @@ #include "freebcp.h" +enum +{ + BCPFORMAT_NONE +, BCPFORMAT_CHARACTER +, BCPFORMAT_NATIVE +, BCPFORMAT_FORMATTED +}; +typedef int BCPFORMAT; + int tdsdump_open(const char *filename); -void pusage(void); -int process_parameters(int, char **, struct pd *); +static void pusage(void); +static int process_parameters(int, char **, BCPPARAMDATA *); static int unescape(char arg[]); -int login_to_database(struct pd *, DBPROCESS **); +static int login_to_database(BCPPARAMDATA * pdata, DBPROCESS ** pdbproc); -int file_character(BCPPARAMDATA * pdata, DBPROCESS * dbproc, DBINT dir); -int file_native(BCPPARAMDATA * pdata, DBPROCESS * dbproc, DBINT dir); -int file_formatted(BCPPARAMDATA * pdata, DBPROCESS * dbproc, DBINT dir); -int setoptions (DBPROCESS * dbproc, BCPPARAMDATA * params); - -int err_handler(DBPROCESS * dbproc, int severity, int dberr, int oserr, char *dberrstr, char *oserrstr); -int msg_handler(DBPROCESS * dbproc, DBINT msgno, int msgstate, int severity, char *msgtext, char *srvname, char *procname, +static int setoptions (DBPROCESS * dbproc, BCPPARAMDATA * params); +static BCPFORMAT get_format(BCPPARAMDATA* params); +static int file_process(BCPPARAMDATA* pdata, DBPROCESS* dbproc, DBINT dir); +static int err_handler(DBPROCESS * dbproc, int severity, int dberr, int oserr, char *dberrstr, char *oserrstr); +static int msg_handler(DBPROCESS * dbproc TDS_UNUSED, DBINT msgno, int msgstate, int severity, char *msgtext, char *srvname, char *procname, int line); + static int set_bcp_hints(BCPPARAMDATA *pdata, DBPROCESS *pdbproc); int @@ -116,21 +124,26 @@ main(int argc, char **argv) if (!setoptions(dbproc, ¶ms)) return FALSE; - if (params.cflag) { /* character format file */ - ok = file_character(¶ms, dbproc, params.direction); - } else if (params.nflag) { /* native format file */ - ok = file_native(¶ms, dbproc, params.direction); - } else if (params.fflag) { /* formatted file */ - ok = file_formatted(¶ms, dbproc, params.direction); - } else { - ok = FALSE; - } + ok = file_process(¶ms, dbproc, params.direction); exit((ok == TRUE) ? EXIT_SUCCESS : EXIT_FAILURE); return 0; } +static BCPFORMAT get_format(BCPPARAMDATA* params) +{ + if (params->cflag) + return BCPFORMAT_CHARACTER; + + if (params->nflag) + return BCPFORMAT_NATIVE; + + if (params->fflag) + return BCPFORMAT_FORMATTED; + + return BCPFORMAT_NONE; +} static int unescape(char arg[]) { @@ -168,7 +181,7 @@ static int unescape(char arg[]) return strchr(p, 0) - arg; } -int +static int process_parameters(int argc, char **argv, BCPPARAMDATA *pdata) { extern char *optarg; @@ -386,7 +399,30 @@ process_parameters(int argc, char **argv, BCPPARAMDATA *pdata) } -int +static int +process_Eflag(BCPPARAMDATA* pdata, DBPROCESS* dbproc) +{ + if (pdata->Eflag) { + + bcp_control(dbproc, BCPKEEPIDENTITY, 1); + + if (dbfcmd(dbproc, "set identity_insert %s on", pdata->dbobject) == FAIL) { + fprintf(stderr, "dbfcmd failed\n"); + return FALSE; + } + + if (dbsqlexec(dbproc) == FAIL) { + fprintf(stderr, "dbsqlexec failed\n"); + return FALSE; + } + + while (NO_MORE_RESULTS != dbresults(dbproc)) + continue; + } + return TRUE; +} + +static int login_to_database(BCPPARAMDATA * pdata, DBPROCESS ** pdbproc) { LOGINREC *login; @@ -455,111 +491,42 @@ login_to_database(BCPPARAMDATA * pdata, DBPROCESS ** pdbproc) } -int -file_character(BCPPARAMDATA * pdata, DBPROCESS * dbproc, DBINT dir) +static RETCODE format_column(BCPPARAMDATA* pdata, DBPROCESS* dbproc, BCPFORMAT file_format, int i, int is_last_column) { - DBINT li_rowsread = 0; - int i; - int li_numcols = 0; - RETCODE ret_code = 0; - - if (FAIL == bcp_init(dbproc, pdata->dbobject, pdata->hostfilename, pdata->errorfile, dir)) - return FALSE; - - if (!set_bcp_hints(pdata, dbproc)) - return FALSE; - - if (pdata->Eflag) { - - bcp_control(dbproc, BCPKEEPIDENTITY, 1); - - if (dbfcmd(dbproc, "set identity_insert %s on", pdata->dbobject) == FAIL) { - fprintf(stderr, "dbfcmd failed\n"); - return FALSE; - } - - if (dbsqlexec(dbproc) == FAIL) { - fprintf(stderr, "dbsqlexec failed\n"); - return FALSE; - } - - while (NO_MORE_RESULTS != dbresults(dbproc)) - continue; - } - - bcp_control(dbproc, BCPFIRST, pdata->firstrow); - bcp_control(dbproc, BCPLAST, pdata->lastrow); - bcp_control(dbproc, BCPMAXERRS, pdata->maxerrors); - - if (dir == DB_QUERYOUT) { - if (dbfcmd(dbproc, "SET FMTONLY ON %s SET FMTONLY OFF", pdata->dbobject) == FAIL) { - fprintf(stderr, "dbfcmd failed\n"); - return FALSE; - } - } else { - if (dbfcmd(dbproc, "SET FMTONLY ON select * from %s SET FMTONLY OFF", pdata->dbobject) == FAIL) { - fprintf(stderr, "dbfcmd failed\n"); - return FALSE; - } - } + int li_coltype = SYBCHAR; + int host_prefixlen = 0; + char* host_term = NULL; + int host_termlen = -1; - if (dbsqlexec(dbproc) == FAIL) { - fprintf(stderr, "dbsqlexec failed\n"); - return FALSE; - } - - while (NO_MORE_RESULTS != (ret_code = dbresults(dbproc))) { - if (ret_code == SUCCEED && li_numcols == 0) { - li_numcols = dbnumcols(dbproc); - } - } - - if (0 == li_numcols) { - fprintf(stderr, "Error in dbnumcols\n"); - return FALSE; - } - - if (bcp_columns(dbproc, li_numcols) == FAIL) { - fprintf(stderr, "Error in bcp_columns.\n"); - return FALSE; - } - - for (i = 1; i < li_numcols; ++i) { - if (bcp_colfmt(dbproc, i, SYBCHAR, 0, -1, (const BYTE *) pdata->fieldterm, - pdata->fieldtermlen, i) == FAIL) { - fprintf(stderr, "Error in bcp_colfmt col %d\n", i); - return FALSE; - } + if (file_format == BCPFORMAT_NATIVE) + { + li_coltype = dbcoltype(dbproc, i); + host_prefixlen = -1; } - - if (bcp_colfmt(dbproc, li_numcols, SYBCHAR, 0, -1, (const BYTE *) pdata->rowterm, - pdata->rowtermlen, li_numcols) == FAIL) { - fprintf(stderr, "Error in bcp_colfmt col %d\n", li_numcols); - return FALSE; + else if (is_last_column) + { + host_term = pdata->rowterm; + host_termlen = pdata->rowtermlen; } - - bcp_control(dbproc, BCPBATCH, pdata->batchsize); - - printf("\nStarting copy...\n"); - - if (FAIL == bcp_exec(dbproc, &li_rowsread)) { - fprintf(stderr, "bcp copy %s failed\n", (dir == DB_IN) ? "in" : "out"); - return FALSE; + else + { + host_term = pdata->fieldterm; + host_termlen = pdata->fieldtermlen; } - printf("%d rows copied.\n", li_rowsread); - - return TRUE; + return bcp_colfmt(dbproc, i, li_coltype, host_prefixlen, -1, (const BYTE *)host_term, host_termlen, i); } -int -file_native(BCPPARAMDATA * pdata, DBPROCESS * dbproc, DBINT dir) +static int file_process(BCPPARAMDATA * pdata, DBPROCESS* dbproc, DBINT dir) { DBINT li_rowsread = 0; - int i; - int li_numcols = 0; - int li_coltype; RETCODE ret_code = 0; + int i; + int li_numcols; + + BCPFORMAT file_format = get_format(pdata); + if (file_format == BCPFORMAT_NONE) + return FALSE; if (FAIL == bcp_init(dbproc, pdata->dbobject, pdata->hostfilename, pdata->errorfile, dir)) return FALSE; @@ -567,123 +534,43 @@ file_native(BCPPARAMDATA * pdata, DBPROCESS * dbproc, DBINT dir) if (!set_bcp_hints(pdata, dbproc)) return FALSE; - if (pdata->Eflag) { - - bcp_control(dbproc, BCPKEEPIDENTITY, 1); - - if (dbfcmd(dbproc, "set identity_insert %s on", pdata->dbobject) == FAIL) { - fprintf(stderr, "dbfcmd failed\n"); - return FALSE; - } - - if (dbsqlexec(dbproc) == FAIL) { - fprintf(stderr, "dbsqlexec failed\n"); - return FALSE; - } - - while (NO_MORE_RESULTS != dbresults(dbproc)) - continue; - } - bcp_control(dbproc, BCPFIRST, pdata->firstrow); bcp_control(dbproc, BCPLAST, pdata->lastrow); bcp_control(dbproc, BCPMAXERRS, pdata->maxerrors); - if (dir == DB_QUERYOUT) { - if (dbfcmd(dbproc, "SET FMTONLY ON %s SET FMTONLY OFF", pdata->dbobject) == FAIL) { - fprintf(stderr, "dbfcmd failed\n"); - return FALSE; - } - } else { - if (dbfcmd(dbproc, "SET FMTONLY ON select * from %s SET FMTONLY OFF", pdata->dbobject) == FAIL) { - fprintf(stderr, "dbfcmd failed\n"); - return FALSE; - } - } - - if (dbsqlexec(dbproc) == FAIL) { - fprintf(stderr, "dbsqlexec failed\n"); - return FALSE; - } - - while (NO_MORE_RESULTS != (ret_code = dbresults(dbproc))) { - if (ret_code == SUCCEED && li_numcols == 0) { - li_numcols = dbnumcols(dbproc); - } - } - - if (0 == li_numcols) { - fprintf(stderr, "Error in dbnumcols\n"); - return FALSE; - } - - if (bcp_columns(dbproc, li_numcols) == FAIL) { - fprintf(stderr, "Error in bcp_columns.\n"); - return FALSE; - } - - for (i = 1; i <= li_numcols; i++) { - li_coltype = dbcoltype(dbproc, i); - - if (bcp_colfmt(dbproc, i, li_coltype, -1, -1, NULL, -1, i) == FAIL) { - fprintf(stderr, "Error in bcp_colfmt col %d\n", i); + switch (file_format) + { + case BCPFORMAT_FORMATTED: + if (FAIL == bcp_readfmt(dbproc, pdata->formatfile)) return FALSE; - } - } - - printf("\nStarting copy...\n\n"); + break; - - if (FAIL == bcp_exec(dbproc, &li_rowsread)) { - fprintf(stderr, "bcp copy %s failed\n", (dir == DB_IN) ? "in" : "out"); - return FALSE; - } - - printf("%d rows copied.\n", li_rowsread); - - return TRUE; -} - -int -file_formatted(BCPPARAMDATA * pdata, DBPROCESS * dbproc, DBINT dir) -{ - - int li_rowsread; - - if (FAIL == bcp_init(dbproc, pdata->dbobject, pdata->hostfilename, pdata->errorfile, dir)) - return FALSE; - - if (!set_bcp_hints(pdata, dbproc)) - return FALSE; - - if (pdata->Eflag) { - - bcp_control(dbproc, BCPKEEPIDENTITY, 1); - - if (dbfcmd(dbproc, "set identity_insert %s on", pdata->dbobject) == FAIL) { - fprintf(stderr, "dbfcmd failed\n"); - return FALSE; - } - - if (dbsqlexec(dbproc) == FAIL) { - fprintf(stderr, "dbsqlexec failed\n"); - return FALSE; + /* The data file does not use TDS nullable types. It uses non-nullable + * representations, and a Length prefix if the target column was nullable. + * The length prefix typically takes value 0 or -1 to indicate a null. + * So, we need to check format of all columns. + */ + case BCPFORMAT_NATIVE: + case BCPFORMAT_CHARACTER: + li_numcols = bcp_gethostcolcount(dbproc); + for (i = 1; i <= li_numcols; ++i) { + if (format_column(pdata, dbproc, file_format, i, i == li_numcols) == FAIL) { + fprintf(stderr, "Error in bcp_colfmt col %d\n", i); + return FALSE; + } } - - while (NO_MORE_RESULTS != dbresults(dbproc)) - continue; + break; } - bcp_control(dbproc, BCPFIRST, pdata->firstrow); - bcp_control(dbproc, BCPLAST, pdata->lastrow); - bcp_control(dbproc, BCPMAXERRS, pdata->maxerrors); + if (file_format == BCPFORMAT_CHARACTER) + bcp_control(dbproc, BCPBATCH, pdata->batchsize); - if (FAIL == bcp_readfmt(dbproc, pdata->formatfile)) - return FALSE; + /* note: process_Eflag frees data needed by format_column() for NATIVE type, + so call this after the column loop. */ + process_Eflag(pdata, dbproc); printf("\nStarting copy...\n\n"); - if (FAIL == bcp_exec(dbproc, &li_rowsread)) { fprintf(stderr, "bcp copy %s failed\n", (dir == DB_IN) ? "in" : "out"); return FALSE; @@ -694,8 +581,7 @@ file_formatted(BCPPARAMDATA * pdata, DBPROCESS * dbproc, DBINT dir) return TRUE; } - -int +static int setoptions(DBPROCESS * dbproc, BCPPARAMDATA * params) { RETCODE fOK; @@ -770,7 +656,7 @@ set_bcp_hints(BCPPARAMDATA *pdata, DBPROCESS *pdbproc) return TRUE; } -void +static void pusage(void) { fprintf(stderr, "usage: freebcp [[database_name.]owner.]table_name|query {in | out | queryout } datafile\n"); @@ -785,7 +671,7 @@ pusage(void) fprintf(stderr, "example: freebcp testdb.dbo.inserttest in inserttest.txt -S mssql -U guest -P password -c\n"); } -int +static int err_handler(DBPROCESS * dbproc, int severity, int dberr, int oserr TDS_UNUSED, char *dberrstr, char *oserrstr TDS_UNUSED) { static int sent = 0; @@ -807,7 +693,7 @@ err_handler(DBPROCESS * dbproc, int severity, int dberr, int oserr TDS_UNUSED, c return INT_CANCEL; } -int +static int msg_handler(DBPROCESS * dbproc TDS_UNUSED, DBINT msgno, int msgstate, int severity, char *msgtext, char *srvname, char *procname, int line) { diff --git a/src/dblib/bcp.c b/src/dblib/bcp.c index 3f7ef30ab7..177327634c 100644 --- a/src/dblib/bcp.c +++ b/src/dblib/bcp.c @@ -581,10 +581,10 @@ bcp_control(DBPROCESS * dbproc, int field, DBINT value) /* * \ingroup dblib_bcp * \brief Get BCP batch option - * + * * \param dbproc contains all information needed by db-lib to manage communications with the server. - * \remarks This function is specific to FreeTDS. - * + * \remarks This function is specific to FreeTDS. + * * \return the value that was set by bcp_control. * \sa bcp_batch(), bcp_control() */ @@ -595,6 +595,25 @@ bcp_getbatchsize(DBPROCESS * dbproc) CHECK_PARAMETER(dbproc->hostfileinfo, SYBEBCPI, -1); return dbproc->hostfileinfo->batch; } +/* + * \ingroup dblib_bcp + * \brief Get number of columns in the BCP data file + * + * \param dbproc contains all information needed by db-lib to manage communications with the server. + * \remarks This function is specific to FreeTDS. + * + * \return the value that was set by bcp_columns(). This represents the number + * of columns of data in the saved data file. Usually the same as the number + * of columns in the database table, but could be fewer if a Format File is + * in use which only selects a subset of the table columns. + * + * \sa bcp_columns() + */ +int +bcp_gethostcolcount(DBPROCESS* dbproc) +{ + return dbproc->hostfileinfo->host_colcount; +} /** * \ingroup dblib_bcp diff --git a/src/dblib/dblib.def b/src/dblib/dblib.def index dc7e58cf6e..5d5d943260 100644 --- a/src/dblib/dblib.def +++ b/src/dblib/dblib.def @@ -10,6 +10,7 @@ EXPORTS bcp_done bcp_exec bcp_getbatchsize + bcp_gethostcolcount bcp_getl bcp_init bcp_options