Drizzled Public API Documentation

drizzleimport.cc
00001 /* 
00002   Copyright (C) 2010 Vijay Samuel
00003   Copyright (C) 2010 Brian Aker
00004   Copyright (C) 2000-2006 MySQL AB
00005   Copyright (C) 2008-2009 Sun Microsystems, Inc.
00006 
00007   This program is free software; you can redistribute it and/or modify
00008   it under the terms of the GNU General Public License as published by
00009   the Free Software Foundation; version 2 of the License.
00010 
00011   This program is distributed in the hope that it will be useful,
00012   but WITHOUT ANY WARRANTY; without even the implied warranty of
00013   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00014   GNU General Public License for more details.
00015 
00016   You should have received a copy of the GNU General Public License
00017   along with this program; if not, write to the Free Software
00018   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA */
00019 
00020 #define IMPORT_VERSION "4.0"
00021 
00022 #include "client_priv.h"
00023 #include <string>
00024 #include <sstream>
00025 #include <iostream>
00026 #include <fstream>
00027 #include <boost/program_options.hpp>
00028 #include <pthread.h>
00029 
00030 #include <drizzled/definitions.h>
00031 #include <drizzled/internal/my_sys.h>
00032 /* Added this for string translation. */
00033 #include <drizzled/gettext.h>
00034 #include <drizzled/configmake.h>
00035 
00036 #include "user_detect.h"
00037 
00038 namespace po= boost::program_options;
00039 using namespace std;
00040 using namespace drizzled;
00041 
00042 extern "C" void * worker_thread(void *arg);
00043 
00044 int exitcode= 0;
00045 
00046 const char *program_name= "drizzleimport";
00047 
00048 /* Global Thread counter */
00049 uint32_t counter;
00050 pthread_mutex_t counter_mutex;
00051 pthread_cond_t count_threshhold;
00052 
00053 static void db_error(drizzle_con_st *con, drizzle_result_st *result,
00054                      drizzle_return_t ret, char *table);
00055 static char *field_escape(char *to,const char *from,uint32_t length);
00056 static char *add_load_option(char *ptr,const char *object,
00057            const char *statement);
00058 
00059 static bool verbose= false, ignore_errors= false,
00060             opt_delete= false, opt_replace= false, silent= false,
00061             ignore_unique= false, opt_low_priority= false,
00062             use_drizzle_protocol= false, opt_local_file;
00063 
00064 static uint32_t opt_use_threads;
00065 static uint32_t opt_drizzle_port= 0;
00066 static int64_t opt_ignore_lines= -1;
00067 
00068 std::string opt_columns,
00069   opt_enclosed,
00070   escaped,
00071   password,
00072   current_db,
00073   lines_terminated,
00074   current_user,
00075   opt_password,
00076   enclosed,  
00077   current_host,
00078   fields_terminated,
00079   opt_protocol;
00080 
00081 
00082 static int get_options(void)
00083 {
00084 
00085   if (! enclosed.empty() && ! opt_enclosed.empty())
00086   {
00087     fprintf(stderr, "You can't use ..enclosed.. and ..optionally-enclosed.. at the same time.\n");
00088     return(1);
00089   }
00090   if (opt_replace && ignore_unique)
00091   {
00092     fprintf(stderr, "You can't use --ignore_unique (-i) and --replace (-r) at the same time.\n");
00093     return(1);
00094   }
00095 
00096   if (tty_password)
00097     opt_password=client_get_tty_password(NULL);
00098   return(0);
00099 }
00100 
00101 
00102 
00103 static int write_to_table(char *filename, drizzle_con_st *con)
00104 {
00105   char tablename[FN_REFLEN], hard_path[FN_REFLEN],
00106        sql_statement[FN_REFLEN*16+256], *end;
00107   drizzle_result_st result;
00108   drizzle_return_t ret;
00109 
00110   internal::fn_format(tablename, filename, "", "", 1 | 2); /* removes path & ext. */
00111   if (not opt_local_file)
00112     strcpy(hard_path,filename);
00113   else
00114     internal::my_load_path(hard_path, filename, NULL); /* filename includes the path */
00115 
00116   if (opt_delete)
00117   {
00118     if (verbose)
00119       fprintf(stdout, "Deleting the old data from table %s\n", tablename);
00120 #ifdef HAVE_SNPRINTF
00121     snprintf(sql_statement, sizeof(sql_statement), "DELETE FROM %s", tablename);
00122 #else
00123     snprintf(sql_statement, sizeof(sql_statement), "DELETE FROM %s", tablename);
00124 #endif
00125     if (drizzle_query_str(con, &result, sql_statement, &ret) == NULL ||
00126         ret != DRIZZLE_RETURN_OK)
00127     {
00128       db_error(con, &result, ret, tablename);
00129       return(1);
00130     }
00131     drizzle_result_free(&result);
00132   }
00133   if (verbose)
00134   {
00135     if (opt_local_file)
00136       fprintf(stdout, "Loading data from LOCAL file: %s into %s\n",
00137         hard_path, tablename);
00138     else
00139       fprintf(stdout, "Loading data from SERVER file: %s into %s\n",
00140         hard_path, tablename);
00141   }
00142   snprintf(sql_statement, sizeof(sql_statement), "LOAD DATA %s %s INFILE '%s'",
00143     opt_low_priority ? "LOW_PRIORITY" : "",
00144     opt_local_file ? "LOCAL" : "", hard_path);
00145   end= strchr(sql_statement, '\0');
00146   if (opt_replace)
00147     end= strcpy(end, " REPLACE")+8;
00148   if (ignore_unique)
00149     end= strcpy(end, " IGNORE")+7;
00150 
00151   end+= sprintf(end, " INTO TABLE %s", tablename);
00152 
00153   if (! fields_terminated.empty() || ! enclosed.empty() || ! opt_enclosed.empty() || ! escaped.empty())
00154       end= strcpy(end, " FIELDS")+7;
00155   end= add_load_option(end, (char *)fields_terminated.c_str(), " TERMINATED BY");
00156   end= add_load_option(end, (char *)enclosed.c_str(), " ENCLOSED BY");
00157   end= add_load_option(end, (char *)opt_enclosed.c_str(),
00158            " OPTIONALLY ENCLOSED BY");
00159   end= add_load_option(end, (char *)escaped.c_str(), " ESCAPED BY");
00160   end= add_load_option(end, (char *)lines_terminated.c_str(), " LINES TERMINATED BY");
00161   if (opt_ignore_lines >= 0)
00162   {
00163     end= strcpy(end, " IGNORE ")+8;
00164     ostringstream buffer;
00165     buffer << opt_ignore_lines;
00166     end= strcpy(end, buffer.str().c_str())+ buffer.str().size();
00167     end= strcpy(end, " LINES")+6;
00168   }
00169   if (! opt_columns.empty())
00170   {
00171     end= strcpy(end, " (")+2;
00172     end= strcpy(end, (char *)opt_columns.c_str()+opt_columns.length());
00173     end= strcpy(end, ")")+1;
00174   }
00175   *end= '\0';
00176 
00177   if (drizzle_query_str(con, &result, sql_statement, &ret) == NULL ||
00178       ret != DRIZZLE_RETURN_OK)
00179   {
00180     db_error(con, &result, ret, tablename);
00181     return(1);
00182   }
00183   if (!silent)
00184   {
00185     if (strcmp(drizzle_result_info(&result), ""))
00186     {
00187       fprintf(stdout, "%s.%s: %s\n", current_db.c_str(), tablename,
00188         drizzle_result_info(&result));
00189     }
00190   }
00191   drizzle_result_free(&result);
00192   return(0);
00193 }
00194 
00195 
00196 static drizzle_con_st *db_connect(const string host, const string database,
00197                                   const string user, const string passwd)
00198 {
00199   drizzle_st *drizzle;
00200   drizzle_con_st *con;
00201   drizzle_return_t ret;
00202 
00203   if (verbose)
00204   {
00205     fprintf(stdout, "Connecting to %s, using protocol %s...\n", ! host.empty() ? host.c_str() : "localhost", opt_protocol.c_str());
00206   }
00207 
00208   if ((drizzle= drizzle_create()) == NULL)
00209   {
00210     return 0;
00211   }
00212 
00213   if (!(con= drizzle_con_add_tcp(drizzle,
00214                                  host.c_str(), opt_drizzle_port,
00215                                  user.c_str(), passwd.c_str(),
00216                                  database.c_str(),
00217                                  use_drizzle_protocol ? DRIZZLE_CON_EXPERIMENTAL : DRIZZLE_CON_MYSQL)))
00218   {
00219     return 0;
00220   }
00221 
00222   if ((ret= drizzle_con_connect(con)) != DRIZZLE_RETURN_OK)
00223   {
00224     ignore_errors=0;    /* NO RETURN FROM db_error */
00225     db_error(con, NULL, ret, NULL);
00226   }
00227 
00228   if (verbose)
00229     fprintf(stdout, "Selecting database %s\n", database.c_str());
00230 
00231   return con;
00232 }
00233 
00234 
00235 
00236 static void db_disconnect(const string host, drizzle_con_st *con)
00237 {
00238   if (verbose)
00239     fprintf(stdout, "Disconnecting from %s\n", ! host.empty() ? host.c_str() : "localhost");
00240   drizzle_free(drizzle_con_drizzle(con));
00241 }
00242 
00243 
00244 
00245 static void safe_exit(int error, drizzle_con_st *con)
00246 {
00247   if (ignore_errors)
00248     return;
00249   if (con)
00250     drizzle_free(drizzle_con_drizzle(con));
00251   exit(error);
00252 }
00253 
00254 
00255 
00256 static void db_error(drizzle_con_st *con, drizzle_result_st *result,
00257                      drizzle_return_t ret, char *table)
00258 {
00259   if (ret == DRIZZLE_RETURN_ERROR_CODE)
00260   {
00261     fprintf(stdout, "Error: %d, %s%s%s",
00262             drizzle_result_error_code(result),
00263             drizzle_result_error(result),
00264             table ? ", when using table: " : "", table ? table : "");
00265     drizzle_result_free(result);
00266   }
00267   else
00268   {
00269     fprintf(stdout, "Error: %d, %s%s%s", ret, drizzle_con_error(con),
00270             table ? ", when using table: " : "", table ? table : "");
00271   }
00272 
00273   safe_exit(1, con);
00274 }
00275 
00276 
00277 static char *add_load_option(char *ptr, const char *object,
00278            const char *statement)
00279 {
00280   if (object)
00281   {
00282     /* Don't escape hex constants */
00283     if (object[0] == '0' && (object[1] == 'x' || object[1] == 'X'))
00284       ptr+= sprintf(ptr, " %s %s", statement, object);
00285     else
00286     {
00287       /* char constant; escape */
00288       ptr+= sprintf(ptr, " %s '", statement); 
00289       ptr= field_escape(ptr,object,(uint32_t) strlen(object));
00290       *ptr++= '\'';
00291     }
00292   }
00293   return ptr;
00294 }
00295 
00296 /*
00297 ** Allow the user to specify field terminator strings like:
00298 ** "'", "\", "\\" (escaped backslash), "\t" (tab), "\n" (newline)
00299 ** This is done by doubleing ' and add a end -\ if needed to avoid
00300 ** syntax errors from the SQL parser.
00301 */
00302 
00303 static char *field_escape(char *to,const char *from,uint32_t length)
00304 {
00305   const char *end;
00306   uint32_t end_backslashes=0;
00307 
00308   for (end= from+length; from != end; from++)
00309   {
00310     *to++= *from;
00311     if (*from == '\\')
00312       end_backslashes^=1;    /* find odd number of backslashes */
00313     else
00314     {
00315       if (*from == '\'' && !end_backslashes)
00316   *to++= *from;      /* We want a dublicate of "'" for DRIZZLE */
00317       end_backslashes=0;
00318     }
00319   }
00320   /* Add missing backslashes if user has specified odd number of backs.*/
00321   if (end_backslashes)
00322     *to++= '\\';
00323   return to;
00324 }
00325 
00326 void * worker_thread(void *arg)
00327 {
00328   int error;
00329   char *raw_table_name= (char *)arg;
00330   drizzle_con_st *con;
00331 
00332   if (!(con= db_connect(current_host,current_db,current_user,opt_password)))
00333   {
00334     return 0;
00335   }
00336 
00337   /*
00338     We are not currently catching the error here.
00339   */
00340   if ((error= write_to_table(raw_table_name, con)))
00341   {
00342     if (exitcode == 0)
00343     {
00344       exitcode= error;
00345     }
00346   }
00347 
00348   if (con)
00349   {
00350     db_disconnect(current_host, con);
00351   }
00352 
00353   pthread_mutex_lock(&counter_mutex);
00354   counter--;
00355   pthread_cond_signal(&count_threshhold);
00356   pthread_mutex_unlock(&counter_mutex);
00357 
00358   return 0;
00359 }
00360 
00361 
00362 int main(int argc, char **argv)
00363 {
00364 try
00365 {
00366   po::options_description commandline_options("Options used only in command line");
00367   commandline_options.add_options()
00368 
00369   ("debug,#", po::value<string>(),
00370   "Output debug log. Often this is 'd:t:o,filename'.")
00371   ("delete,d", po::value<bool>(&opt_delete)->default_value(false)->zero_tokens(),
00372   "First delete all rows from table.")
00373   ("help,?", "Displays this help and exits.")
00374   ("ignore,i", po::value<bool>(&ignore_unique)->default_value(false)->zero_tokens(),
00375   "If duplicate unique key was found, keep old row.")
00376   ("low-priority", po::value<bool>(&opt_low_priority)->default_value(false)->zero_tokens(),
00377   "Use LOW_PRIORITY when updating the table.")
00378   ("replace,r", po::value<bool>(&opt_replace)->default_value(false)->zero_tokens(),
00379   "If duplicate unique key was found, replace old row.")
00380   ("verbose,v", po::value<bool>(&verbose)->default_value(false)->zero_tokens(),
00381   "Print info about the various stages.")
00382   ("version,V", "Output version information and exit.")
00383   ;
00384 
00385   po::options_description import_options("Options specific to the drizzleimport");
00386   import_options.add_options()
00387   ("columns,C", po::value<string>(&opt_columns)->default_value(""),
00388   "Use only these columns to import the data to. Give the column names in a comma separated list. This is same as giving columns to LOAD DATA INFILE.")
00389   ("fields-terminated-by", po::value<string>(&fields_terminated)->default_value(""),
00390   "Fields in the textfile are terminated by ...")
00391   ("fields-enclosed-by", po::value<string>(&enclosed)->default_value(""),
00392   "Fields in the importfile are enclosed by ...")
00393   ("fields-optionally-enclosed-by", po::value<string>(&opt_enclosed)->default_value(""),
00394   "Fields in the i.file are opt. enclosed by ...")
00395   ("fields-escaped-by", po::value<string>(&escaped)->default_value(""),
00396   "Fields in the i.file are escaped by ...")
00397   ("force,f", po::value<bool>(&ignore_errors)->default_value(false)->zero_tokens(),
00398   "Continue even if we get an sql-error.")
00399   ("ignore-lines", po::value<int64_t>(&opt_ignore_lines)->default_value(0),
00400   "Ignore first n lines of data infile.")
00401   ("lines-terminated-by", po::value<string>(&lines_terminated)->default_value(""),
00402   "Lines in the i.file are terminated by ...")
00403   ("local,L", po::value<bool>(&opt_local_file)->default_value(false)->zero_tokens(),
00404   "Read all files through the client.")
00405   ("silent,s", po::value<bool>(&silent)->default_value(false)->zero_tokens(),
00406   "Be more silent.")
00407   ("use-threads", po::value<uint32_t>(&opt_use_threads)->default_value(4),
00408   "Load files in parallel. The argument is the number of threads to use for loading data (default is 4.")
00409   ;
00410 
00411   po::options_description client_options("Options specific to the client");
00412   client_options.add_options()
00413   ("host,h", po::value<string>(&current_host)->default_value("localhost"),
00414   "Connect to host.")
00415   ("password,P", po::value<string>(&password),
00416   "Password to use when connecting to server. If password is not given it's asked from the tty." )
00417   ("port,p", po::value<uint32_t>(&opt_drizzle_port)->default_value(0),
00418   "Port number to use for connection") 
00419   ("protocol", po::value<string>(&opt_protocol)->default_value("mysql"),
00420   "The protocol of connection (mysql or drizzle).")
00421   ("user,u", po::value<string>(&current_user)->default_value(UserDetect().getUser()),
00422   "User for login if not current user.")
00423   ;
00424 
00425   po::options_description long_options("Allowed Options");
00426   long_options.add(commandline_options).add(import_options).add(client_options);
00427 
00428   std::string system_config_dir_import(SYSCONFDIR); 
00429   system_config_dir_import.append("/drizzle/drizzleimport.cnf");
00430 
00431   std::string system_config_dir_client(SYSCONFDIR); 
00432   system_config_dir_client.append("/drizzle/client.cnf");
00433   
00434   std::string user_config_dir((getenv("XDG_CONFIG_HOME")? getenv("XDG_CONFIG_HOME"):"~/.config"));
00435 
00436   if (user_config_dir.compare(0, 2, "~/") == 0)
00437   {
00438     char *homedir;
00439     homedir= getenv("HOME");
00440     if (homedir != NULL)
00441       user_config_dir.replace(0, 1, homedir);
00442   }
00443 
00444   po::variables_map vm;
00445 
00446   // Disable allow_guessing
00447   int style = po::command_line_style::default_style & ~po::command_line_style::allow_guessing;
00448 
00449   po::store(po::command_line_parser(argc, argv).options(long_options).
00450             style(style).extra_parser(parse_password_arg).run(), vm);
00451 
00452   std::string user_config_dir_import(user_config_dir);
00453   user_config_dir_import.append("/drizzle/drizzleimport.cnf"); 
00454 
00455   std::string user_config_dir_client(user_config_dir);
00456   user_config_dir_client.append("/drizzle/client.cnf");
00457 
00458   ifstream user_import_ifs(user_config_dir_import.c_str());
00459   po::store(parse_config_file(user_import_ifs, import_options), vm);
00460 
00461   ifstream user_client_ifs(user_config_dir_client.c_str());
00462   po::store(parse_config_file(user_client_ifs, client_options), vm);
00463 
00464   ifstream system_import_ifs(system_config_dir_import.c_str());
00465   store(parse_config_file(system_import_ifs, import_options), vm);
00466  
00467   ifstream system_client_ifs(system_config_dir_client.c_str());
00468   po::store(parse_config_file(system_client_ifs, client_options), vm);
00469 
00470   po::notify(vm);
00471   if (vm.count("protocol"))
00472   {
00473     boost::to_lower(opt_protocol);
00474     if (not opt_protocol.compare("mysql"))
00475       use_drizzle_protocol=false;
00476     else if (not opt_protocol.compare("drizzle"))
00477       use_drizzle_protocol=true;
00478     else
00479     {
00480       cout << _("Error: Unknown protocol") << " '" << opt_protocol << "'" << endl;
00481       exit(-1);
00482     }
00483   }
00484 
00485   if (vm.count("port"))
00486   {
00487     
00488     /* If the port number is > 65535 it is not a valid port
00489        This also helps with potential data loss casting unsigned long to a
00490        uint32_t. */
00491     if (opt_drizzle_port > 65535)
00492     {
00493       fprintf(stderr, _("Value supplied for port is not valid.\n"));
00494       exit(EXIT_ARGUMENT_INVALID);
00495     }
00496   }
00497 
00498   if( vm.count("password") )
00499   {
00500     if (!opt_password.empty())
00501       opt_password.erase();
00502     if (password == PASSWORD_SENTINEL)
00503     {
00504       opt_password= "";
00505     }
00506     else
00507     {
00508       opt_password= password;
00509       tty_password= false;
00510     }
00511   }
00512   else
00513   {
00514       tty_password= true;
00515   }
00516 
00517 
00518   if (vm.count("version"))
00519   {
00520     printf("%s  Ver %s Distrib %s, for %s-%s (%s)\n", program_name,
00521     IMPORT_VERSION, drizzle_version(),HOST_VENDOR,HOST_OS,HOST_CPU);
00522   }
00523   
00524   if (vm.count("help") || argc < 2)
00525   {
00526     printf("%s  Ver %s Distrib %s, for %s-%s (%s)\n", program_name,
00527     IMPORT_VERSION, drizzle_version(),HOST_VENDOR,HOST_OS,HOST_CPU);
00528     puts("This software comes with ABSOLUTELY NO WARRANTY. This is free software,\nand you are welcome to modify and redistribute it under the GPL license\n");
00529     printf("\
00530     Loads tables from text files in various formats.  The base name of the\n\
00531     text file must be the name of the table that should be used.\n\
00532     If one uses sockets to connect to the Drizzle server, the server will open and\n\
00533     read the text file directly. In other cases the client will open the text\n\
00534     file. The SQL command 'LOAD DATA INFILE' is used to import the rows.\n");
00535 
00536     printf("\nUsage: %s [OPTIONS] database textfile...", program_name);
00537     cout<<long_options;
00538     exit(0);
00539   }
00540 
00541 
00542   if (get_options())
00543   {
00544     return(1);
00545   }
00546   
00547   current_db= (*argv)++;
00548   argc--;
00549 
00550   if (opt_use_threads)
00551   {
00552     pthread_t mainthread;            /* Thread descriptor */
00553     pthread_attr_t attr;          /* Thread attributes */
00554     pthread_attr_init(&attr);
00555     pthread_attr_setdetachstate(&attr,
00556                                 PTHREAD_CREATE_DETACHED);
00557 
00558     pthread_mutex_init(&counter_mutex, NULL);
00559     pthread_cond_init(&count_threshhold, NULL);
00560 
00561     for (counter= 0; *argv != NULL; argv++) /* Loop through tables */
00562     {
00563       pthread_mutex_lock(&counter_mutex);
00564       while (counter == opt_use_threads)
00565       {
00566         struct timespec abstime;
00567 
00568         set_timespec(abstime, 3);
00569         pthread_cond_timedwait(&count_threshhold, &counter_mutex, &abstime);
00570       }
00571       /* Before exiting the lock we set ourselves up for the next thread */
00572       counter++;
00573       pthread_mutex_unlock(&counter_mutex);
00574       /* now create the thread */
00575       if (pthread_create(&mainthread, &attr, worker_thread,
00576                          (void *)*argv) != 0)
00577       {
00578         pthread_mutex_lock(&counter_mutex);
00579         counter--;
00580         pthread_mutex_unlock(&counter_mutex);
00581         fprintf(stderr,"%s: Could not create thread\n", program_name);
00582       }
00583     }
00584 
00585     /*
00586       We loop until we know that all children have cleaned up.
00587     */
00588     pthread_mutex_lock(&counter_mutex);
00589     while (counter)
00590     {
00591       struct timespec abstime;
00592 
00593       set_timespec(abstime, 3);
00594       pthread_cond_timedwait(&count_threshhold, &counter_mutex, &abstime);
00595     }
00596     pthread_mutex_unlock(&counter_mutex);
00597     pthread_mutex_destroy(&counter_mutex);
00598     pthread_cond_destroy(&count_threshhold);
00599     pthread_attr_destroy(&attr);
00600   }
00601   else
00602   {
00603     drizzle_con_st *con;
00604 
00605     if (!(con= db_connect(current_host,current_db,current_user,opt_password)))
00606     {
00607       return(1);
00608     }
00609 
00610     for (; *argv != NULL; argv++)
00611       if (int error= write_to_table(*argv, con))
00612         if (exitcode == 0)
00613           exitcode= error;
00614     db_disconnect(current_host, con);
00615   }
00616   opt_password.empty();
00617 }
00618   catch(exception &err)
00619   {
00620     cerr<<err.what()<<endl;
00621   }
00622   return(exitcode);
00623 }