X-Git-Url: https://git.sur5r.net/?a=blobdiff_plain;f=bacula%2Fsrc%2Fcats%2Fsqlite.c;h=bc2458a731f167b54595849bed63f973f2c11d69;hb=c9657772721a3782558c4eb8b99978c7873b45fd;hp=1e9781906ecaa0a82b09cf191ad9a94ca28bc628;hpb=9f8c89965e9f2d7e4fb55e9fd79a2aa83b85c985;p=bacula%2Fbacula diff --git a/bacula/src/cats/sqlite.c b/bacula/src/cats/sqlite.c index 1e9781906e..bc2458a731 100644 --- a/bacula/src/cats/sqlite.c +++ b/bacula/src/cats/sqlite.c @@ -1,41 +1,48 @@ /* - * Bacula Catalog Database routines specific to SQLite - * - * Kern Sibbald, January 2002 - * - * Version $Id$ - */ + Bacula® - The Network Backup Solution -/* - Copyright (C) 2002 Kern Sibbald and John Walker + Copyright (C) 2000-2010 Free Software Foundation Europe e.V. - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of - the License, or (at your option) any later version. + The main author of Bacula is Kern Sibbald, with contributions from + many others, a complete list can be found in the file AUTHORS. + This program is Free Software; you can redistribute it and/or + modify it under the terms of version three of the GNU Affero General Public + License as published by the Free Software Foundation and included + in the file LICENSE. - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU General Public - License along with this program; if not, write to the Free - Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, - MA 02111-1307, USA. + You should have received a copy of the GNU Affero General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301, USA. + Bacula® is a registered trademark of Kern Sibbald. + The licensor of Bacula is the Free Software Foundation Europe + (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zürich, + Switzerland, email:ftf@fsfeurope.org. +*/ +/* + * Bacula Catalog Database routines specific to SQLite + * + * Kern Sibbald, January 2002 + * */ + /* The following is necessary so that we do not include * the dummy external definition of DB. */ -#define __SQL_C /* indicate that this is sql.c */ +#define __SQL_C /* indicate that this is sql.c */ #include "bacula.h" #include "cats.h" -#ifdef HAVE_SQLITE +#if HAVE_SQLITE || HAVE_SQLITE3 /* ----------------------------------------------------------------------- * @@ -44,14 +51,42 @@ * ----------------------------------------------------------------------- */ -extern char *working_directory; - /* List of open databases */ -static BQUEUE db_list = {&db_list, &db_list}; +static dlist *db_list = NULL; static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; -int QueryDB(char *file, int line, B_DB *db, char *select_cmd); +/* + * Retrieve database type + */ +const char * +db_get_type(void) +{ +#ifdef HAVE_SQLITE3 + return "SQLite3"; +#else + return "SQLite"; +#endif +} + +/* + * When using mult_db_connections = 1, + * sqlite can be BUSY. We just need sleep a little in this case. + */ + +#ifdef HAVE_SQLITE3 +static int my_busy_handler(void *arg, int calls) +{ + bmicrosleep(0, 500); + return 1; +} +#else +static int my_busy_handler(void *arg, const char* p, int calls) +{ + bmicrosleep(0, 500); + return 1; +} +#endif /* @@ -59,33 +94,46 @@ int QueryDB(char *file, int line, B_DB *db, char *select_cmd); * never have errors, or it is really fatal. */ B_DB * -db_init_database(void *jcr, char *db_name, char *db_user, char *db_password) +db_init_database(JCR *jcr, const char *db_name, const char *db_user, const char *db_password, + const char *db_address, int db_port, const char *db_socket, + int mult_db_connections) { - B_DB *mdb; + B_DB *mdb = NULL; - P(mutex); /* lock DB queue */ + P(mutex); /* lock DB queue */ + if (db_list == NULL) { + db_list = New(dlist(mdb, &mdb->link)); + } /* Look to see if DB already open */ - for (mdb=NULL; (mdb=(B_DB *)qnext(&db_list, &mdb->bq)); ) { - if (strcmp(mdb->db_name, db_name) == 0) { - Dmsg2(100, "DB REopen %d %s\n", mdb->ref_count, db_name); - mdb->ref_count++; - V(mutex); - return mdb; /* already open */ + if (!mult_db_connections) { + foreach_dlist(mdb, db_list) { + if (bstrcmp(mdb->db_name, db_name) && + bstrcmp(mdb->db_address, db_address) && + mdb->db_port == db_port) { + Dmsg2(300, "DB REopen %d %s\n", mdb->ref_count, db_name); + mdb->ref_count++; + V(mutex); + return mdb; /* already open */ + } } } - Dmsg0(100, "db_open first time\n"); - mdb = (B_DB *) malloc(sizeof(B_DB)); + Dmsg0(300, "db_open first time\n"); + mdb = (B_DB *)malloc(sizeof(B_DB)); memset(mdb, 0, sizeof(B_DB)); mdb->db_name = bstrdup(db_name); - mdb->have_insert_id = TRUE; mdb->errmsg = get_pool_memory(PM_EMSG); /* get error message buffer */ *mdb->errmsg = 0; mdb->cmd = get_pool_memory(PM_EMSG); /* get command buffer */ mdb->cached_path = get_pool_memory(PM_FNAME); mdb->cached_path_id = 0; mdb->ref_count = 1; - qinsert(&db_list, &mdb->bq); /* put db in list */ - mdb->jcr = jcr; + mdb->fname = get_pool_memory(PM_FNAME); + mdb->path = get_pool_memory(PM_FNAME); + mdb->esc_name = get_pool_memory(PM_FNAME); + mdb->esc_path = get_pool_memory(PM_FNAME); + mdb->esc_obj = get_pool_memory(PM_FNAME); + mdb->allow_transactions = mult_db_connections; + db_list->append(mdb); V(mutex); return mdb; } @@ -93,14 +141,17 @@ db_init_database(void *jcr, char *db_name, char *db_user, char *db_password) /* * Now actually open the database. This can generate errors, * which are returned in the errmsg + * + * DO NOT close the database or free(mdb) here !!!! */ int -db_open_database(B_DB *mdb) +db_open_database(JCR *jcr, B_DB *mdb) { char *db_name; int len; struct stat statbuf; int errstat; + int retry = 0; P(mutex); if (mdb->connected) { @@ -110,116 +161,210 @@ db_open_database(B_DB *mdb) mdb->connected = FALSE; if ((errstat=rwl_init(&mdb->lock)) != 0) { - Mmsg1(&mdb->errmsg, _("Unable to initialize DB lock. ERR=%s\n"), - strerror(errstat)); + berrno be; + Mmsg1(&mdb->errmsg, _("Unable to initialize DB lock. ERR=%s\n"), + be.bstrerror(errstat)); V(mutex); return 0; } /* open the database */ - len = strlen(working_directory) + strlen(mdb->db_name) + 5; + len = strlen(working_directory) + strlen(mdb->db_name) + 5; db_name = (char *)malloc(len); strcpy(db_name, working_directory); strcat(db_name, "/"); strcat(db_name, mdb->db_name); strcat(db_name, ".db"); if (stat(db_name, &statbuf) != 0) { - Mmsg1(&mdb->errmsg, _("Database %s does not exist, please create it.\n"), - db_name); + Mmsg1(&mdb->errmsg, _("Database %s does not exist, please create it.\n"), + db_name); free(db_name); V(mutex); return 0; } - mdb->db = sqlite_open( - db_name, /* database name */ - 644, /* mode */ - &mdb->sqlite_errmsg); /* error message */ - Dmsg0(50, "sqlite_open\n"); - + for (mdb->db=NULL; !mdb->db && retry++ < 10; ) { +#ifdef HAVE_SQLITE3 + int stat = sqlite3_open(db_name, &mdb->db); + if (stat != SQLITE_OK) { + mdb->sqlite_errmsg = (char *)sqlite3_errmsg(mdb->db); + sqlite3_close(mdb->db); + mdb->db = NULL; + } else { + mdb->sqlite_errmsg = NULL; + } +#else + mdb->db = sqlite_open( + db_name, /* database name */ + 644, /* mode */ + &mdb->sqlite_errmsg); /* error message */ +#endif + + Dmsg0(300, "sqlite_open\n"); + if (!mdb->db) { + bmicrosleep(1, 0); + } + } if (mdb->db == NULL) { Mmsg2(&mdb->errmsg, _("Unable to open Database=%s. ERR=%s\n"), db_name, mdb->sqlite_errmsg ? mdb->sqlite_errmsg : _("unknown")); free(db_name); V(mutex); return 0; - } + } + mdb->connected = true; free(db_name); - if (!check_tables_version(mdb)) { + + /* set busy handler to wait when we use mult_db_connections = 1 */ +#ifdef HAVE_SQLITE3 + sqlite3_busy_handler(mdb->db, my_busy_handler, NULL); +#else + sqlite_busy_handler(mdb->db, my_busy_handler, NULL); +#endif + +#if defined(HAVE_SQLITE3) && defined(SQLITE3_INIT_QUERY) + db_sql_query(mdb, SQLITE3_INIT_QUERY, NULL, NULL); +#endif + + if (!check_tables_version(jcr, mdb)) { V(mutex); return 0; } - mdb->connected = TRUE; + V(mutex); return 1; } void -db_close_database(B_DB *mdb) +db_close_database(JCR *jcr, B_DB *mdb) { + if (!mdb) { + return; + } + db_end_transaction(jcr, mdb); P(mutex); + sql_free_result(mdb); mdb->ref_count--; if (mdb->ref_count == 0) { - qdchain(&mdb->bq); + db_list->remove(mdb); if (mdb->connected && mdb->db) { - sqlite_close(mdb->db); + sqlite_close(mdb->db); } - rwl_destroy(&mdb->lock); + rwl_destroy(&mdb->lock); free_pool_memory(mdb->errmsg); free_pool_memory(mdb->cmd); free_pool_memory(mdb->cached_path); + free_pool_memory(mdb->fname); + free_pool_memory(mdb->path); + free_pool_memory(mdb->esc_name); + free_pool_memory(mdb->esc_path); + free_pool_memory(mdb->esc_obj); if (mdb->db_name) { - free(mdb->db_name); + free(mdb->db_name); } free(mdb); + if (db_list->size() == 0) { + delete db_list; + db_list = NULL; + } } V(mutex); } +void db_check_backend_thread_safe() +{ +#ifdef HAVE_BATCH_FILE_INSERT +# ifdef HAVE_SQLITE3_THREADSAFE + if (!sqlite3_threadsafe()) { + Emsg0(M_ABORT, 0, _("SQLite3 client library must be thread-safe " + "when using BatchMode.\n")); + } +# endif +#endif +} + +void db_thread_cleanup() +{ +#ifdef HAVE_SQLITE3 + sqlite3_thread_cleanup(); +#endif +} + /* * Return the next unique index (auto-increment) for * the given table. Return 0 on error. */ -int db_next_index(B_DB *mdb, char *table, char *index) +int db_next_index(JCR *jcr, B_DB *mdb, char *table, char *index) { - SQL_ROW row; + strcpy(index, "NULL"); + return 1; +} - db_lock(mdb); +/* + * Escape binary object so that SQLite is happy + * Memory is stored in B_DB struct, no need to free it + * + * TODO: this should be implemented (escape \0) + */ +char * +db_escape_object(JCR *jcr, B_DB *mdb, char *old, int len) +{ + char *n, *o; - Mmsg(&mdb->cmd, -"SELECT id FROM NextId WHERE TableName=\"%s\"", table); - if (!QUERY_DB(mdb, mdb->cmd)) { - Mmsg(&mdb->errmsg, _("next_index query error: ERR=%s\n"), sql_strerror(mdb)); - db_unlock(mdb); - return 0; - } - if ((row = sql_fetch_row(mdb)) == NULL) { - Mmsg(&mdb->errmsg, _("Error fetching index: ERR=%s\n"), sql_strerror(mdb)); - db_unlock(mdb); - return 0; + n = mdb->esc_obj = check_pool_memory_size(mdb->esc_obj, len*2+1); + o = old; + while (len--) { + switch (*o) { + case '\'': + *n++ = '\''; + *n++ = '\''; + o++; + break; + case 0: + *n++ = '\\'; + *n++ = 0; + o++; + break; + default: + *n++ = *o++; + break; + } } - strncpy(index, row[0], 28); - index[28] = 0; - sql_free_result(mdb); + *n = 0; + return mdb->esc_obj; +} - Mmsg(&mdb->cmd, -"UPDATE NextId SET id=id+1 WHERE TableName=\"%s\"", table); - if (!QUERY_DB(mdb, mdb->cmd)) { - Mmsg(&mdb->errmsg, _("next_index update error: ERR=%s\n"), sql_strerror(mdb)); - db_unlock(mdb); - return 0; +/* + * Unescape binary object so that SQLIte is happy + * + * TODO: need to be implemented (escape \0) + */ +void +db_unescape_object(JCR *jcr, B_DB *mdb, + char *from, int32_t expected_len, + POOLMEM **dest, int32_t *dest_len) +{ + if (!from) { + *dest[0] = 0; + *dest_len = 0; + return; } - sql_free_result(mdb); - - db_unlock(mdb); - return 1; -} - - + *dest = check_pool_memory_size(*dest, expected_len+1); + *dest_len = expected_len; + memcpy(*dest, from, expected_len); + (*dest)[expected_len]=0; +} +/* + * Escape strings so that SQLite is happy + * + * NOTE! len is the length of the old string. Your new + * string must be long enough (max 2*old+1) to hold + * the escaped output. + */ void -db_escape_string(char *snew, char *old, int len) +db_escape_string(JCR *jcr, B_DB *mdb, char *snew, char *old, int len) { char *n, *o; @@ -230,16 +375,16 @@ db_escape_string(char *snew, char *old, int len) case '\'': *n++ = '\''; *n++ = '\''; - o++; - break; + o++; + break; case 0: *n++ = '\\'; - *n++ = 0; - o++; - break; + *n++ = 0; + o++; + break; default: - *n++ = *o++; - break; + *n++ = *o++; + break; } } *n = 0; @@ -250,12 +395,12 @@ struct rh_data { void *ctx; }; -/* - * Convert SQLite's callback into Bacula DB callback +/* + * Convert SQLite's callback into Bacula DB callback */ static int sqlite_result(void *arh_data, int num_fields, char **rows, char **col_names) { - struct rh_data *rh_data = (struct rh_data *)arh_data; + struct rh_data *rh_data = (struct rh_data *)arh_data; if (rh_data->result_handler) { (*(rh_data->result_handler))(rh_data->ctx, num_fields, rows); @@ -267,49 +412,61 @@ static int sqlite_result(void *arh_data, int num_fields, char **rows, char **col * Submit a general SQL command (cmd), and for each row returned, * the sqlite_handler is called with the ctx. */ -int db_sql_query(B_DB *mdb, char *query, DB_RESULT_HANDLER *result_handler, void *ctx) +bool db_sql_query(B_DB *mdb, const char *query, DB_RESULT_HANDLER *result_handler, void *ctx) { struct rh_data rh_data; int stat; db_lock(mdb); if (mdb->sqlite_errmsg) { +#ifdef HAVE_SQLITE3 + sqlite3_free(mdb->sqlite_errmsg); +#else actuallyfree(mdb->sqlite_errmsg); +#endif mdb->sqlite_errmsg = NULL; } rh_data.result_handler = result_handler; rh_data.ctx = ctx; stat = sqlite_exec(mdb->db, query, sqlite_result, (void *)&rh_data, &mdb->sqlite_errmsg); - if (stat != 0) { - Mmsg(&mdb->errmsg, _("Query failed: %s: ERR=%s\n"), query, sql_strerror(mdb)); + if (stat != SQLITE_OK) { + Mmsg(mdb->errmsg, _("Query failed: %s: ERR=%s\n"), query, sql_strerror(mdb)); db_unlock(mdb); - return 0; + return false; } db_unlock(mdb); - return 1; + return true; } /* * Submit a sqlite query and retrieve all the data */ -int my_sqlite_query(B_DB *mdb, char *cmd) +int my_sqlite_query(B_DB *mdb, const char *cmd) { int stat; + my_sqlite_free_table(mdb); if (mdb->sqlite_errmsg) { +#ifdef HAVE_SQLITE3 + sqlite3_free(mdb->sqlite_errmsg); +#else actuallyfree(mdb->sqlite_errmsg); +#endif mdb->sqlite_errmsg = NULL; } - stat = sqlite_get_table(mdb->db, cmd, &mdb->result, &mdb->nrow, &mdb->ncolumn, - &mdb->sqlite_errmsg); - mdb->row = 0; /* row fetched */ + stat = sqlite_get_table(mdb->db, (char *)cmd, &mdb->result, &mdb->nrow, &mdb->ncolumn, + &mdb->sqlite_errmsg); + mdb->row = 0; /* no row fetched yet */ + if (stat != 0) { /* something went wrong */ + mdb->nrow = mdb->ncolumn = 0; + } return stat; } /* Fetch one row at a time */ SQL_ROW my_sqlite_fetch_row(B_DB *mdb) { - if (mdb->row >= mdb->nrow) { + if (!mdb->result || (mdb->row >= mdb->nrow)) { return NULL; } mdb->row++; @@ -318,135 +475,124 @@ SQL_ROW my_sqlite_fetch_row(B_DB *mdb) void my_sqlite_free_table(B_DB *mdb) { - unsigned int i; + int i; if (mdb->fields_defined) { for (i=0; i < sql_num_fields(mdb); i++) { - free(mdb->fields[i]); + if (mdb->fields[i]) { + free(mdb->fields[i]); + mdb->fields[i] = NULL; + } } - free(mdb->fields); - mdb->fields_defined = FALSE; + if (mdb->fields) { + free(mdb->fields); + mdb->fields = NULL; + } + mdb->fields_defined = false; + } + if (mdb->result) { + sqlite_free_table(mdb->result); + mdb->result = NULL; } - sqlite_free_table(mdb->result); - mdb->nrow = mdb->ncolumn = 0; + mdb->nrow = mdb->ncolumn = 0; } void my_sqlite_field_seek(B_DB *mdb, int field) { - unsigned int i, j; + int i, j; if (mdb->result == NULL) { + mdb->field = 0; return; } /* On first call, set up the fields */ if (!mdb->fields_defined && sql_num_fields(mdb) > 0) { mdb->fields = (SQL_FIELD **)malloc(sizeof(SQL_FIELD) * mdb->ncolumn); for (i=0; i < sql_num_fields(mdb); i++) { - mdb->fields[i] = (SQL_FIELD *)malloc(sizeof(SQL_FIELD)); - mdb->fields[i]->name = mdb->result[i]; - mdb->fields[i]->length = strlen(mdb->fields[i]->name); - mdb->fields[i]->max_length = mdb->fields[i]->length; - for (j=1; j <= (unsigned)mdb->nrow; j++) { - uint32_t len; - if (mdb->result[i + mdb->ncolumn *j]) { - len = (uint32_t)strlen(mdb->result[i + mdb->ncolumn * j]); - } else { - len = 0; - } - if (len > mdb->fields[i]->max_length) { - mdb->fields[i]->max_length = len; - } - } - mdb->fields[i]->type = 0; - mdb->fields[i]->flags = 1; /* not null */ + mdb->fields[i] = (SQL_FIELD *)malloc(sizeof(SQL_FIELD)); + /* ***FIXME*** it seems to me that this is wrong + * fields has lots of items + */ + if (mdb->result[i] == NULL) { + mdb->fields_defined = false; + free(mdb->fields); + mdb->fields = NULL; + mdb->field = 0; + return; + } + mdb->fields[i]->name = mdb->result[i]; + mdb->fields[i]->length = cstrlen(mdb->fields[i]->name); + mdb->fields[i]->max_length = mdb->fields[i]->length; + for (j=1; j <= mdb->nrow; j++) { + int len; + if (mdb->result[i + mdb->ncolumn *j]) { + len = (uint32_t)cstrlen(mdb->result[i + mdb->ncolumn * j]); + } else { + len = 0; + } + if (len > mdb->fields[i]->max_length) { + mdb->fields[i]->max_length = len; + } + } + mdb->fields[i]->type = 0; + mdb->fields[i]->flags = 1; /* not null */ } - mdb->fields_defined = TRUE; + mdb->fields_defined = true; } - if (field > (int)sql_num_fields(mdb)) { - field = (int)sql_num_fields(mdb); + if (sql_num_fields(mdb) <= 0) { + field = 0; + } else if (field > sql_num_fields(mdb) - 1) { + field = sql_num_fields(mdb) - 1; } mdb->field = field; - } SQL_FIELD *my_sqlite_fetch_field(B_DB *mdb) { - return mdb->fields[mdb->field++]; -} - -static void -list_dashes(B_DB *mdb, DB_LIST_HANDLER *send, void *ctx) -{ - SQL_FIELD *field; - unsigned int i, j; - - sql_field_seek(mdb, 0); - send(ctx, "+"); - for (i = 0; i < sql_num_fields(mdb); i++) { - field = sql_fetch_field(mdb); - for (j = 0; j < field->max_length + 2; j++) - send(ctx, "-"); - send(ctx, "+"); + if (mdb->fields_defined && mdb->field < sql_num_fields(mdb)) { + return mdb->fields[mdb->field++]; + } else { + mdb->field = 0; + return NULL; } - send(ctx, "\n"); } -void -list_result(B_DB *mdb, DB_LIST_HANDLER *send, void *ctx) +uint64_t my_sqlite_insert_autokey_record(B_DB *mdb, const char *query, const char *table_name) { - SQL_FIELD *field; - SQL_ROW row; - unsigned int i, col_len; - char buf[2000], ewc[30]; - - if (mdb->result == NULL || mdb->nrow == 0) { - send(ctx, _("No results to list.\n")); - return; - } - /* determine column display widths */ - sql_field_seek(mdb, 0); - for (i = 0; i < sql_num_fields(mdb); i++) { - field = sql_fetch_field(mdb); - if (IS_NUM(field->type) && field->max_length > 0) { /* fixup for commas */ - field->max_length += (field->max_length - 1) / 3; - } - col_len = strlen(field->name); - if (col_len < field->max_length) - col_len = field->max_length; - if (col_len < 4 && !IS_NOT_NULL(field->flags)) - col_len = 4; /* 4 = length of the word "NULL" */ - field->max_length = col_len; /* reset column info */ + /* + * First execute the insert query and then retrieve the currval. + */ + if (my_sqlite_query(mdb, query)) { + return 0; } - list_dashes(mdb, send, ctx); - send(ctx, "|"); - sql_field_seek(mdb, 0); - for (i = 0; i < sql_num_fields(mdb); i++) { - field = sql_fetch_field(mdb); - sprintf(buf, " %-*s |", field->max_length, field->name); - send(ctx, buf); - } - send(ctx, "\n"); - list_dashes(mdb, send, ctx); - - while ((row = sql_fetch_row(mdb)) != NULL) { - sql_field_seek(mdb, 0); - send(ctx, "|"); - for (i = 0; i < sql_num_fields(mdb); i++) { - field = sql_fetch_field(mdb); - if (row[i] == NULL) { - sprintf(buf, " %-*s |", field->max_length, "NULL"); - } else if (IS_NUM(field->type)) { - sprintf(buf, " %*s |", field->max_length, - add_commas(row[i], ewc)); - } else { - sprintf(buf, " %-*s |", field->max_length, row[i]); - } - send(ctx, buf); - } - send(ctx, "\n"); + mdb->num_rows = sql_affected_rows(mdb); + if (mdb->num_rows != 1) { + return 0; } - list_dashes(mdb, send, ctx); + + mdb->changes++; + +#ifdef HAVE_SQLITE3 + return sqlite3_last_insert_rowid(mdb->db); +#else + return sqlite_last_insert_rowid(mdb->db); +#endif } +#ifdef HAVE_BATCH_FILE_INSERT +const char *my_sqlite_batch_lock_query = "BEGIN"; +const char *my_sqlite_batch_unlock_query = "COMMIT"; + +const char *my_sqlite_batch_fill_path_query = + "INSERT INTO Path (Path)" + " SELECT DISTINCT Path FROM batch" + " EXCEPT SELECT Path FROM Path"; + +const char *my_sqlite_batch_fill_filename_query = + "INSERT INTO Filename (Name)" + " SELECT DISTINCT Name FROM batch " + " EXCEPT SELECT Name FROM Filename"; +#endif /* HAVE_BATCH_FILE_INSERT */ + #endif /* HAVE_SQLITE */