Index: ext/fts1/fts1.c ================================================================== --- ext/fts1/fts1.c +++ ext/fts1/fts1.c @@ -845,29 +845,35 @@ static char *string_dup(const char *s){ return string_dup_n(s, strlen(s)); } /* Format a string, replacing each occurrence of the % character with - * zName. This may be more convenient than sqlite_mprintf() + * zDb.zName. This may be more convenient than sqlite_mprintf() * when one string is used repeatedly in a format string. * The caller must free() the returned string. */ -static char *string_format(const char *zFormat, const char *zName){ +static char *string_format(const char *zFormat, + const char *zDb, const char *zName){ const char *p; size_t len = 0; + size_t nDb = strlen(zDb); size_t nName = strlen(zName); + size_t nFullTableName = nDb+1+nName; char *result; char *r; /* first compute length needed */ for(p = zFormat ; *p ; ++p){ - len += (*p=='%' ? nName : 1); + len += (*p=='%' ? nFullTableName : 1); } len += 1; /* for null terminator */ r = result = malloc(len); for(p = zFormat; *p; ++p){ if( *p=='%' ){ + memcpy(r, zDb, nDb); + r += nDb; + *r++ = '.'; memcpy(r, zName, nName); r += nName; } else { *r++ = *p; } @@ -875,22 +881,23 @@ *r++ = '\0'; assert( r == result + len ); return result; } -static int sql_exec(sqlite3 *db, const char *zName, const char *zFormat){ - char *zCommand = string_format(zFormat, zName); +static int sql_exec(sqlite3 *db, const char *zDb, const char *zName, + const char *zFormat){ + char *zCommand = string_format(zFormat, zDb, zName); int rc; TRACE(("FTS1 sql: %s\n", zCommand)); rc = sqlite3_exec(db, zCommand, NULL, 0, NULL); free(zCommand); return rc; } -static int sql_prepare(sqlite3 *db, const char *zName, sqlite3_stmt **ppStmt, - const char *zFormat){ - char *zCommand = string_format(zFormat, zName); +static int sql_prepare(sqlite3 *db, const char *zDb, const char *zName, + sqlite3_stmt **ppStmt, const char *zFormat){ + char *zCommand = string_format(zFormat, zDb, zName); int rc; TRACE(("FTS1 prepare: %s\n", zCommand)); rc = sqlite3_prepare(db, zCommand, -1, ppStmt, NULL); free(zCommand); return rc; @@ -1038,10 +1045,11 @@ ** arguments. */ struct fulltext_vtab { sqlite3_vtab base; /* Base class used by SQLite core */ sqlite3 *db; /* The database connection */ + const char *zDb; /* logical database name */ const char *zName; /* virtual table name */ int nColumn; /* number of columns in virtual table */ char **azColumn; /* column names. malloced */ char **azContentColumn; /* column names in content table; malloced */ sqlite3_tokenizer *pTokenizer; /* tokenizer for inserts and queries */ @@ -1137,11 +1145,11 @@ case CONTENT_UPDATE_STMT: zStmt = contentUpdateStatement(v); break; default: zStmt = fulltext_zStatement[iStmt]; } - rc = sql_prepare(v->db, v->zName, &v->pFulltextStatements[iStmt], + rc = sql_prepare(v->db, v->zDb, v->zName, &v->pFulltextStatements[iStmt], zStmt); if( zStmt != fulltext_zStatement[iStmt]) free((void *) zStmt); if( rc!=SQLITE_OK ) return rc; } else { int rc = sqlite3_reset(v->pFulltextStatements[iStmt]); @@ -1740,10 +1748,11 @@ ** An instance of this structure defines the "spec" of a ** full text index. This structure is populated by parseSpec ** and use by fulltextConnect and fulltextCreate. */ typedef struct TableSpec { + const char *zDb; /* Logical database name */ const char *zName; /* Name of the full-text index */ int nColumn; /* Number of columns to be indexed */ char **azColumn; /* Original names of columns to be indexed */ char **azContentColumn; /* Column names for %_content */ char **azTokenizer; /* Name of tokenizer and its arguments */ @@ -1802,10 +1811,11 @@ } /* Identify the column names and the tokenizer and delimiter arguments ** in the argv[][] array. */ + pSpec->zDb = azArg[1]; pSpec->zName = azArg[2]; pSpec->nColumn = 0; pSpec->azColumn = azArg; zTokenizer = "tokenize simple"; for(i=3; ibase */ v->db = db; + v->zDb = spec->zDb; /* Freed when azColumn is freed */ v->zName = spec->zName; /* Freed when azColumn is freed */ v->nColumn = spec->nColumn; v->azContentColumn = spec->azContentColumn; spec->azContentColumn = 0; v->azColumn = spec->azColumn; @@ -2018,15 +2029,15 @@ initStringBuffer(&schema); append(&schema, "CREATE TABLE %_content("); appendList(&schema, spec.nColumn, spec.azContentColumn); append(&schema, ")"); - rc = sql_exec(db, spec.zName, schema.s); + rc = sql_exec(db, spec.zDb, spec.zName, schema.s); free(schema.s); if( rc!=SQLITE_OK ) goto out; - rc = sql_exec(db, spec.zName, + rc = sql_exec(db, spec.zDb, spec.zName, "create table %_term(term text, segment integer, doclist blob, " "primary key(term, segment));"); if( rc!=SQLITE_OK ) goto out; rc = constructVtab(db, &spec, ppVTab, pzErr); @@ -2080,11 +2091,11 @@ static int fulltextDestroy(sqlite3_vtab *pVTab){ fulltext_vtab *v = (fulltext_vtab *)pVTab; int rc; TRACE(("FTS1 Destroy %p\n", pVTab)); - rc = sql_exec(v->db, v->zName, + rc = sql_exec(v->db, v->zDb, v->zName, "drop table if exists %_content;" "drop table if exists %_term;" ); if( rc!=SQLITE_OK ) return rc; @@ -2837,11 +2848,11 @@ TRACE(("FTS1 Filter %p\n",pCursor)); zSql = sqlite3_mprintf("select rowid, * from %%_content %s", idxNum==QUERY_GENERIC ? "" : "where rowid=?"); sqlite3_finalize(c->pStmt); - rc = sql_prepare(v->db, v->zName, &c->pStmt, zSql); + rc = sql_prepare(v->db, v->zDb, v->zName, &c->pStmt, zSql); sqlite3_free(zSql); if( rc!=SQLITE_OK ) return rc; c->iCursorType = idxNum; switch( idxNum ){ Index: ext/fts2/fts2.c ================================================================== --- ext/fts2/fts2.c +++ ext/fts2/fts2.c @@ -1351,29 +1351,35 @@ static char *string_dup(const char *s){ return string_dup_n(s, strlen(s)); } /* Format a string, replacing each occurrence of the % character with - * zName. This may be more convenient than sqlite_mprintf() + * zDb.zName. This may be more convenient than sqlite_mprintf() * when one string is used repeatedly in a format string. * The caller must free() the returned string. */ -static char *string_format(const char *zFormat, const char *zName){ +static char *string_format(const char *zFormat, + const char *zDb, const char *zName){ const char *p; size_t len = 0; + size_t nDb = strlen(zDb); size_t nName = strlen(zName); + size_t nFullTableName = nDb+1+nName; char *result; char *r; /* first compute length needed */ for(p = zFormat ; *p ; ++p){ - len += (*p=='%' ? nName : 1); + len += (*p=='%' ? nFullTableName : 1); } len += 1; /* for null terminator */ r = result = malloc(len); for(p = zFormat; *p; ++p){ if( *p=='%' ){ + memcpy(r, zDb, nDb); + r += nDb; + *r++ = '.'; memcpy(r, zName, nName); r += nName; } else { *r++ = *p; } @@ -1381,22 +1387,23 @@ *r++ = '\0'; assert( r == result + len ); return result; } -static int sql_exec(sqlite3 *db, const char *zName, const char *zFormat){ - char *zCommand = string_format(zFormat, zName); +static int sql_exec(sqlite3 *db, const char *zDb, const char *zName, + const char *zFormat){ + char *zCommand = string_format(zFormat, zDb, zName); int rc; TRACE(("FTS2 sql: %s\n", zCommand)); rc = sqlite3_exec(db, zCommand, NULL, 0, NULL); free(zCommand); return rc; } -static int sql_prepare(sqlite3 *db, const char *zName, sqlite3_stmt **ppStmt, - const char *zFormat){ - char *zCommand = string_format(zFormat, zName); +static int sql_prepare(sqlite3 *db, const char *zDb, const char *zName, + sqlite3_stmt **ppStmt, const char *zFormat){ + char *zCommand = string_format(zFormat, zDb, zName); int rc; TRACE(("FTS2 prepare: %s\n", zCommand)); rc = sqlite3_prepare(db, zCommand, -1, ppStmt, NULL); free(zCommand); return rc; @@ -1541,10 +1548,11 @@ ** arguments. */ struct fulltext_vtab { sqlite3_vtab base; /* Base class used by SQLite core */ sqlite3 *db; /* The database connection */ + const char *zDb; /* logical database name */ const char *zName; /* virtual table name */ int nColumn; /* number of columns in virtual table */ char **azColumn; /* column names. malloced */ char **azContentColumn; /* column names in content table; malloced */ sqlite3_tokenizer *pTokenizer; /* tokenizer for inserts and queries */ @@ -1640,11 +1648,11 @@ case CONTENT_UPDATE_STMT: zStmt = contentUpdateStatement(v); break; default: zStmt = fulltext_zStatement[iStmt]; } - rc = sql_prepare(v->db, v->zName, &v->pFulltextStatements[iStmt], + rc = sql_prepare(v->db, v->zDb, v->zName, &v->pFulltextStatements[iStmt], zStmt); if( zStmt != fulltext_zStatement[iStmt]) free((void *) zStmt); if( rc!=SQLITE_OK ) return rc; } else { int rc = sqlite3_reset(v->pFulltextStatements[iStmt]); @@ -1714,11 +1722,11 @@ */ static int sql_get_leaf_statement(fulltext_vtab *v, int idx, sqlite3_stmt **ppStmt){ assert( idx>=0 && idxpLeafSelectStmts[idx]==NULL ){ - int rc = sql_prepare(v->db, v->zName, &v->pLeafSelectStmts[idx], + int rc = sql_prepare(v->db, v->zDb, v->zName, &v->pLeafSelectStmts[idx], LEAF_SELECT); if( rc!=SQLITE_OK ) return rc; }else{ int rc = sqlite3_reset(v->pLeafSelectStmts[idx]); if( rc!=SQLITE_OK ) return rc; @@ -2332,10 +2340,11 @@ ** An instance of this structure defines the "spec" of a ** full text index. This structure is populated by parseSpec ** and use by fulltextConnect and fulltextCreate. */ typedef struct TableSpec { + const char *zDb; /* Logical database name */ const char *zName; /* Name of the full-text index */ int nColumn; /* Number of columns to be indexed */ char **azColumn; /* Original names of columns to be indexed */ char **azContentColumn; /* Column names for %_content */ char **azTokenizer; /* Name of tokenizer and its arguments */ @@ -2394,10 +2403,11 @@ } /* Identify the column names and the tokenizer and delimiter arguments ** in the argv[][] array. */ + pSpec->zDb = azArg[1]; pSpec->zName = azArg[2]; pSpec->nColumn = 0; pSpec->azColumn = azArg; zTokenizer = "tokenize simple"; for(i=3; ibase */ v->db = db; + v->zDb = spec->zDb; /* Freed when azColumn is freed */ v->zName = spec->zName; /* Freed when azColumn is freed */ v->nColumn = spec->nColumn; v->azContentColumn = spec->azContentColumn; spec->azContentColumn = 0; v->azColumn = spec->azColumn; @@ -2581,18 +2592,19 @@ initStringBuffer(&schema); append(&schema, "CREATE TABLE %_content("); appendList(&schema, spec.nColumn, spec.azContentColumn); append(&schema, ")"); - rc = sql_exec(db, spec.zName, stringBufferData(&schema)); + rc = sql_exec(db, spec.zDb, spec.zName, stringBufferData(&schema)); stringBufferDestroy(&schema); if( rc!=SQLITE_OK ) goto out; - rc = sql_exec(db, spec.zName, "create table %_segments(block blob);"); + rc = sql_exec(db, spec.zDb, spec.zName, + "create table %_segments(block blob);"); if( rc!=SQLITE_OK ) goto out; - rc = sql_exec(db, spec.zName, + rc = sql_exec(db, spec.zDb, spec.zName, "create table %_segdir(" " level integer," " idx integer," " start_block integer," " leaves_end_block integer," @@ -2653,11 +2665,11 @@ static int fulltextDestroy(sqlite3_vtab *pVTab){ fulltext_vtab *v = (fulltext_vtab *)pVTab; int rc; TRACE(("FTS2 Destroy %p\n", pVTab)); - rc = sql_exec(v->db, v->zName, + rc = sql_exec(v->db, v->zDb, v->zName, "drop table if exists %_content;" "drop table if exists %_segments;" "drop table if exists %_segdir;" ); if( rc!=SQLITE_OK ) return rc; @@ -3417,11 +3429,11 @@ TRACE(("FTS2 Filter %p\n",pCursor)); zSql = sqlite3_mprintf("select rowid, * from %%_content %s", idxNum==QUERY_GENERIC ? "" : "where rowid=?"); sqlite3_finalize(c->pStmt); - rc = sql_prepare(v->db, v->zName, &c->pStmt, zSql); + rc = sql_prepare(v->db, v->zDb, v->zName, &c->pStmt, zSql); sqlite3_free(zSql); if( rc!=SQLITE_OK ) return rc; c->iCursorType = idxNum; switch( idxNum ){ ADDED test/fts1j.test Index: test/fts1j.test ================================================================== --- /dev/null +++ test/fts1j.test @@ -0,0 +1,89 @@ +# 2007 February 6 +# +# The author disclaims copyright to this source code. +# +#************************************************************************* +# This file implements regression tests for SQLite library. This +# tests creating fts1 tables in an attached database. +# +# $Id: fts1j.test,v 1.1 2007/02/07 01:01:18 shess Exp $ +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl + +# If SQLITE_ENABLE_FTS1 is defined, omit this file. +ifcapable !fts1 { + finish_test + return +} + +# Clean up anything left over from a previous pass. +file delete -force test2.db +file delete -force test2.db-journal +sqlite3 db2 test2.db + +db eval { + CREATE VIRTUAL TABLE t3 USING fts1(content); + INSERT INTO t3 (rowid, content) VALUES(1, "hello world"); +} + +db2 eval { + CREATE VIRTUAL TABLE t1 USING fts1(content); + INSERT INTO t1 (rowid, content) VALUES(1, "hello world"); + INSERT INTO t1 (rowid, content) VALUES(2, "hello there"); + INSERT INTO t1 (rowid, content) VALUES(3, "cruel world"); +} + +# This has always worked because the t1_* tables used by fts1 will be +# the defaults. +do_test fts1j-1.1 { + execsql { + ATTACH DATABASE 'test2.db' AS two; + SELECT rowid FROM t1 WHERE t1 MATCH 'hello'; + DETACH DATABASE two; + } +} {1 2} +# Make certain we're detached if there was an error. +catch {db eval {DETACH DATABASE two}} + +# In older code, this appears to work fine, but the t2_* tables used +# by fts1 will be created in database 'main' instead of database +# 'two'. It appears to work fine because the tables end up being the +# defaults, but obviously is badly broken if you hope to use things +# other than in the exact same ATTACH setup. +do_test fts1j-1.2 { + execsql { + ATTACH DATABASE 'test2.db' AS two; + CREATE VIRTUAL TABLE two.t2 USING fts1(content); + INSERT INTO t2 (rowid, content) VALUES(1, "hello world"); + INSERT INTO t2 (rowid, content) VALUES(2, "hello there"); + INSERT INTO t2 (rowid, content) VALUES(3, "cruel world"); + SELECT rowid FROM t2 WHERE t2 MATCH 'hello'; + DETACH DATABASE two; + } +} {1 2} +catch {db eval {DETACH DATABASE two}} + +# In older code, this broke because the fts1 code attempted to create +# t3_* tables in database 'main', but they already existed. Normally +# this wouldn't happen without t3 itself existing, in which case the +# fts1 code would never be called in the first place. +do_test fts1j-1.3 { + execsql { + ATTACH DATABASE 'test2.db' AS two; + + CREATE VIRTUAL TABLE two.t3 USING fts1(content); + INSERT INTO two.t3 (rowid, content) VALUES(2, "hello there"); + INSERT INTO two.t3 (rowid, content) VALUES(3, "cruel world"); + SELECT rowid FROM two.t3 WHERE t3 MATCH 'hello'; + + DETACH DATABASE two; + } db2 +} {2} +catch {db eval {DETACH DATABASE two}} + +catch {db2 close} +file delete -force test2.db + +finish_test ADDED test/fts2j.test Index: test/fts2j.test ================================================================== --- /dev/null +++ test/fts2j.test @@ -0,0 +1,89 @@ +# 2007 February 6 +# +# The author disclaims copyright to this source code. +# +#************************************************************************* +# This file implements regression tests for SQLite library. This +# tests creating fts2 tables in an attached database. +# +# $Id: fts2j.test,v 1.1 2007/02/07 01:01:18 shess Exp $ +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl + +# If SQLITE_ENABLE_FTS2 is defined, omit this file. +ifcapable !fts2 { + finish_test + return +} + +# Clean up anything left over from a previous pass. +file delete -force test2.db +file delete -force test2.db-journal +sqlite3 db2 test2.db + +db eval { + CREATE VIRTUAL TABLE t3 USING fts2(content); + INSERT INTO t3 (rowid, content) VALUES(1, "hello world"); +} + +db2 eval { + CREATE VIRTUAL TABLE t1 USING fts2(content); + INSERT INTO t1 (rowid, content) VALUES(1, "hello world"); + INSERT INTO t1 (rowid, content) VALUES(2, "hello there"); + INSERT INTO t1 (rowid, content) VALUES(3, "cruel world"); +} + +# This has always worked because the t1_* tables used by fts2 will be +# the defaults. +do_test fts2j-1.1 { + execsql { + ATTACH DATABASE 'test2.db' AS two; + SELECT rowid FROM t1 WHERE t1 MATCH 'hello'; + DETACH DATABASE two; + } +} {1 2} +# Make certain we're detached if there was an error. +catch {db eval {DETACH DATABASE two}} + +# In older code, this appears to work fine, but the t2_* tables used +# by fts2 will be created in database 'main' instead of database +# 'two'. It appears to work fine because the tables end up being the +# defaults, but obviously is badly broken if you hope to use things +# other than in the exact same ATTACH setup. +do_test fts2j-1.2 { + execsql { + ATTACH DATABASE 'test2.db' AS two; + CREATE VIRTUAL TABLE two.t2 USING fts2(content); + INSERT INTO t2 (rowid, content) VALUES(1, "hello world"); + INSERT INTO t2 (rowid, content) VALUES(2, "hello there"); + INSERT INTO t2 (rowid, content) VALUES(3, "cruel world"); + SELECT rowid FROM t2 WHERE t2 MATCH 'hello'; + DETACH DATABASE two; + } +} {1 2} +catch {db eval {DETACH DATABASE two}} + +# In older code, this broke because the fts2 code attempted to create +# t3_* tables in database 'main', but they already existed. Normally +# this wouldn't happen without t3 itself existing, in which case the +# fts2 code would never be called in the first place. +do_test fts2j-1.3 { + execsql { + ATTACH DATABASE 'test2.db' AS two; + + CREATE VIRTUAL TABLE two.t3 USING fts2(content); + INSERT INTO two.t3 (rowid, content) VALUES(2, "hello there"); + INSERT INTO two.t3 (rowid, content) VALUES(3, "cruel world"); + SELECT rowid FROM two.t3 WHERE t3 MATCH 'hello'; + + DETACH DATABASE two; + } db2 +} {2} +catch {db eval {DETACH DATABASE two}} + +catch {db2 close} +file delete -force test2.db + +finish_test