/* ** 2013-06-10 ** ** The author disclaims copyright to this source code. In place of ** a legal notice, here is a blessing: ** ** May you do good and not evil. ** May you find forgiveness for yourself and forgive others. ** May you share freely, never taking more than you give. ** ************************************************************************* */ #include #include #include #include #include #include #include #include "sqlite3.h" /* ** Implementation of the "readtext(X)" SQL function. The entire content ** of the file named X is read and returned as a TEXT value. It is assumed ** the file contains UTF-8 text. NULL is returned if the file does not ** exist or is unreadable. */ static void readfileFunc( sqlite3_context *context, int argc, sqlite3_value **argv ){ const char *zName; FILE *in; long nIn; void *pBuf; zName = (const char*)sqlite3_value_text(argv[0]); if( zName==0 ) return; in = fopen(zName, "rb"); if( in==0 ) return; fseek(in, 0, SEEK_END); nIn = ftell(in); rewind(in); pBuf = sqlite3_malloc( nIn ); if( pBuf && 1==fread(pBuf, nIn, 1, in) ){ sqlite3_result_text(context, pBuf, nIn, sqlite3_free); }else{ sqlite3_free(pBuf); } fclose(in); } /* ** Print usage text for this program and exit. */ static void showHelp(const char *zArgv0){ printf("\n" "Usage: %s SWITCHES... DB\n" "\n" " This program opens the database named on the command line and attempts to\n" " create an FTS table named \"fts\" with a single column. If successful, it\n" " recursively traverses the directory named by the -dir option and inserts\n" " the contents of each file into the fts table. All files are assumed to\n" " contain UTF-8 text.\n" "\n" "Switches are:\n" " -fts [345] FTS version to use (default=5)\n" " -idx [01] Create a mapping from filename to rowid (default=0)\n" " -dir Root of directory tree to load data from (default=.)\n" " -trans Number of inserts per transaction (default=1)\n" , zArgv0 ); exit(1); } /* ** Exit with a message based on the argument and the current value of errno. */ static void error_out(const char *zText){ fprintf(stderr, "%s: %s\n", zText, strerror(errno)); exit(-1); } /* ** Exit with a message based on the first argument and the error message ** currently stored in database handle db. */ static void sqlite_error_out(const char *zText, sqlite3 *db){ fprintf(stderr, "%s: %s\n", zText, sqlite3_errmsg(db)); exit(-1); } /* ** Context object for visit_file(). */ typedef struct VisitContext VisitContext; struct VisitContext { int nRowPerTrans; sqlite3 *db; /* Database handle */ sqlite3_stmt *pInsert; /* INSERT INTO fts VALUES(readtext(:1)) */ }; /* ** Callback used with traverse(). The first argument points to an object ** of type VisitContext. This function inserts the contents of the text ** file zPath into the FTS table. */ void visit_file(void *pCtx, const char *zPath){ int rc; VisitContext *p = (VisitContext*)pCtx; /* printf("%s\n", zPath); */ sqlite3_bind_text(p->pInsert, 1, zPath, -1, SQLITE_STATIC); sqlite3_step(p->pInsert); rc = sqlite3_reset(p->pInsert); if( rc!=SQLITE_OK ){ sqlite_error_out("insert", p->db); }else if( p->nRowPerTrans>0 && (sqlite3_last_insert_rowid(p->db) % p->nRowPerTrans)==0 ){ sqlite3_exec(p->db, "COMMIT ; BEGIN", 0, 0, 0); } } /* ** Recursively traverse directory zDir. For each file that is not a ** directory, invoke the supplied callback with its path. */ static void traverse( const char *zDir, /* Directory to traverse */ void *pCtx, /* First argument passed to callback */ void (*xCallback)(void*, const char *zPath) ){ DIR *d; struct dirent *e; d = opendir(zDir); if( d==0 ) error_out("opendir()"); for(e=readdir(d); e; e=readdir(d)){ if( strcmp(e->d_name, ".")==0 || strcmp(e->d_name, "..")==0 ) continue; char *zPath = sqlite3_mprintf("%s/%s", zDir, e->d_name); if (e->d_type & DT_DIR) { traverse(zPath, pCtx, xCallback); }else{ xCallback(pCtx, zPath); } sqlite3_free(zPath); } closedir(d); } int main(int argc, char **argv){ int iFts = 5; /* Value of -fts option */ int bMap = 0; /* True to create mapping table */ const char *zDir = "."; /* Directory to scan */ int i; int rc; int nRowPerTrans = 0; sqlite3 *db; char *zSql; VisitContext sCtx; if( argc % 2 ) showHelp(argv[0]); for(i=1; i<(argc-1); i+=2){ char *zOpt = argv[i]; char *zArg = argv[i+1]; if( strcmp(zOpt, "-fts")==0 ){ iFts = atoi(zArg); if( iFts!=3 && iFts!=4 && iFts!= 5) showHelp(argv[0]); } if( strcmp(zOpt, "-trans")==0 ){ nRowPerTrans = atoi(zArg); } else if( strcmp(zOpt, "-idx")==0 ){ bMap = atoi(zArg); if( bMap!=0 && bMap!=1 ) showHelp(argv[0]); } else if( strcmp(zOpt, "-dir")==0 ){ zDir = zArg; } } /* Open the database file */ rc = sqlite3_open(argv[argc-1], &db); if( rc!=SQLITE_OK ) sqlite_error_out("sqlite3_open()", db); rc = sqlite3_create_function(db, "readtext", 1, SQLITE_UTF8, 0, readfileFunc, 0, 0); if( rc!=SQLITE_OK ) sqlite_error_out("sqlite3_create_function()", db); /* Create the FTS table */ zSql = sqlite3_mprintf("CREATE VIRTUAL TABLE fts USING fts%d(content)", iFts); rc = sqlite3_exec(db, zSql, 0, 0, 0); if( rc!=SQLITE_OK ) sqlite_error_out("sqlite3_exec(1)", db); sqlite3_free(zSql); /* Compile the INSERT statement to write data to the FTS table. */ memset(&sCtx, 0, sizeof(VisitContext)); sCtx.db = db; sCtx.nRowPerTrans = nRowPerTrans; rc = sqlite3_prepare_v2(db, "INSERT INTO fts VALUES(readtext(?))", -1, &sCtx.pInsert, 0 ); if( rc!=SQLITE_OK ) sqlite_error_out("sqlite3_prepare_v2(1)", db); /* Load all files in the directory hierarchy into the FTS table. */ if( sCtx.nRowPerTrans>0 ) sqlite3_exec(db, "BEGIN", 0, 0, 0); traverse(zDir, (void*)&sCtx, visit_file); if( sCtx.nRowPerTrans>0 ) sqlite3_exec(db, "COMMIT", 0, 0, 0); /* Clean up and exit. */ sqlite3_finalize(sCtx.pInsert); sqlite3_close(db); return 0; }